/* * $Id$ * * Authors: * Jeff Buchbinder <jeff@freemedsoftware.org> * * REMITT Electronic Medical Information Translation and Transmission * Copyright (C) 1999-2014 FreeMED Software Foundation * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ package org.remitt.server; import it.sauronsoftware.cron4j.Scheduler; import java.io.File; import java.net.MalformedURLException; import java.net.URL; import java.rmi.RemoteException; import java.sql.CallableStatement; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; import java.util.List; import javax.servlet.http.HttpServlet; import javax.xml.rpc.ServiceException; import org.apache.axis.client.Call; import org.apache.axis.client.Stub; import org.apache.commons.configuration.CompositeConfiguration; import org.apache.commons.configuration.ConfigurationException; import org.apache.commons.configuration.PropertiesConfiguration; import org.apache.commons.configuration.reloading.FileChangedReloadingStrategy; import org.apache.log4j.Logger; import org.pb.x12.FormatException; import org.remitt.client.RemittCallback.RemittCallback_PortType; import org.remitt.client.RemittCallback.RemittCallback_Service; import org.remitt.client.RemittCallback.RemittCallback_ServiceLocator; import org.remitt.datastore.UserManagement; import org.remitt.parser.X12Message835; import org.remitt.prototype.ConfigurationOption; import org.remitt.prototype.EligibilityInterface; import org.remitt.prototype.PluginInterface; import org.remitt.prototype.UserDTO; import com.mchange.v2.c3p0.ComboPooledDataSource; public class Configuration { public static String DEFAULT_CONFIG = "/WEB-INF/remitt.properties"; public static String OVERRIDE_CONFIG = System.getProperty("remitt.properties"); static final Logger log = Logger.getLogger(Configuration.class); protected static CompositeConfiguration compositeConfiguration = null; protected static HttpServlet servletContext = null; protected static ControlThread controlThread = null; protected static ComboPooledDataSource comboPooledDataSource = null; protected static Scheduler scheduler = null; /** * Get servlet object. * * @return */ public static HttpServlet getServletContext() { return servletContext; } /** * Store servlet object. * * @param hS */ public static void setServletContext(HttpServlet hS) { servletContext = hS; } /** * Get current global configuration object. * * @return */ public static CompositeConfiguration getConfiguration() { if (compositeConfiguration == null) { Configuration.loadConfiguration(); } if (compositeConfiguration == null) { log .error("Should never be null here, configuration is failing to load!"); } return compositeConfiguration; } /** * Load configuration from both template and override properties files. */ public static void loadConfiguration() { log.trace("Entered loadConfiguration"); if (compositeConfiguration == null) { log.info("Configuration object not present, instantiating"); compositeConfiguration = new CompositeConfiguration(); PropertiesConfiguration defaults = null; try { log .info("Attempting to create PropertiesConfiguration object for DEFAULT_CONFIG"); defaults = new PropertiesConfiguration(servletContext .getServletContext().getRealPath(DEFAULT_CONFIG)); log.info("Loading default configuration from " + servletContext.getServletContext().getRealPath( DEFAULT_CONFIG)); defaults.load(); } catch (ConfigurationException e) { log.error("Could not load default configuration from " + servletContext.getServletContext().getRealPath( DEFAULT_CONFIG)); log.error(e); } if (OVERRIDE_CONFIG != null) { PropertiesConfiguration overrides = null; try { log .info("Attempting to create PropertiesConfiguration object for OVERRIDE_CONFIG"); overrides = new PropertiesConfiguration(); log.info("Setting file for OVERRIDE_CONFIG"); overrides.setFile(new File(OVERRIDE_CONFIG)); log.info("Setting reload strategy for OVERRIDE_CONFIG"); overrides .setReloadingStrategy(new FileChangedReloadingStrategy()); log.info("Loading OVERRIDE_CONFIG"); overrides.load(); } catch (ConfigurationException e) { log.error("Could not load overrides", e); } catch (Exception ex) { log.error(ex); } if (overrides != null) { compositeConfiguration.addConfiguration(overrides); } } // Afterwards, add defaults so they're read second. compositeConfiguration.addConfiguration(defaults); } } public static void setControlThread(ControlThread ct) { controlThread = ct; } public static ControlThread getControlThread() { return controlThread; } /** * Attempt to find the current installed location of REMITT. First tries * "REMITT_HOME" environment variable, then attempts to use the * catalina.home property. * * @return */ public static String getInstallLocation() { String home = System.getenv("REMITT_HOME"); home = (home == null || home == "") ? System .getProperty("catalina.home") : home; home = (home == null || home == "") ? System.getProperty("jetty.home") : home; return home; } /** * Get an unused database connection from the <ComboPooledDataSource> pool * of db connections. * * @return */ public static Connection getConnection() { try { return comboPooledDataSource.getConnection(); } catch (SQLException e) { log.error(e); return null; } } public static ComboPooledDataSource getComboPooledDataSource() { return comboPooledDataSource; } public static void setComboPooledDataSource(ComboPooledDataSource c) { comboPooledDataSource = c; } /** * Resolve plugin name for translation plugin to be used with render and * transport plugins and their respective parameters. * * @param renderPlugin * Full class name of the render plugin being used * @param renderOption * Name of the render option being passed * @param transportPlugin * Full class name of the transport plugin being used * @param transportOption * Option name of the transport plugin being passed * @return Full class name of the appropriate translation plugin, or null if * none is available. */ public static String resolveTranslationPlugin(String renderPlugin, String renderOption, String transportPlugin, String transportOption) { log.info("resolveTranslationPlugin: " + renderPlugin + ", " + renderOption + ", " + transportPlugin + ", " + (transportOption == null ? "" : transportOption)); Connection c = Configuration.getConnection(); CallableStatement cStmt = null; String ret = null; try { cStmt = c .prepareCall("CALL p_ResolveTranslationPlugin( ?, ?, ?, ? );"); cStmt.setString(1, renderPlugin); cStmt.setString(2, renderOption); cStmt.setString(3, transportPlugin); cStmt.setString(4, transportOption); boolean hasResult = cStmt.execute(); if (hasResult) { ResultSet r = cStmt.getResultSet(); r.next(); ret = r.getString(1); log.info("Resolved to class : " + ret); DbUtil.closeSafely(r); } } catch (NullPointerException npe) { log.error("Caught NullPointerException", npe); } catch (SQLException e) { log.error("Caught SQLException", e); } finally { DbUtil.closeSafely(cStmt); DbUtil.closeSafely(c); } return ret; } /** * Get a list of plugins for a particular category. * * @param category * @return */ public static List<String> getPlugins(String category) { List<String> results = new ArrayList<String>(); if (category.equalsIgnoreCase("validation")) { category = "validation"; // validation } else if (category.equalsIgnoreCase("render")) { category = "render"; // render } else if (category.equalsIgnoreCase("translation")) { category = "translation"; // translation } else if (category.equalsIgnoreCase("transport")) { category = "transport"; // transport } else if (category.equalsIgnoreCase("eligibility")) { category = "eligibility"; // eligibility } else if (category.equalsIgnoreCase("scooper")) { category = "scooper"; // scooper } else { // No plugins for dud categories. log.error("Could not find plugins for category " + category); return results; } Connection c = getConnection(); PreparedStatement cStmt = null; try { log.debug("SELECT * FROM tPlugins " + "WHERE category = " + category); cStmt = c.prepareStatement("SELECT * FROM tPlugins " + "WHERE category = ?"); cStmt.setString(1, category); boolean hadResults = cStmt.execute(); if (hadResults) { ResultSet rs = cStmt.getResultSet(); while (rs.next()) { results.add(rs.getString("plugin")); } rs.close(); } } catch (NullPointerException npe) { log.error("Caught NullPointerException", npe); } catch (SQLException e) { log.error("Caught SQLException", e); } finally { DbUtil.closeSafely(cStmt); DbUtil.closeSafely(c); } return results; } /** * Retrieve user-set configuration option for a particular plugin namespace. * * @param plugin * Fully qualified class name with path (example: * "org.remitt.plugin.render.XsltPlugin") * @param userName * Corresponds to username field in tUser table. * @param option * Option string name. * @return */ public static String getPluginOption(Object plugin, String userName, String option) { Connection c = getConnection(); String className = plugin.getClass().getName(); if (!(plugin instanceof PluginInterface) && !(plugin instanceof EligibilityInterface)) { log.error("Could not resolve plugin"); return null; } PreparedStatement cStmt = null; String ret = null; try { cStmt = c.prepareStatement("SELECT cValue FROM tUserConfig WHERE " + " cNamespace=? AND user=? AND cOption=?;"); cStmt.setString(1, className); cStmt.setString(2, userName); cStmt.setString(3, option); boolean hasResult = cStmt.execute(); if (hasResult) { ResultSet r = cStmt.getResultSet(); r.next(); ret = r.getString(1); DbUtil.closeSafely(r); } } catch (NullPointerException npe) { log.error("Caught NullPointerException", npe); } catch (SQLException e) { log.error("Caught SQLException", e); log.info("Statement: SELECT cValue FROM tUserConfig WHERE " + "cNameSpace = '" + className + "' AND user = '" + userName + "' AND cOption = " + option); } finally { DbUtil.closeSafely(cStmt); DbUtil.closeSafely(c); } return ret; } /** * Get all configuration values for a user. * * @param username * @return */ public static ConfigurationOption[] getConfigValues(String username) { List<ConfigurationOption> results = new ArrayList<ConfigurationOption>(); Connection c = Configuration.getConnection(); PreparedStatement pStmt = null; try { pStmt = c.prepareStatement("SELECT * FROM tUserConfig " + " WHERE user = ?"); pStmt.setString(1, username); boolean hasResult = pStmt.execute(); if (hasResult) { ResultSet rs = pStmt.getResultSet(); while (!rs.isAfterLast()) { rs.next(); ConfigurationOption item = new ConfigurationOption(rs .getString("cNamespace"), rs.getString("cOption"), rs.getString("cValue")); results.add(item); } rs.close(); } } catch (NullPointerException npe) { log.error("Caught NullPointerException", npe); } catch (SQLException e) { log.error("Caught SQLException", e); } finally { DbUtil.closeSafely(pStmt); DbUtil.closeSafely(c); } return results.toArray(new ConfigurationOption[0]); } /** * Set the configuration value for an option. * * @param user * @param namespace * @param option * @param value */ public static void setConfigValue(String user, String namespace, String option, String value) { log.info("setConfigValue: " + user + ", " + namespace + ", " + option + ", " + value); Connection c = Configuration.getConnection(); CallableStatement cStmt = null; try { cStmt = c.prepareCall("{ CALL p_UserConfigUpdate( ?, ?, ?, ? ); }"); cStmt.setString(1, user); cStmt.setString(2, namespace); cStmt.setString(3, option); cStmt.setString(4, value); cStmt.execute(); cStmt.getResultSet(); } catch (NullPointerException npe) { log.error("Caught NullPointerException", npe); } catch (SQLException e) { log.error("Caught SQLException", e); } finally { DbUtil.closeSafely(cStmt); DbUtil.closeSafely(c); } } /** * Get list of previously scooped files. * * @param username * Username of user running scooper * @param pluginClass * Fully qualified scooper class name * @param host * Host name * @param path * SFTP path to files * @return */ public static List<String> getScoopedFiles(String username, String pluginClass, String host, String path) { List<String> results = new ArrayList<String>(); Connection c = Configuration.getConnection(); PreparedStatement pStmt = null; try { pStmt = c.prepareStatement("SELECT filename, stamp FROM tScooper " + " WHERE scooperClass = ? AND user = ? " + " AND host = ? AND path = ?"); pStmt.setString(1, pluginClass); pStmt.setString(2, username); pStmt.setString(3, host); pStmt.setString(4, path); boolean hasResult = pStmt.execute(); if (hasResult) { ResultSet rs = pStmt.getResultSet(); while (!rs.isAfterLast()) { rs.next(); results.add(rs.getString("filename")); } rs.close(); } } catch (NullPointerException npe) { log.error("Caught NullPointerException", npe); } catch (SQLException e) { log.error("Caught SQLException", e); } finally { DbUtil.closeSafely(pStmt); DbUtil.closeSafely(c); } return results; } /** * Record scooped file. * * @param scooperClass * @param user * @param host * @param path * @param filename * @param content * @return */ public static Integer addScoopedFile(String scooperClass, String user, String host, String path, String filename, byte[] content) { Connection c = Configuration.getConnection(); PreparedStatement cStmt = null; Integer ret = null; try { cStmt = c.prepareStatement("INSERT INTO tScooper ( " + " scooperClass, user, host, path, filename, content " + " ) VALUES ( " + "?, ?, ?, ?, ?, ? " + " );", PreparedStatement.RETURN_GENERATED_KEYS); cStmt.setString(1, scooperClass); cStmt.setString(2, user); cStmt.setString(3, host); cStmt.setString(4, path); cStmt.setString(5, filename); cStmt.setBytes(6, content); cStmt.execute(); ResultSet newKey = cStmt.getGeneratedKeys(); ret = newKey.getInt("id"); DbUtil.closeSafely(newKey); } catch (NullPointerException npe) { log.error("Caught NullPointerException", npe); } catch (SQLException e) { log.error("Caught SQLException", e); } finally { DbUtil.closeSafely(cStmt); DbUtil.closeSafely(c); } return ret; } /** * Push a remittance payload obtained from a scooper back to the * "callback url" specified for the user who owns the payload. * * @param scooperId */ public static void pushScoopedData(Integer scooperId) { Connection c = Configuration.getConnection(); String user = null; byte[] content = null; PreparedStatement cStmt = null; try { cStmt = c.prepareStatement("SELECT " + " user, host, path, filename, content" + " FROM tScooper AS a " + " WHERE id = ? " + ";"); cStmt.setInt(1, scooperId); if (cStmt.execute()) { ResultSet rs = cStmt.getResultSet(); rs.next(); user = rs.getString(1); content = rs.getBytes(5); rs.close(); } } catch (NullPointerException npe) { log.error("Caught NullPointerException", npe); } catch (Throwable e) { } finally { DbUtil.closeSafely(cStmt); DbUtil.closeSafely(c); } // Bomb the hell out if we somehow fail. if (user == null || content == null) { log.error("Unable to pull scooper content for id = " + scooperId.toString()); return; } X12Message835 parser = new X12Message835(); String parsedMessage = null; try { parser.parse(new String(content)); parsedMessage = parser.getRemittance().toString(); } catch (FormatException e) { log.error(e); return; } if (parsedMessage == null) { log.error("Null message, cannot process any further"); return; } UserDTO u = UserManagement.getUser(user); pushDataCallback(u, content); } public static void pushRemittanceData(String username, byte[] content) { X12Message835 parser = new X12Message835(); String parsedMessage = null; try { parser.parse(new String(content)); parsedMessage = parser.getRemittance().toString(); } catch (FormatException e) { log.error(e); return; } if (parsedMessage == null) { log.error("Null message, cannot process any further"); return; } UserDTO u = UserManagement.getUser(username); pushDataCallback(u, parsedMessage.getBytes()); } private static void pushDataCallback(UserDTO u, byte[] content) { RemittCallback_Service locator = new RemittCallback_ServiceLocator(); RemittCallback_PortType service = null; String serviceUrl = u.getCallbackServiceUri(); log.debug("pushDataCallback(): Using callback service URI : " + u.getCallbackServiceUri()); try { service = locator.getRemittCallbackSOAP(new URL(serviceUrl)); } catch (ServiceException e) { log.error(e); return; } catch (MalformedURLException e) { log.error(e); return; } // Override with basic authentication for callback ((Stub) service).setUsername(u.getCallbackUsername()); ((Stub) service).setPassword(u.getCallbackPassword()); ((Stub) service)._setProperty(Call.USERNAME_PROPERTY, u .getCallbackUsername()); ((Stub) service)._setProperty(Call.PASSWORD_PROPERTY, u .getCallbackPassword()); log .info("pushDataCallback(): TODO: Need to pull original reference from payloads to pass back"); int response = -1; try { response = service .sendRemittancePayload(0, "", new String(content)); } catch (RemoteException e) { log.error(e); } log.info("pushDataCallback(): received response of " + response); } /** * Get a constructed file name based on the jobId presented with a * particular file extension. * * @param jobId * @param fileExtension * @return */ public static String getFileName(Integer jobId, String fileExtension) { return jobId.toString() + "-" + System.currentTimeMillis() + "." + fileExtension; } /** * Get scheduler object. * * @return */ public static Scheduler getScheduler() { return scheduler; } /** * Store scheduler object. * * @param s */ public static void setScheduler(Scheduler s) { scheduler = s; } /** * Get <List<String>> of users who have a certain scooper enabled. * * @param scooperEnabledConfigOption * @return */ public static List<String> getUsersForScooper( String scooperEnabledConfigOption) { List<String> results = new ArrayList<String>(); Connection c = Configuration.getConnection(); PreparedStatement pStmt = null; try { pStmt = c.prepareStatement("SELECT user FROM tUserConfig " + " WHERE cNamespace = ? " + " AND cValue IN ( '1', 'yes', 'true' );"); pStmt.setString(1, scooperEnabledConfigOption); boolean hasResult = pStmt.execute(); if (hasResult) { ResultSet rs = pStmt.getResultSet(); while (rs.next()) { results.add(rs.getString(1)); } rs.close(); } } catch (NullPointerException npe) { log.error("Caught NullPointerException", npe); } catch (SQLException e) { log.error("Caught SQLException", e); } finally { DbUtil.closeSafely(pStmt); DbUtil.closeSafely(c); } return results; } }