ADempiere Jasper report patch

This patch replaces Jasper included to version 3 and also contains a new ReportStarter class that accepts complex Jasper reports packed as a zip file.

 

Index: .project
===================================================================
--- .project	(revision 5753)
+++ .project	(working copy)
@@ -6,16 +6,6 @@
 	</projects>
 	<buildSpec>
 		<buildCommand>
-			<name>org.eclipse.ui.externaltools.ExternalToolBuilder</name>
-			<triggers>full,incremental,</triggers>
-			<arguments>
-				<dictionary>
-					<key>LaunchConfigHandle</key>
-					<value>&lt;project&gt;/.externalToolBuilders/org.eclipse.wst.common.project.facet.core.builder.launch</value>
-				</dictionary>
-			</arguments>
-		</buildCommand>
-		<buildCommand>
 			<name>org.eclipse.jdt.core.javabuilder</name>
 			<arguments>
 			</arguments>
Index: tools/lib/jfreechart-1.0.2.jar
===================================================================
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
Index: tools/lib/jfreechart-1.0.3.jar
===================================================================
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream

Property changes on: tools/lib/jfreechart-1.0.3.jar
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Index: tools/build.xml
===================================================================
--- tools/build.xml	(revision 5753)
+++ tools/build.xml	(working copy)
@@ -360,7 +360,7 @@
 		<patternset refid="manifest.exclude"/>
 	  </zipfileset>
       <!-- JFreeChart			-->
-      <zipfileset src="lib/jfreechart-1.0.2.jar" >
+      <zipfileset src="lib/jfreechart-1.0.3.jar" >
 		<patternset refid="manifest.exclude"/>
 	  </zipfileset>
       <zipfileset src="lib/jcommon-1.0.5.jar" >
Index: .classpath
===================================================================
--- .classpath	(revision 5753)
+++ .classpath	(working copy)
@@ -46,7 +46,7 @@
 	<classpathentry exported="true" kind="lib" path="tools/lib/jgraph.jar"/>
 	<classpathentry exported="true" kind="lib" path="tools/lib/barbecue-1.1.jar"/>
 	<classpathentry exported="true" kind="lib" path="tools/lib/jcommon-1.0.5.jar"/>
-	<classpathentry exported="true" kind="lib" path="tools/lib/jfreechart-1.0.2.jar"/>
+	<classpathentry exported="true" kind="lib" path="tools/lib/jfreechart-1.0.3.jar"/>
 	<classpathentry exported="true" kind="lib" path="tools/lib/postgresql.jar"/>
 	<classpathentry exported="true" kind="lib" path="posterita/src/web/WEB-INF/lib/struts.jar"/>
 	<classpathentry exported="true" kind="lib" path="posterita/src/web/WEB-INF/lib/barcode4j.jar"/>
@@ -70,7 +70,7 @@
 	<classpathentry exported="true" kind="lib" path="tools/lib/xml-apis.jar"/>
 	<classpathentry exported="true" kind="lib" path="tools/lib/itext-1.4.8.jar"/>
 	<classpathentry exported="true" kind="lib" path="tools/lib/jpedal.jar"/>
-	<classpathentry exported="true" kind="lib" path="JasperReportsTools/lib/jasperreports-1.3.0.jar"/>
+	<classpathentry exported="true" kind="lib" path="JasperReportsTools/lib/jasperreports-3.0.0.jar"/>
 	<classpathentry exported="true" kind="lib" path="tools/lib/swingx-0.9.0.jar"/>
 	<classpathentry exported="true" kind="lib" path="tools/lib/bsh-2.0b5.jar"/>
 	<classpathentry exported="true" kind="lib" path="tools/lib/bsh-engine.jar"/>
Index: JasperReportsTools/lib/iReport.jar
===================================================================
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
Index: JasperReportsTools/lib/jfreechart-1.0.3.jar
===================================================================
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream

Property changes on: JasperReportsTools/lib/jfreechart-1.0.3.jar
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Index: JasperReportsTools/lib/jasperreports-3.0.0.jar
===================================================================
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream

