/** * Helios, OpenSource Monitoring * Brought to you by the Helios Development Group * * Copyright 2007, Helios Development Group and individual contributors * as indicated by the @author tags. See the copyright.txt file in the * distribution for a full listing of individual contributors. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. * */ package org.helios.collector.jdbc; import org.apache.log4j.Level; import org.apache.log4j.Logger; import org.helios.apmrouter.jmx.XMLHelper; import org.helios.apmrouter.trace.ITracer; import org.helios.collector.jdbc.binding.provider.BindVariableProviderFactory; import org.helios.collector.jdbc.binding.provider.IBindVariableProvider; import org.helios.collector.jdbc.binding.provider.ProviderNotFoundException; import org.helios.collector.jdbc.binding.provider.ProviderToken; import org.helios.collector.jdbc.extract.ProcessedResultSet; import org.helios.collector.jdbc.mapping.InvalidMetricMappingException; import org.helios.collector.jdbc.mapping.MetricMap; import org.springframework.beans.factory.BeanNameAware; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.jmx.export.annotation.ManagedResource; import org.w3c.dom.Node; import javax.management.MBeanServer; import java.sql.*; import java.util.*; import java.util.concurrent.atomic.AtomicReference; import java.util.regex.Matcher; /** * <p>Title: SQLMapping</p> * <p>Description: A collection mapping that associates a SQL query to a set of collected metrics.</p> * <p>Company: Helios Development Group</p> * @author Whitehead (whitehead.nicholas@gmail.com) * @version $LastChangedRevision$ * $HeadURL$ * $Id$ */ @ManagedResource public class SQLMapping implements ApplicationContextAware, BeanNameAware { /** The collecting query text */ protected AtomicReference<String> sql = new AtomicReference<String>(null); /** The allowed elapsed time for a SQLMap operation */ protected long operationTimeOut = 3000; /** Indicates if bind variables are supported */ protected boolean bindVarsSupported = true; /** SQLMapping Name */ protected String mappingName = null; // /** A reference to the collector's collector cache */ // protected Ehcache collectorCache = null; /** A reference to the MBeanServer where the collector is registered */ protected MBeanServer server = null; /** A handle to the application context so bind providers can be located */ protected ApplicationContext appContext = null; /** A bind sequence sorted map of bind variables */ protected SortedMap<Integer, IBindVariableProvider> binds = new TreeMap<Integer, IBindVariableProvider>(); /** A map of bind variables keyed by token */ protected Map<String, IBindVariableProvider> tbinds = new HashMap<String, IBindVariableProvider>(); /** The connection meta-data */ protected Map<String, Object> connMetaData = null; /** The metric mapping prefix */ protected String[] prefix = null; /** The tracer */ protected ITracer tracer = null; /** the set of metric maps */ protected Set<MetricMap> metricMaps = new HashSet<MetricMap>(); /** Flag indicating this mapping is a preQuery */ protected boolean pre = false; /** An array of sqlmap names of sqlmaps that are pres for this map */ protected String[] pres = null; /** Class logger */ protected static Logger LOG = Logger.getLogger(SQLMapping.class); /** * Called on collector init. * @param prefix The collector provided metric name prefix. * @param tracer The collector provided HOT tracer * @param server The collector provided MBeanServer. * @throws ProviderNotFoundException */ public void init(String[] prefix, ITracer tracer, /*Ehcache collectorCache,*/ MBeanServer server) throws ProviderNotFoundException { this.prefix = prefix; //this.collectorCache = collectorCache; this.tracer = tracer; this.server = server; if(sql.get() != null) { sql.set(preBind(sql.get())); } for(Iterator<MetricMap> mm = metricMaps.iterator(); mm.hasNext(); ) { MetricMap metricMap = null; try { metricMap = mm.next(); metricMap.init(prefix, tracer, /*collectorCache,*/ server); } catch (InvalidMetricMappingException e) { LOG.error("Failed to initialize MetricMap", e); mm.remove(); } } } /** * @param conn * @throws SQLException */ public void execute(Connection conn) throws SQLException { PreparedStatement ps = null; Statement st = null; ResultSet rset = null; long start = System.currentTimeMillis(), elapsed = 0; try { if(bindVarsSupported) { if(LOG.isDebugEnabled()) LOG.debug("Compiling PreparedQuery SQL"); String rSql = sql.get(); for(Map.Entry<String, IBindVariableProvider> bind: tbinds.entrySet()) { rSql = bind.getValue().bind(rSql, bind.getKey()).toString(); } if(LOG.isDebugEnabled()) LOG.debug("Prepared SQL:[" + rSql + "]"); ps = conn.prepareStatement(rSql); for(Map.Entry<Integer, IBindVariableProvider> bind: binds.entrySet()) { bind.getValue().bind(ps, bind.getKey()); } if(LOG.isDebugEnabled()) LOG.debug("Executing PreparedQuery"); rset = ps.executeQuery(); } else { CharSequence boundSql = sql.get(); for(Map.Entry<String, IBindVariableProvider> bind: tbinds.entrySet()) { boundSql = bind.getValue().bind(boundSql, bind.getKey()); } if(LOG.isDebugEnabled()) LOG.debug("Prepared SQL:[" + boundSql + "]"); st = conn.createStatement(); if(LOG.isDebugEnabled()) LOG.debug("Executing Statement"); rset = st.executeQuery(boundSql.toString()); } ProcessedResultSet prs = new ProcessedResultSet(rset); prs.setQueryName(mappingName); //if(LOG.isDebugEnabled()) LOG.debug("Retrieved [" + prs.getRowCount() + "] rows."); try { rset.close(); } catch (Exception e) {} elapsed = System.currentTimeMillis()-start; //LOG.info("Elapsed Time to PRS:" + elapsed + " ms."); if(!pre) { if(LOG.isDebugEnabled()) LOG.debug("Resetting Scope on Metric Maps"); for(MetricMap mm: metricMaps) { mm.resetScope(); } if(LOG.isDebugEnabled()) LOG.debug("Reset Scope on Metric Maps"); if(LOG.isDebugEnabled()) LOG.debug("Firing Trace on Metric Maps"); while(prs.next()) { for(MetricMap mm: metricMaps) { mm.traceMetrics(prs, connMetaData); mm.executeBinds(prs, connMetaData); } } if(LOG.isDebugEnabled()) LOG.debug("Fired Trace on Metric Maps"); if(LOG.isDebugEnabled()) LOG.debug("Firing Scope Failures on Metric Maps"); for(MetricMap mm: metricMaps) { mm.traceScopeFailures(); } if(LOG.isDebugEnabled()) LOG.debug("Fired Scope Failures on Metric Maps"); } else { if(LOG.isDebugEnabled()) LOG.debug("Firing Binds on Pre Metric Maps"); for(MetricMap mm: metricMaps) { while(prs.next()) { mm.executeBinds(prs, connMetaData); } } if(LOG.isDebugEnabled()) LOG.debug("Fired Binds on Pre Metric Maps"); } elapsed = System.currentTimeMillis()-start; //- tracer.traceSticky(elapsed, "Collection Time (ms)", "Helios", "Collectors", "Database", mappingName); tracer.traceGauge(elapsed, "ElapsedTime", "Collectors", getClass().getSimpleName(),mappingName); } catch (Exception e) { if(LOG.isEnabledFor(Level.ERROR)) LOG.error("SQLMap Execution Error:\n\tSQL:" + sql, e); } finally { try { if(rset!=null) rset.close(); } catch (Exception e) {} try { if(st!=null) st.close(); } catch (Exception e) {} try { if(ps!=null) ps.close(); } catch (Exception e) {} } } /** * Processes tokens in the configured sql statement. * For prepared statements, the tokens are replaced with <code>?</code> bind targets and the providers are stored keyed by bind sequence. * For statements, the sql is not modified and the providers are stored keyed by token. * @param sql The configured sql * @return The processed sql, modified if using a prepared statement, unmodified if a regular statement. * @throws ProviderNotFoundException */ protected String preBind(String sql) throws ProviderNotFoundException { Matcher m =ProviderToken.BIND_VAR_PATTERN.matcher(sql); String processedSql = sql; int bindSeq = 1; while(m.find()) { String token = m.group(); IBindVariableProvider provider = BindVariableProviderFactory.getInstance().getProvider(token); if(bindVarsSupported && !provider.isForceNoBind()) { processedSql = processedSql.replace(token, "?"); binds.put(bindSeq, provider); bindSeq++; } else { tbinds.put(token, provider); } } LOG.info("[" + mappingName + "]Processed SQL:" + processedSql); return processedSql; } // /** // * @param cache the cache to set // */ // public void setCache(Ehcache cache) { // this.collectorCache = cache; // } /** * @param appContext the appContext to set */ public void setApplicationContext(ApplicationContext appContext) { this.appContext = appContext; } /** * @return the sql */ public String getSql() { return sql.get(); } /** * @param sql the sql to set */ public void setSql(String sql) { this.sql.set(sql); } /** * @return the operationTimeOut */ public long getOperationTimeOut() { return operationTimeOut; } /** * @param operationTimeOut the operationTimeOut to set */ public void setOperationTimeOut(long operationTimeOut) { this.operationTimeOut = operationTimeOut; } /** * @return the bindVarsSupported */ public boolean isBindVarsSupported() { return bindVarsSupported; } /** * @param bindVarsSupported the bindVarsSupported to set */ public void setBindVarsSupported(boolean bindVarsSupported) { this.bindVarsSupported = bindVarsSupported; } /** * @return * @see java.lang.Object#hashCode() */ @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((sql == null) ? 0 : sql.hashCode()); return result; } /** * @param obj * @return * @see java.lang.Object#equals(java.lang.Object) */ @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; SQLMapping other = (SQLMapping) obj; if (sql == null) { if (other.sql != null) return false; } else if (!sql.equals(other.sql)) return false; return true; } /** * Sets the bean name for this SQLMapping. * @param beanName the bean name. * @see org.springframework.beans.factory.BeanNameAware#setBeanName(java.lang.String) */ public void setBeanName(String beanName) { mappingName = beanName; } /** * @param connMetaData the connMetaData to set */ public void setConnMetaData(Map<String, Object> connMetaData) { this.connMetaData = connMetaData; } /** * @param prefix the prefix to set */ public void setPrefix(String[] prefix) { this.prefix = prefix; } /** * @param tracer the tracer to set */ public void setTracer(ITracer tracer) { this.tracer = tracer; } /** * @return the mappingName */ public String getName() { return mappingName; } /** * @param mappingName the mappingName to set */ public void setName(String mappingName) { this.mappingName = mappingName; } /** * Adds a set of metric maps to the SQLMapping * @param metricMaps the metricMaps to set */ public void setMetricMaps(Set<MetricMap> metricMaps) { this.metricMaps.addAll(metricMaps); } /** * Adds a node of <code><MetricMaps></code> to the SQLMapping. * @param configNode An XML node containing XML defined <code>MetricMap</code>s. */ public void setMetricMapsNode(Node configNode) { Node metricMapNode = XMLHelper.getChildNodeByName(configNode, "MetricMaps", false); for(Node node: XMLHelper.getChildNodesByName(metricMapNode, "MetricMap", false)) { try { MetricMap mm = new MetricMap(node); metricMaps.add(mm); if(LOG.isDebugEnabled()) { LOG.debug("Added MetricMap to SQLMap:" + mm); } } catch (InvalidMetricMappingException e) { LOG.error("Failed to create metric map", e); } } } /** * @return the pre */ public boolean isPre() { return pre; } /** * @param pre the pre to set */ public void setPre(boolean pre) { this.pre = pre; } /** * @return the pres */ public String[] getPres() { return pres; } /** * @param pres the pres to set */ public void setPres(String[] pres) { this.pres = pres; } }