package com.cfinkel.reports.web; import com.cfinkel.reports.ReportSessionInfo; import com.cfinkel.reports.exceptions.BadReportSyntaxException; import com.cfinkel.reports.generatedbeans.ReportElement; import com.cfinkel.reports.util.DirectoryIterator; import com.cfinkel.reports.util.RunnerException; import com.cfinkel.reports.util.RunnerForFile; import com.cfinkel.reports.wrappers.Report; import org.apache.log4j.Logger; import org.springframework.jdbc.core.JdbcTemplate; import javax.naming.Context; import javax.naming.InitialContext; import javax.naming.NamingException; import javax.servlet.ServletContext; import javax.servlet.http.HttpSession; import javax.sql.DataSource; import javax.xml.bind.JAXBContext; import javax.xml.bind.JAXBException; import javax.xml.bind.Unmarshaller; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; import java.io.OutputStream; import java.net.MalformedURLException; import java.net.URL; import java.net.URLClassLoader; import java.sql.SQLException; import java.util.*; import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.regex.Pattern; /** * $Author: charles $ * $Revision: 8904 $ * $Date: 2006-05-01 18:02:06 -0400 (Mon, 01 May 2006) $ * <p/> * Created by IntelliJ IDEA. * User: charles * Date: Apr 14, 2006 * Time: 1:41:10 AM * <p/> * Stores all app-scoped data: * Lots of static data */ public class AppData { private static final Logger log = Logger.getLogger(AppData.class); /** * holds custom classes for QueryGenerator and PostQueryProcessor interfaces */ private static URLClassLoader customClassLoader; static File classDirectory; static Unmarshaller reportUnmarshaller; static Context initContext; private static ServletContext context; public static ReentrantReadWriteLock getReportsLock() { return reportsLock; } static List<HttpSession> sessions = Collections.synchronizedList(new ArrayList<HttpSession>()); private static final ReentrantReadWriteLock reportsLock = new ReentrantReadWriteLock(); /** * all the datasources: */ private static Map<String, DataSource> dataSources; // must start with a slash: private static String reportsURL = "/report"; private static File reportsDirectory; static { try { initContext = new InitialContext(); } catch (NamingException e) { log.error("Error getting initial context", e); throw new RuntimeException(e); } } static JAXBContext getJAXBContext() { return jaxBContext; } static JAXBContext jaxBContext; /** * Data structure that holds all reports: */ private static Map<String, Report> reports = Collections.synchronizedMap(new HashMap<String, Report>()); static void setReportsDirectory(File reportsDirectory) { AppData.reportsDirectory = reportsDirectory; } static void setDataSources(Map<String, DataSource> dataSources) { AppData.dataSources = dataSources; } static void setCustomClassLoader(URLClassLoader customClassLoader) { AppData.customClassLoader = customClassLoader; } public static URLClassLoader getCustomClassLoader() { return customClassLoader; } public static Map<String, DataSource> getDataSources() { return dataSources; } public static DataSource addAndReturnDataSource(String datasource) throws NamingException, SQLException { String jndiPrefix = "java:comp/env/jdbc/" + datasource; DataSource ds = (DataSource) initContext.lookup(jndiPrefix); // runReport dummy query if not HSQL (probably should do if not other DB's as well) if (!ds.getConnection().getMetaData().getDriverName().equals("HSQL Database Engine Driver")) { JdbcTemplate jdbcTemplate = new JdbcTemplate(ds); jdbcTemplate.execute("select null from dual"); } dataSources.put(datasource, ds); log.info("Added Datasource " + datasource); return ds; } public static String getReportsURL() { return reportsURL; } public static File getReportsDirectory() { return reportsDirectory; } public static Map<String, Report> getReports() { return reports; } static void loadAllClassFiles(File dir, final String baseDir) throws RunnerException { RunnerForFile runnerForFile = new RunnerForFile() { public void run(File file) throws RunnerException { try { // convert to string: String classString = file.toURI().toURL().toString(); // remove .class: classString = classString.substring(0, classString.length() - 6); // remove c:/etc: classString = classString.substring(baseDir.length(), classString.length()); // convert / to .: classString = classString.replaceAll("/", "."); getCustomClassLoader().loadClass(classString); } catch (MalformedURLException e) { throw new RunnerException("Malformed URL Exception", e); } catch (ClassNotFoundException e) { throw new RunnerException("Class not found exception", e); } } }; DirectoryIterator directoryIterator = new DirectoryIterator(Pattern.compile(".*\\.class"), runnerForFile); directoryIterator.iterateForDirectory(dir); } /** * updates custom classes */ static void updateCustomClasses() { for (String reportPath : getReports().keySet()) { Report report = getReports().get(reportPath); try { report.updateCustomClasses(); } catch (BadReportSyntaxException e) { log.error("Error updating class for report at path " + reportPath + ", unloading report from the app."); getReports().remove(reportPath); } } } static void reloadClasses() throws IOException, RunnerException { // call classloader to clear all references to old classes: URL[] classURLs = new URL[1]; classURLs[0] = classDirectory.toURI().toURL(); setCustomClassLoader(new URLClassLoader(classURLs, Thread.currentThread().getContextClassLoader())); loadAllClassFiles(classDirectory, classDirectory.toURI().toURL().toString()); } /** * get all paths to reports * * @return list of paths to reports */ public static List<String> getReportPaths() throws RunnerException { return getReportPaths(reportsDirectory); } private static List<String> getReportPaths(File dir) throws RunnerException { final List<String> reportPaths = new ArrayList<String>(); RunnerForFile runnerForFile = new RunnerForFile() { public void run(File file) throws RunnerException { try { // convert to string: String xmlString = file.toURI().toURL().toString(); // remove .xml: xmlString = xmlString.substring(0, xmlString.length() - 4); // remove c:/etc: xmlString = xmlString.substring( reportsDirectory.toURI().toURL().toString().length() - 1, xmlString.length()); reportPaths.add(xmlString); } catch (MalformedURLException e) { throw new RunnerException("malformedURLException", e); } } }; DirectoryIterator directoryIterator = new DirectoryIterator(Pattern.compile(".*\\.xml"), runnerForFile); directoryIterator.iterateForDirectory(dir); return reportPaths; } static void unloadReports() { getReports().clear(); } public static void loadAllReports(OutputStream outputStream) throws IOException { List<String> reportPaths; try { reportPaths = getReportPaths(); for (String reportPath : reportPaths) { loadReport(reportPath, outputStream); } } catch (RunnerException e) { log.error(e); outputStream.write(("Exception getting all report paths: " + e.getMessage() + "\n").getBytes()); } } public static void loadReport(String reportPath, OutputStream outputStream) throws IOException { // first check ti see that this report isn't already here: if (reports.get(reportPath) != null) { String message = "Report at path " + reportPath + " is already loaded.\n"; outputStream.write(message.getBytes()); return; } try { ReportElement reportElement = (ReportElement) reportUnmarshaller.unmarshal (new File(AppData.getReportsDirectory() + reportPath + ".xml")); Report report = new Report(reportElement, reportPath); reports.put(reportPath, report); String message = "Successfully loaded report at path " + reportPath + "\n\r"; outputStream.write(message.getBytes()); } catch (BadReportSyntaxException e) { String message = "Failed to load report at path '" + reportPath + "' with exception: " + e + "\n"; log.info(message, e); outputStream.write(message.getBytes()); } catch (JAXBException e) { String message = "Failed to load report at path '" + reportPath + "' with JAXB exception: " + e + "\n"; log.info(message); outputStream.write(message.getBytes()); } } public static void unloadReport(String reportPath, OutputStream outputStream) throws IOException { Report report = getReports().get(reportPath); if (report == null) { outputStream.write(("No report at '" + reportPath + "' is loaded.\n").getBytes()); } // remove session data that attaches to this report: for (HttpSession session : sessions) { if (session.getAttribute(AttributeNames.reportSessionInfos) != null) { Map<String, ReportSessionInfo> reportSessionInfos = (Map<String, ReportSessionInfo>) session.getAttribute(AttributeNames.reportSessionInfos); if (reportSessionInfos.get(reportPath) != null) { reportSessionInfos.remove(reportPath); outputStream.write(("Removed session data for this report for session id #" + session.getId() + "\n").getBytes()); } } } // remove report from app scope: getReports().remove(reportPath); outputStream.write(("Successfully unloaded report at " + reportPath + "\n").getBytes()); } public static void clearSessionData(OutputStream outputStream) throws IOException { for (HttpSession session : sessions) { if (session.getAttribute(AttributeNames.reportSessionInfos) != null) { session.removeAttribute(AttributeNames.reportSessionInfos); outputStream.write(("Removed session info for session id #" + session.getId() + "\n").getBytes()); } } } public static void reloadReport(String reportPath, OutputStream outputStream) throws IOException { unloadReport(reportPath, outputStream); loadReport(reportPath, outputStream); } public static void reloadLoadedReports(ByteArrayOutputStream outputStream) throws IOException { // clone report names: String[] reportPaths = reports.keySet().toArray(new String[0]).clone(); for (String reportPath : reportPaths) { reloadReport(reportPath, outputStream); } } public static void setServletContext(ServletContext c) { context = c; } public static ServletContext getContext() { return context; } }