Property changes on: JasperReportsTools/lib/jasperreports-3.0.0.jar
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Index: JasperReports/src/org/compiere/report/FileVisitor.java
===================================================================
--- JasperReports/src/org/compiere/report/FileVisitor.java	(revision 0)
+++ JasperReports/src/org/compiere/report/FileVisitor.java	(revision 0)
@@ -0,0 +1,34 @@
+package org.compiere.report;
+
+
+import java.io.File;
+
+public class FileVisitor {
+	public interface Visitor
+	{
+		void visitFile(java.io.File file) throws Exception;
+	}
+	public static void visitRecursive(java.io.File dir, Visitor visitor) throws Exception
+	{
+		File[] fs=dir.listFiles();
+		if(fs!=null)
+		{
+			for(File f: fs)
+			{
+				visitor.visitFile(f);
+				visitRecursive(f, visitor);
+			}
+		}
+	}
+	public static void visit(java.io.File dir, Visitor visitor) throws Exception
+	{
+		File[] fs=dir.listFiles();
+		if(fs!=null)
+		{
+			for(File f: fs)
+			{
+				visitor.visitFile(f);
+			}
+		}
+	}
+}
Index: JasperReports/src/org/compiere/report/ReportStarter2.java
===================================================================
--- JasperReports/src/org/compiere/report/ReportStarter2.java	(revision 0)
+++ JasperReports/src/org/compiere/report/ReportStarter2.java	(revision 0)
@@ -0,0 +1,439 @@
+/*
+ */
+package org.compiere.report;
+
+import java.io.File;
+import java.io.FilenameFilter;
+import java.io.IOException;
+import java.math.BigDecimal;
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.sql.Timestamp;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Properties;
+import java.util.logging.Level;
+
+import net.sf.jasperreports.engine.JasperExportManager;
+import net.sf.jasperreports.engine.JasperFillManager;
+import net.sf.jasperreports.engine.JasperPrint;
+import net.sf.jasperreports.engine.JasperPrintManager;
+import net.sf.jasperreports.engine.JasperReport;
+
+import org.compiere.db.ServerConnection;
+import org.compiere.process.ClientProcess;
+import org.compiere.process.ProcessCall;
+import org.compiere.process.ProcessInfo;
+import org.compiere.process.ProcessInfoParameter;
+import org.compiere.util.CLogger;
+import org.compiere.util.DB;
+import org.compiere.util.Env;
+import org.compiere.util.Ini;
+import org.compiere.util.Language;
+import org.compiere.util.Trx;
+import org.compiere.utils.DBUtils;
+
+/**
+ * @author rlemeill originaly coming from an application note from
+ *         compiere.co.uk --- Modifications: Allow Jasper Reports to be able to
+ *         be run on VPN profile (i.e: no direct connection to DB). Implemented
+ *         ClientProcess for it to run on client.
+ * @author Ashley Ramdass
+ * @author victor.perez@e-evolution.com
+ * @see FR 1906632
+ *      http://sourceforge.net/tracker/?func=detail&atid=879335&aid=1906632&grou...
+ */
+public class ReportStarter2 implements ProcessCall, ClientProcess {
+	// logger
+	private static CLogger log = CLogger.getCLogger(ReportStarter2.class);
+
+	private static JRViewerProvider viewerProvider = new SwingJRViewerProvider();
+
+	static {
+		System.setProperty("javax.xml.parsers.SAXParserFactory",
+				"org.apache.xerces.jaxp.SAXParserFactoryImpl");
+		System.setProperty("org.xml.sax.driver",
+				"org.apache.xerces.parsers.SAXParser");
+	}
+
+	private ProcessInfo processInfo;
+
+
+	/**
+	 * Returns the Server Connection if direct connection is not available (VPN,
+	 * WAN, Terminal) and thus query has to be run on server only else return a
+	 * direct connection to DB.
+	 * 
+	 * Notes: Need to refactor and integrate in DB if confirmed to be working as
+	 * expected.
+	 * 
+	 * @author Ashley Ramdass
+	 * @return Connection DB Connection
+	 */
+	protected Connection getConnection() {
+		if (DB.isRemoteObjects()) {
+			return new ServerConnection();
+		} else {
+			return DB.getConnectionRW();
+		}
+	}
+
+	static JasperReportCache jasperReportCache;
+
+	void initCache() throws Exception {
+		if (jasperReportCache == null) {
+			jasperReportCache = new JasperReportCache();
+		}
+	}
+
+	/**
+	 * Start the process. Called then pressing the Process button in R_Request.
+	 * It should only return false, if the function could not be performed as
+	 * this causes the process to abort.
+	 * 
+	 * @author rlemeill
+	 * @param ctx
+	 *            context
+	 * @param pi
+	 *            Compiere standard process info
+	 * @param trx
+	 * @return true if success
+	 */
+	public boolean startProcess(Properties ctx, ProcessInfo pi, Trx trx) {
+
+		processInfo = pi;
+		String Name = pi.getTitle();
+		int AD_PInstance_ID = pi.getAD_PInstance_ID();
+		int Record_ID = pi.getRecord_ID();
+		log.info("Name=" + Name + "  AD_PInstance_ID=" + AD_PInstance_ID
+				+ " Record_ID=" + Record_ID);
+		String trxName = null;
+		if (trx != null) {
+			trxName = trx.getTrxName();
+		}
+		try {
+			initCache();
+			ReportData reportData = getReportData(pi, trxName);
+			if (reportData == null) {
+				reportResult(AD_PInstance_ID, "Can not find report data",
+						trxName);
+				return false;
+			}
+
+			String reportPath = reportData.getReportFilePath();
+			if ((reportPath == null) || (reportPath.length() == 0)) {
+				throw new RuntimeException("Can not find report - report file name not set");
+			}
+
+			HashMap<String, Object> params = new HashMap(ctx);
+		
+
+			addProcessParameters(AD_PInstance_ID, params, trxName);
+
+			addProcessInfoParameters(params, pi.getParameter());
+			JasperReportCache.FileLoadEnvironment fileLoadEnvironment=jasperReportCache. new FileLoadEnvironment(pi, reportPath);
+			JasperReport jasperReport = fileLoadEnvironment.getRootReportFile();
+
+			if (jasperReport != null) {
+				if (Record_ID > 0)
+					params.put("RECORD_ID", new Integer(Record_ID));
+
+				// contribution from Ricardo (ralexsander)
+				// in iReports you can 'SELECT' AD_Client_ID, AD_Org_ID and
+				// AD_User_ID using only AD_PINSTANCE_ID
+				params.put("AD_PINSTANCE_ID", new Integer(AD_PInstance_ID));
+
+				Language currLang = Env.getLanguage(Env.getCtx());
+				params.put("CURRENT_LANG", currLang.getAD_Language());
+				
+				fileLoadEnvironment.fillParams(params);
+				Connection conn = getConnection();
+				try {
+					JasperPrint jasperPrint = JasperFillManager.fillReport(
+							jasperReport, params, conn);
+					if (reportData.isDirectPrint()) {
+						log.info("ReportStarter.startProcess print report -"
+								+ jasperPrint.getName());
+
+						// RF 1906632
+						if (!processInfo.isBatch())
+							JasperPrintManager.printReport(jasperPrint, false);
+						else {
+							// You can use JasperPrint to create PDF
+							// Used For the PH
+							try {
+								File PDF = File.createTempFile("mail", ".pdf");
+								JasperExportManager.exportReportToPdfFile(
+										jasperPrint, PDF.getAbsolutePath());
+								processInfo.setPDFReport(PDF);
+							} catch (IOException e) {
+								log
+										.severe("ReportStarter.startProcess: Can not make PDF File - "
+												+ e.getMessage());
+							}
+						}
+
+						// You can use JasperPrint to create PDF
+						// JasperExportManager.exportReportToPdfFile(jasperPrint,
+						// "BasicReport.pdf");
+					} else {
+						log.info("ReportStarter.startProcess run report -"
+								+ jasperPrint.getName());
+						JRViewerProvider viewerLauncher = getReportViewerProvider();
+						viewerLauncher.openViewer(jasperPrint, pi.getTitle()
+								+ " - " + reportPath);
+					}
+				} finally {
+					conn.close();
+				}
+			}
+			reportResult(AD_PInstance_ID, null, trxName);
+		} catch (Exception e) {
+			log.log(Level.SEVERE, "Jasper report process error", e);
+			reportResult(AD_PInstance_ID, e.toString(), trxName);
+			return false;
+		}
+
+		return true;
+	}
+
+
+	/**
+	 * @author rlemeill
+	 * @param AD_PInstance_ID
+	 * @param errMsg
+	 */
+	protected void reportResult(int AD_PInstance_ID, String errMsg,
+			String trxName) {
+		int result = (errMsg == null) ? 1 : 0;
+		errMsg = (errMsg == null) ? "" : errMsg;
+		String sql = "UPDATE AD_PInstance SET result=" + result
+				+ ", errormsg='" + errMsg + "' " + "WHERE AD_PInstance_ID="
+				+ AD_PInstance_ID;
+		Statement pstmt = null;
+		try {
+			pstmt = DB.createStatement(ResultSet.TYPE_FORWARD_ONLY,
+					ResultSet.CONCUR_UPDATABLE, trxName);
+			pstmt.executeUpdate(sql);
+			pstmt.close();
+		} catch (SQLException e) {
+			log.severe(sql + e.getMessage());
+		} finally {
+			DBUtils.close(pstmt);
+		}
+	}
+
+	protected void addProcessParameters(int AD_PInstance_ID,
+			Map<String, Object> params, String trxName) {
+		log.info("");
+		String sql = "SELECT ParameterName, " + "P_String, " + "P_String_To, "
+				+ "P_Number, " + "P_Number_To, " + "P_Date, " + "P_Date_To "
+				+ "FROM AD_PInstance_Para " + "WHERE AD_PInstance_ID=?";
+		PreparedStatement pstmt = null;
+		ResultSet rs = null;
+		try {
+			pstmt = DB.prepareStatement(sql, ResultSet.TYPE_FORWARD_ONLY,
+					ResultSet.CONCUR_READ_ONLY, trxName);
+			pstmt.setInt(1, AD_PInstance_ID);
+			rs = pstmt.executeQuery();
+			while (rs.next()) {
+				String name = rs.getString(1);
+				String pStr = rs.getString(2);
+				String pStrTo = rs.getString(3);
+				// Double pNum = new Double( rs.getDouble(4));
+				// Double pNumTo = new Double( rs.getDouble(5));
+				BigDecimal pNum = rs.getBigDecimal(4);
+				BigDecimal pNumTo = rs.getBigDecimal(5);
+
+				Timestamp pDate = rs.getTimestamp(6);
+				Timestamp pDateTo = rs.getTimestamp(7);
+				if (pStr != null) {
+					if (pStrTo != null) {
+						params.put(name + "1", pStr);
+						params.put(name + "2", pStrTo);
+					} else {
+						params.put(name, pStr);
+					}
+				} else if (pDate != null) {
+					if (pDateTo != null) {
+						params.put(name + "1", pDate);
+						params.put(name + "2", pDateTo);
+					} else {
+						params.put(name, pDate);
+					}
+				} else if (pNum != null) {
+					if (rs.getBigDecimal(5) != null) {
+						params.put(name + "1", pNum);
+						params.put(name + "2", pNumTo);
+					} else {
+						params.put(name, pNum);
+					}
+				}
+			}
+		} catch (SQLException e) {
+			log.severe("Execption; sql = " + sql + "; e.getMessage() = "
+					+ e.getMessage());
+		} finally {
+			DBUtils.close(rs);
+			DBUtils.close(pstmt);
+		}
+	}
+
+	private void addProcessInfoParameters(Map<String, Object> params,
+			ProcessInfoParameter[] para) {
+		if (para != null) {
+			for (int i = 0; i < para.length; i++) {
+				if (para[i].getParameter_To() == null) {
+					params.put(para[i].getParameterName(), para[i]
+							.getParameter());
+				} else {
+					params.put(para[i].getParameterName() + "1", para[i]
+							.getParameter());
+					params.put(para[i].getParameterName() + "2", para[i]
+							.getParameter_To());
+				}
+			}
+		}
+	}
+
+	/**
+	 * @author rlemeill
+	 * @param ProcessInfo
+	 * @return ReportData
+	 */
+	public ReportData getReportData(ProcessInfo pi, String trxName) {
+		log.info("");
+		String sql = "SELECT pr.JasperReport, pr.IsDirectPrint "
+				+ "FROM AD_Process pr, AD_PInstance pi "
+				+ "WHERE pr.AD_Process_ID = pi.AD_Process_ID "
+				+ " AND pi.AD_PInstance_ID=?";
+		PreparedStatement pstmt = null;
+		ResultSet rs = null;
+		try {
+			pstmt = DB.prepareStatement(sql, ResultSet.TYPE_FORWARD_ONLY,
+					ResultSet.CONCUR_READ_ONLY, trxName);
+			pstmt.setInt(1, pi.getAD_PInstance_ID());
+			rs = pstmt.executeQuery();
+			String path = null;
+			boolean directPrint = false;
+			boolean isPrintPreview = pi.isPrintPreview();
+			if (rs.next()) {
+				path = rs.getString(1);
+
+				if ("Y".equalsIgnoreCase(rs.getString(2))
+						&& !Ini.isPropertyBool(Ini.P_PRINTPREVIEW)
+						&& !isPrintPreview)
+					directPrint = true;
+			} else {
+				log.severe("data not found; sql = " + sql);
+				return null;
+			}
+
+			return new ReportData(path, directPrint);
+		} catch (SQLException e) {
+			log.severe("sql = " + sql + "; e.getMessage() = " + e.getMessage());
+			return null;
+		} finally {
+			DBUtils.close(rs);
+			DBUtils.close(pstmt);
+		}
+	}
+
+	/**
+	 * Set jasper report viewer provider.
+	 * 
+	 * @param provider
+	 */
+	public static void setReportViewerProvider(JRViewerProvider provider) {
+		if (provider == null)
+			throw new IllegalArgumentException(
+					"Cannot set report viewer provider to null");
+		viewerProvider = provider;
+	}
+
+	/**
+	 * Get the current jasper report viewer provider
+	 * 
+	 * @return JRViewerProvider
+	 */
+	public static JRViewerProvider getReportViewerProvider() {
+		return viewerProvider;
+	}
+
+	class ReportData {
+		private String reportFilePath;
+		private boolean directPrint;
+
+		public ReportData(String reportFilePath, boolean directPrint) {
+			this.reportFilePath = reportFilePath;
+			this.directPrint = directPrint;
+		}
+
+		public String getReportFilePath() {
+			return reportFilePath;
+		}
+
+		public boolean isDirectPrint() {
+			return directPrint;
+		}
+	}
+
+	class JasperData {
+		private JasperReport jasperReport;
+		private File reportDir;
+		private String jasperName;
+		private File jasperFile;
+
+		public JasperData(JasperReport jasperReport, File reportDir,
+				String jasperName, File jasperFile) {
+			this.jasperReport = jasperReport;
+			this.reportDir = reportDir;
+			this.jasperName = jasperName;
+			this.jasperFile = jasperFile;
+		}
+
+		public JasperReport getJasperReport() {
+			return jasperReport;
+		}
+
+		public File getReportDir() {
+			return reportDir;
+		}
+
+		public String getJasperName() {
+			return jasperName;
+		}
+
+		public File getJasperFile() {
+			return jasperFile;
+		}
+	}
+
+	class FileFilter implements FilenameFilter {
+		private String reportStart;
+		private File directory;
+		private String extension;
+
+		public FileFilter(String reportStart, File directory, String extension) {
+			this.reportStart = reportStart;
+			this.directory = directory;
+			this.extension = extension;
+		}
+
+		public boolean accept(File file, String name) {
+			if (file.equals(directory)) {
+				if (name.startsWith(reportStart)) {
+					int pos = name.lastIndexOf(extension);
+					if ((pos != -1)
+							&& (pos == (name.length() - extension.length())))
+						return true;
+				}
+			}
+			return false;
+		}
+	}
+
+}
\ No newline at end of file
Index: JasperReports/src/org/compiere/report/JasperReportCache.java
===================================================================
--- JasperReports/src/org/compiere/report/JasperReportCache.java	(revision 0)
+++ JasperReports/src/org/compiere/report/JasperReportCache.java	(revision 0)
@@ -0,0 +1,397 @@
+package org.compiere.report;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeSet;
+
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.xpath.XPath;
+import javax.xml.xpath.XPathConstants;
+import javax.xml.xpath.XPathExpression;
+import javax.xml.xpath.XPathExpressionException;
+import javax.xml.xpath.XPathFactory;
+
+import net.sf.jasperreports.engine.JRException;
+import net.sf.jasperreports.engine.JasperCompileManager;
+import net.sf.jasperreports.engine.JasperReport;
+import net.sf.jasperreports.engine.util.FileResolver;
+import net.sf.jasperreports.engine.util.JRLoader;
+
+import org.compiere.process.ProcessInfo;
+import org.compiere.util.CLogger;
+import org.compiere.util.ZipUtil;
+import org.compiere.utils.DigestOfFile;
+import org.w3c.dom.Document;
+import org.w3c.dom.NodeList;
+import org.xml.sax.SAXException;
+
+/**
+ * Cache compiled jasper reports on the client. Handle several type of jasper
+ * report file source.
+ * 
+ * @author schmidta
+ * 
+ */
+public class JasperReportCache {
+	FileSource fileSource = new FileSource();
+
+	@Override
+	public String toString() {
+		return super.toString() + " on " + cacheDir;
+	}
+
+	public JasperReportCache() throws Exception {
+		init();
+	}
+
+	public class FileLoadEnvironment
+	{
+		ProcessInfo pi;
+		String jasperPath;
+		File jasperDir;
+		public FileLoadEnvironment(ProcessInfo pi, String jasperPath) throws XPathExpressionException, IOException, SAXException, ParserConfigurationException, JRException {
+			super();
+			this.pi = pi;
+			this.jasperPath=jasperPath;
+			jasperDir=checkLocalCopyOfZip();
+		}
+		public JasperReport getRootReportFile() throws Exception {
+			return (JasperReport) JRLoader.loadObject(new File(jasperDir, "report.jasper"));
+		}
+		public FileResolver getFileResolver() {
+			return new FileResolver()
+			{
+				public File resolveFile(String arg0) {
+					File ret=new File(jasperDir, arg0);
+					log.info("Resolving file: "+arg0 +" to: "+ret);
+					return ret;
+				}
+			};
+		}
+		@Override
+		public String toString() {
+			return "Jasper zip: "+jasperPath;
+		}
+		public void fillParams(Map<String, Object> params) {
+			params.put(net.sf.jasperreports.engine.JRParameter.REPORT_FILE_RESOLVER,
+					getFileResolver());
+			params.put("SUBREPORT_DIR", "");
+		}
+		
+		/**
+		 * Check and create or refresh the local copy of a report file.
+		 * 
+		 * @param reportPath
+		 * @return
+		 * @throws IOException
+		 * @throws ParserConfigurationException
+		 * @throws SAXException
+		 * @throws XPathExpressionException
+		 * @throws JRException
+		 */
+		protected File checkLocalCopyOfZip() throws IOException,
+				XPathExpressionException, SAXException,
+				ParserConfigurationException, JRException {
+			try {
+				String name = getFileName(jasperPath);
+				File localFile = new File(cacheDir, name);
+				File zipDir=new File(localFile.getParentFile(),
+						localFile.getName().substring(0,
+								localFile.getName().length()-".zip".length()));
+				if (!isFresh(this, localFile, this.jasperPath)) {
+					UtilFile.deleteRec(zipDir);
+					fileSource.copyTo(this, jasperPath, localFile);
+					log.info("unzipping: "+localFile.getAbsolutePath()+" to: "+zipDir.getAbsolutePath());
+					ZipUtil.unzip(localFile, 
+							zipDir);
+					compileAll(zipDir);
+					processed.add(jasperPath);
+				}
+				return zipDir;
+			} catch (Exception e) {
+				throw new RuntimeException("Error resolving "
+						+ this, e);
+			}
+		}
+
+	}
+	interface IFileSource {
+		boolean handles(FileLoadEnvironment loadEnvironment,String fileName) throws Exception;
+
+		String getMd5(FileLoadEnvironment loadEnvironment, String fileName) throws Exception;
+
+		void copyTo(FileLoadEnvironment loadEnvironment, String fileName, File target) throws Exception;
+	}
+
+	class FileSource implements IFileSource {
+		List<IFileSource> types;
+
+		public FileSource() {
+			types = new ArrayList<IFileSource>();
+			types.add(new JasperFileSourceAttachment());
+			types.add(new JasperFileSourceURL());
+			types.add(new JasperFileSourceFile());
+		}
+
+		IFileSource getType(FileLoadEnvironment loadEnvironment, String reportPath) throws Exception {
+			for (IFileSource type : types) {
+				if (type.handles(loadEnvironment, reportPath)) {
+					return type;
+				}
+			}
+			throw new IOException("report file name is not handled: "
+					+ reportPath);
+		}
+
+		public String getMd5(FileLoadEnvironment loadEnvironment, String fileName) throws Exception {
+			IFileSource type = getType(loadEnvironment, fileName);
+			return type.getMd5(loadEnvironment, fileName);
+		}
+
+		public void copyTo(FileLoadEnvironment loadEnvironment, String fileName, File target) throws Exception {
+			IFileSource type = getType(loadEnvironment, fileName);
+			type.copyTo(loadEnvironment, fileName, target);
+		}
+
+		public boolean handles(FileLoadEnvironment loadEnvironment, String fileName) throws Exception {
+			return getType(loadEnvironment, fileName) != null;
+		}
+	}
+
+	private static CLogger log = CLogger.getCLogger(JasperReportCache.class);
+
+	File cacheDir;
+	boolean inited = false;
+
+	void init() throws Exception {
+		if (!inited) {
+			cacheDir = File.createTempFile("jasper", "cache");
+//			cacheDir=new File("/tmp/jasperCache");
+			UtilFile.deleteRec(cacheDir);
+			cacheDir.mkdir();
+			inited = true;
+		}
+	}
+
+	String getFileName(String reportPath) {
+		int idx = reportPath.lastIndexOf("/");
+		if (idx >= 0) {
+			reportPath = reportPath.substring(idx + 1);
+		}
+		idx = reportPath.lastIndexOf(File.separator);
+		if (idx >= 0) {
+			reportPath = reportPath.substring(idx + 1);
+		}
+		return reportPath;
+	}
+
+	String getFileNameWithoutExtension(String reportPath) {
+		int idx = reportPath.lastIndexOf("/");
+		if (idx >= 0) {
+			reportPath = reportPath.substring(idx + 1);
+		}
+		idx = reportPath.lastIndexOf('.');
+		if (idx >= 0) {
+			reportPath = reportPath.substring(0, idx);
+		}
+		return reportPath;
+	}
+
+	String getFilePath(String reportPath) {
+		int idx = reportPath.lastIndexOf("/");
+		if (idx >= 0) {
+			reportPath = reportPath.substring(0, idx + 1);
+			return reportPath;
+		}
+		return "";
+	}
+
+	String getFileExtension(String reportPath) {
+		int idx = reportPath.lastIndexOf(".");
+		if (idx >= 0) {
+			return reportPath.substring(idx);
+		}
+		return "";
+	}
+	/**
+	 * Check and create or refresh the local copy of a report file.
+	 * 
+	 * @param reportPath
+	 * @return
+	 * @throws IOException
+	 * @throws ParserConfigurationException
+	 * @throws SAXException
+	 * @throws XPathExpressionException
+	 * @throws JRException
+	 */
+	private File checkLocalCopyOfFile(FileLoadEnvironment loadEnvironment, String reportPath) throws IOException,
+			XPathExpressionException, SAXException,
+			ParserConfigurationException, JRException {
+		try {
+			String name = getFileName(reportPath);
+			String path = getFilePath(reportPath);
+			String extension = getFileExtension(reportPath);
+			File localFile = new File(cacheDir, name);
+			File localJasper = new File(cacheDir,
+					getFileNameWithoutExtension(reportPath)+".jasper");
+			if (!isFresh(loadEnvironment, localFile, reportPath)) {
+				fileSource.copyTo(loadEnvironment, reportPath, localFile);
+				JasperCompileManager.compileReportToFile(localFile
+						.getAbsolutePath(), localJasper.getAbsolutePath());
+			}
+			List<String> subreps = getSubreportPaths(localFile);
+			for (String s : subreps) {
+				if (s.endsWith(".jasper")) {
+					s = s.substring(0, s.length() - ".jasper".length())
+							+ extension;
+				}
+				String subReportPath = path + s;
+				checkLocalCopyOfFile(loadEnvironment, subReportPath);
+			}
+			return localJasper;
+		} catch (Exception e) {
+			throw new RuntimeException("Error resolving Jasper file: "
+					+ reportPath, e);
+		}
+	}
+
+	private void compileAll(File zipDir) throws Exception {
+		FileVisitor.visitRecursive(zipDir, new FileVisitor.Visitor(){
+			public void visitFile(File file) throws Exception{
+				if(file.exists()&&file.isFile()&&file.getName().endsWith(".jrxml"))
+				{
+					File localJasper = new File(file.getParentFile(),
+							getFileNameWithoutExtension(file.getName())+".jasper");
+					JasperCompileManager.compileReportToFile(file
+								.getAbsolutePath(), localJasper.getAbsolutePath());
+				}
+			}});
+	}
+
+	/**
+	 * @author rlemeill Correct the class path if loaded from java web start
+	 */
+	private void JWScorrectClassPath() {
+		URL jasperreportsAbsoluteURL = Thread.currentThread()
+				.getContextClassLoader().getResource(
+						"net/sf/jasperreports/engine");
+		String jasperreportsAbsolutePath = "";
+
+		if (jasperreportsAbsoluteURL.toString().startsWith("jar:http:")
+				|| jasperreportsAbsoluteURL.toString().startsWith("jar:https:")) {
+			// Jasper classes are deployed to a webserver (Java Webstart)
+			jasperreportsAbsolutePath = jasperreportsAbsoluteURL.toString()
+					.split("!")[0].split("jar:")[1];
+
+			// Download the required jasper libraries if they are not already
+			// existing
+			File reqLib = new File(System.getProperty("java.io.tmpdir"),
+					"CompiereJasperReqs.jar");
+			if (!reqLib.exists() && !(reqLib.length() > 0)) {
+				try {
+					URL reqLibURL = new URL(jasperreportsAbsolutePath);
+					InputStream in = reqLibURL.openStream();
+
+					FileOutputStream fout = new FileOutputStream(reqLib);
+
+					byte buf[] = new byte[1024];
+					int s = 0;
+
+					while ((s = in.read(buf, 0, 1024)) > 0)
+						fout.write(buf, 0, s);
+
+					in.close();
+					fout.flush();
+					fout.close();
+				} catch (FileNotFoundException e) {
+					log.warning("Required library not found " + e.getMessage());
+					reqLib.delete();
+					reqLib = null;
+				} catch (IOException e) {
+					log
+							.severe("I/O error downloading required library from server "
+									+ e.getMessage());
+					reqLib.delete();
+					reqLib = null;
+				}
+			}
+
+			jasperreportsAbsolutePath = reqLib.getAbsolutePath();
+		} else {
+			// Jasper classes are locally available (Local client)
+			jasperreportsAbsolutePath = jasperreportsAbsoluteURL.toString()
+					.split("!")[0].split("file:")[1];
+		}
+
+		if (jasperreportsAbsolutePath != null
+				&& !jasperreportsAbsolutePath.trim().equals("")) {
+			// Check whether the current CLASSPATH already contains our
+			// jasper libraries and dependencies or not.
+			if (System.getProperty("java.class.path").indexOf(
+					jasperreportsAbsolutePath) < 0) {
+				System.setProperty("java.class.path", System
+						.getProperty("java.class.path")
+						+ System.getProperty("path.separator")
+						+ jasperreportsAbsolutePath);
+				log.info("Classpath has been corrected to "
+						+ System.getProperty("java.class.path"));
+			}
+		}
+	}
+
+	static List<String> getSubreportPaths(File f)
+			throws XPathExpressionException, SAXException, IOException,
+			ParserConfigurationException {
+		List<String> ret = new ArrayList<String>();
+		String expression = "//subreport/subreportExpression";
+		// Compile the expression to get a XPathExpression object.
+		Document xmlDocument = DocumentBuilderFactory.newInstance()
+				.newDocumentBuilder().parse(f);
+		XPathFactory xPathFactory = XPathFactory.newInstance();
+		// To get an instance of the XPathFactory object itself.
+
+		XPath xPath = xPathFactory.newXPath();
+		// Create an instance of XPath from the factory class.
+		XPathExpression xPathExpression = xPath.compile(expression);
+		Object result = xPathExpression.evaluate(xmlDocument,
+				XPathConstants.NODESET);
+		NodeList l = (NodeList) result;
+		XPathExpression toString = xPath.compile(".");
+		for (int i = 0; i < l.getLength(); ++i) {
+			Object item = l.item(i);
+			String s = "" + toString.evaluate(item);
+			int a = s.indexOf('\"');
+			int b = s.lastIndexOf('\"');
+			if (a >= 0 && b > a) {
+				ret.add(s.substring(a + 1, b));
+			}
+		}
+		return ret;
+	}
+	Set<String> processed=new TreeSet<String>();
+	private boolean isFresh(FileLoadEnvironment loadEnvironment, File localFile, String reportPath)
+			throws Exception {
+		if (!localFile.exists()) {
+			return false;
+		}
+		if(processed.contains(reportPath))
+		{
+			String md5local = DigestOfFile.GetLocalMD5Hash(localFile);
+			String md5remote = fileSource.getMd5(loadEnvironment, reportPath);
+			return md5local.equals(md5remote);
+		}else
+		{
+			return false;
+		}
+	}
+
+}
Index: JasperReports/src/org/compiere/report/JasperFileSourceFile.java
===================================================================
--- JasperReports/src/org/compiere/report/JasperFileSourceFile.java	(revision 0)
+++ JasperReports/src/org/compiere/report/JasperFileSourceFile.java	(revision 0)
@@ -0,0 +1,65 @@
+package org.compiere.report;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+
+import org.compiere.report.JasperReportCache.FileLoadEnvironment;
+import org.compiere.util.Ini;
+import org.compiere.util.ZipUtil;
+import org.compiere.utils.DigestOfFile;
+
+public class JasperFileSourceFile implements JasperReportCache.IFileSource {
+	private static File REPORT_HOME = null;
+	static {
+		String reportPath = System.getProperty("org.compiere.report.path");
+		if (reportPath == null) {
+			REPORT_HOME = new File(Ini.getAdempiereHome() + File.separator
+					+ "reports");
+		} else {
+			REPORT_HOME = new File(reportPath);
+		}
+	}
+
+	File getFile(FileLoadEnvironment loadEnvironment, String reportPath) throws URISyntaxException {
+		if (reportPath.startsWith("/")) {
+			return new File(reportPath);
+		} else if (reportPath.startsWith("file:/")) {
+			return new File(new URI(reportPath));
+		} else {
+			return new File(REPORT_HOME, reportPath);
+		}
+	}
+
+	public void copyTo(FileLoadEnvironment loadEnvironment, String fileName, File target) throws IOException, URISyntaxException {
+		File source=getFile(loadEnvironment, fileName);
+		copy(source,target);
+	}
+
+	void copy(File inputFile, File outputFile) throws IOException {
+		FileInputStream in = new FileInputStream(inputFile);
+		try {
+			FileOutputStream out = new FileOutputStream(outputFile);
+			try {
+				ZipUtil.copyInputStream(in, out);
+			} finally {
+				out.close();
+			}
+		} finally {
+			in.close();
+		}
+	}
+
+	public String getMd5(FileLoadEnvironment loadEnvironment, String fileName) throws IOException, URISyntaxException {
+		String md5local = DigestOfFile.GetLocalMD5Hash(getFile(loadEnvironment, fileName));
+		return md5local;
+	}
+
+	public boolean handles(FileLoadEnvironment loadEnvironment, String fileName) {
+		return true;
+	}
+
+}
Index: JasperReports/src/org/compiere/report/JasperFileSourceURL.java
===================================================================
--- JasperReports/src/org/compiere/report/JasperFileSourceURL.java	(revision 0)
+++ JasperReports/src/org/compiere/report/JasperFileSourceURL.java	(revision 0)
@@ -0,0 +1,69 @@
+package org.compiere.report;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+import org.compiere.model.MAttachment;
+import org.compiere.model.MAttachmentEntry;
+import org.compiere.model.MProcess;
+import org.compiere.report.JasperReportCache.FileLoadEnvironment;
+import org.compiere.util.Env;
+import org.compiere.utils.DigestOfFile;
+
+public class JasperFileSourceURL implements JasperReportCache.IFileSource {
+
+	MAttachmentEntry getAttachment(FileLoadEnvironment loadEnvironment, 
+			String fileName) throws IOException
+	{
+		String name = fileName.substring(prefix.length()).trim();
+		if(name.startsWith("//"))
+		{
+			name=name.substring(2);
+		}
+		MProcess process = new MProcess(Env.getCtx(),
+				loadEnvironment.
+				pi.getAD_Process_ID(),
+				loadEnvironment.pi.getTransactionName()); 
+		MAttachment attachment = process.getAttachment();
+		if (attachment != null) {
+			MAttachmentEntry[] entries = attachment.getEntries();
+			for (int i = 0; i < entries.length; i++) {
+				if (entries[i].getName().equals(name)) {
+					return entries[i];
+				}
+			}
+		}
+		throw new IOException("Process attachment does not exist: "+fileName+" on process: "+loadEnvironment.pi.getAD_Process_ID());
+	}
+	String prefix="attachment:";
+	public void copyTo(FileLoadEnvironment loadEnvironment, String fileName, File target) throws Exception {
+		MAttachmentEntry entry=getAttachment(loadEnvironment, fileName);
+		FileOutputStream fos=new FileOutputStream(target);
+		try
+		{
+			fos.write(entry.getData());
+		}finally
+		{
+			fos.close();
+		}
+	}
+
+	public String getMd5(FileLoadEnvironment loadEnvironment, String fileName) throws Exception {
+		MAttachmentEntry entry=getAttachment(loadEnvironment, fileName);
+		return DigestOfFile.getMD5Hash(entry.getData());
+	}
+
+	public boolean handles(FileLoadEnvironment loadEnvironment, String fileName) throws Exception {
+		return false;
+//		java.net.URL url=new URL(fileName);
+//		InputStream is=url.openStream();
+//		try
+//		{
+//			return is!=null;
+//		}finally
+//		{
+//			is.close();
+//		}
+	}
+}
Index: JasperReports/src/org/compiere/report/UtilFile.java
===================================================================
--- JasperReports/src/org/compiere/report/UtilFile.java	(revision 0)
+++ JasperReports/src/org/compiere/report/UtilFile.java	(revision 0)
@@ -0,0 +1,57 @@
+package org.compiere.report;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+
+public class UtilFile {
+	public static String loadResourceAsString(Class<?> c, String res) throws IOException
+	{
+		URL url=c.getResource(res);
+		InputStream is=url.openStream();
+		try
+		{
+			StringBuilder ret=new StringBuilder();
+			InputStreamReader isr=new InputStreamReader(is,"UTF-8");
+			char[] chs=new char[1024];
+			int count;
+			while((count=isr.read(chs))>0)
+			{
+				ret.append(chs,0,count);
+			}
+			return ret.toString();
+		}finally
+		{
+			is.close();
+		}
+	}
+
+	public static void deleteRec(File toDelete) throws Exception {
+		if(!toDelete.exists())
+		{
+			return;
+		}
+		final List<File> l=new ArrayList<File>();
+		l.add(toDelete);
+		FileVisitor.visitRecursive(toDelete, new FileVisitor.Visitor()
+		{
+			public void visitFile(File file) throws Exception {
+				l.add(file);
+			}
+		});
+		Collections.reverse(l);
+		for(File f:l)
+		{
+			if(!f.delete())
+			{
+				throw new IOException("Cannot delete file: "+f);
+			}
+		}
+	}
+}
Index: JasperReports/src/org/compiere/report/JasperFileSourceAttachment.java
===================================================================
--- JasperReports/src/org/compiere/report/JasperFileSourceAttachment.java	(revision 0)
+++ JasperReports/src/org/compiere/report/JasperFileSourceAttachment.java	(revision 0)
@@ -0,0 +1,60 @@
+package org.compiere.report;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+import org.compiere.model.MAttachment;
+import org.compiere.model.MAttachmentEntry;
+import org.compiere.model.MProcess;
+import org.compiere.report.JasperReportCache.FileLoadEnvironment;
+import org.compiere.util.Env;
+import org.compiere.utils.DigestOfFile;
+
+public class JasperFileSourceAttachment implements JasperReportCache.IFileSource {
+
+	MAttachmentEntry getAttachment(FileLoadEnvironment loadEnvironment, 
+			String fileName) throws IOException
+	{
+		String name = fileName.substring(prefix.length()).trim();
+		if(name.startsWith("//"))
+		{
+			name=name.substring(2);
+		}
+		MProcess process = new MProcess(Env.getCtx(),
+				loadEnvironment.
+				pi.getAD_Process_ID(),
+				loadEnvironment.pi.getTransactionName()); 
+		MAttachment attachment = process.getAttachment();
+		if (attachment != null) {
+			MAttachmentEntry[] entries = attachment.getEntries();
+			for (int i = 0; i < entries.length; i++) {
+				if (entries[i].getName().equals(name)) {
+					return entries[i];
+				}
+			}
+		}
+		throw new IOException("Process attachment does not exist: "+fileName+" on process: "+loadEnvironment.pi.getAD_Process_ID());
+	}
+	String prefix="attachment:";
+	public void copyTo(FileLoadEnvironment loadEnvironment, String fileName, File target) throws Exception {
+		MAttachmentEntry entry=getAttachment(loadEnvironment, fileName);
+		FileOutputStream fos=new FileOutputStream(target);
+		try
+		{
+			fos.write(entry.getData());
+		}finally
+		{
+			fos.close();
+		}
+	}
+
+	public String getMd5(FileLoadEnvironment loadEnvironment, String fileName) throws Exception {
+		MAttachmentEntry entry=getAttachment(loadEnvironment, fileName);
+		return DigestOfFile.getMD5Hash(entry.getData());
+	}
+
+	public boolean handles(FileLoadEnvironment loadEnvironment, String fileName) throws Exception {
+		return fileName.startsWith(prefix);
+	}
+}
Index: JasperReports/build.xml
===================================================================
--- JasperReports/build.xml	(revision 5753)
+++ JasperReports/build.xml	(working copy)
@@ -64,7 +64,7 @@
 	      <pathelement path="../lib/CSTools.jar"/>
 	      <pathelement path="../looks/CLooks.jar"/>
 	      <pathelement path="../lib/oracle.jar"/>
-		  <pathelement path="../JasperReportsTools/lib/jasperreports-1.3.0.jar"/>
+		  <pathelement path="../JasperReportsTools/lib/jasperreports-3.0.0.jar"/>
 	      <pathelement path="../JasperReportsTools/lib/commons-digester-1.7.jar"/>
 	      <pathelement path="../tools/lib/commons-collections-3.1.jar"/>
 	      <pathelement path="../tools/lib/commons-logging.jar"/>
@@ -124,14 +124,14 @@
   	    </patternset>
 	</unjar>
   	<!-- futur dev jfree chart but it needs scriptlets
-  	<unjar src="../tools/lib/jfreechart-0.9.21.jar" dest="${needed.dir}" />
+  	<unjar src="../tools/lib/jfreechart-1.0.3.jar" dest="${needed.dir}" />
   	-->
   	<jar jarfile="${dist.dir}/${needed.jar.name}.jar"
 	  excludes="**/*.jbx"
 	  index="yes"
   	  duplicate="preserve">
       <fileset dir="${needed.dir}"/>
-  	  <zipfileset src="../JasperReportsTools/lib/jasperreports-1.3.0.jar" >
+  	  <zipfileset src="../JasperReportsTools/lib/jasperreports-3.0.0.jar" >
 		<patternset refid="manifest.exclude"/>
 	  </zipfileset>
   	  <zipfileset src="../JasperReportsTools/lib/commons-digester-1.7.jar" >
Index: base/src/org/compiere/util/ZipUtil.java
===================================================================
--- base/src/org/compiere/util/ZipUtil.java	(revision 5753)
+++ base/src/org/compiere/util/ZipUtil.java	(working copy)
@@ -435,5 +435,63 @@
 			System.err.println(ex);
 		}
 	}
+	
+	public static final void copyInputStream(InputStream in, OutputStream out)
+			throws IOException {
+		byte[] buffer = new byte[1024];
+		int len;
 
+		while ((len = in.read(buffer)) >= 0)
+			out.write(buffer, 0, len);
+
+		in.close();
+		out.close();
+	}
+
+	static CLogger log = CLogger.getCLogger(ZipUtil.class);
+
+	/**
+	 * Unzip a zip file into a directory.
+	 * 
+	 * @param zipF
+	 * @param toDir
+	 */
+	public static void unzip(File zipF, File toDir) {
+
+		try {
+			ZipFile zipFile = new ZipFile(zipF);
+
+			Enumeration<? extends ZipEntry> entries = zipFile.entries();
+			log.info("unzipping file: " + zipF.getAbsolutePath() + " todir: "
+					+ toDir.getAbsolutePath());
+
+			while (entries.hasMoreElements()) {
+				ZipEntry entry = entries.nextElement();
+				File target = new File(toDir, entry.getName());
+				if (entry.isDirectory()) {
+					// Assume directories are stored parents first then
+					// children.
+					System.err.println("Extracting directory: "
+							+ entry.getName());
+					// This is not robust, just for demonstration purposes.
+					target.mkdirs();
+				} else {
+					log.info("unzipping file to: " + target.getAbsolutePath());
+					target.getParentFile().mkdirs();
+					copyInputStream(zipFile.getInputStream(entry),
+							new BufferedOutputStream(new FileOutputStream(
+									target)));
+
+				}
+			}
+
+			zipFile.close();
+		} catch (IOException ioe) {
+			System.err.println("Unhandled exception:");
+			ioe.printStackTrace();
+			return;
+		}
+	}
+
+
 }	//	ZipUtil
Index: client/build.xml
===================================================================
--- client/build.xml	(revision 5753)
+++ client/build.xml	(working copy)
@@ -28,7 +28,7 @@
     <pathelement path="../lib/customization.jar"/>
     <pathelement path="../lib/patches.jar"/>
     <pathelement path="../lib/jcommon-1.0.5.jar"/>
-    <pathelement path="../lib/jfreechart-1.0.2.jar"/>
+    <pathelement path="../lib/jfreechart-1.0.3.jar"/>
     <pathelement path="../looks/CLooks.jar"/>
     <pathelement path="../extend/Extend.jar"/>
     <pathelement path="../tools/lib/j2ee.jar"/>