Java tutorial
/* Copyright (c) 2001 - 2013 OpenPlans - www.openplans.org. All rights reserved. * This code is licensed under the GPL 2.0 license, available at the root * application directory. */ package org.geoserver.wfs.notification; import java.io.IOException; import java.io.StringReader; import java.util.Map; import java.util.Map.Entry; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicLong; import javax.management.JMException; import javax.management.MBeanServer; import javax.management.MBeanServerFactory; import javax.management.MalformedObjectNameException; import javax.management.ObjectName; import javax.xml.namespace.QName; import javax.xml.transform.TransformerException; import javax.xml.transform.TransformerFactory; import javax.xml.transform.TransformerFactoryConfigurationError; import javax.xml.transform.sax.SAXResult; import javax.xml.transform.sax.SAXTransformerFactory; import javax.xml.transform.sax.TransformerHandler; import javax.xml.transform.stream.StreamResult; import javax.xml.transform.stream.StreamSource; import org.apache.commons.io.output.StringBuilderWriter; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.eclipse.xsd.XSDSchema; import org.eclipse.xsd.XSDTypeDefinition; import org.geoserver.catalog.Catalog; import org.geoserver.catalog.CatalogException; import org.geoserver.catalog.event.CatalogAddEvent; import org.geoserver.catalog.event.CatalogListener; import org.geoserver.catalog.event.CatalogModifyEvent; import org.geoserver.catalog.event.CatalogPostModifyEvent; import org.geoserver.catalog.event.CatalogRemoveEvent; import org.geoserver.wfs.WFSException; import org.geotools.gml3.GML; import org.geotools.math.Statistics; import org.geotools.xml.Configuration; import org.geotools.xml.Encoder; import org.opengis.feature.Feature; import org.opengis.feature.type.Name; import org.opengis.filter.identity.Identifier; import org.xml.sax.helpers.DefaultHandler; import org.xml.sax.helpers.NamespaceSupport; public class GMLNotificationSerializer implements PublishCallbackMBean, CatalogListener, NotificationSerializer { private static final Log LOG = LogFactory.getLog(GMLNotificationSerializer.class); private static final SAXTransformerFactory STF = (SAXTransformerFactory) TransformerFactory.newInstance(); private static final boolean isDebug = Boolean .parseBoolean(System.getProperty("com.fsi.c2rpc.geoserver.wsn.PublishCallback.debug")); private final Catalog catalog; private final Configuration xmlConfig; private AtomicLong catalogModCount = new AtomicLong(0); private final ThreadLocal<Long> catalogModCountLocal = new ThreadLocal<Long>() { @Override protected Long initialValue() { return 0L; } }; private final ThreadLocal<Encoder> encoder = new ThreadLocal<Encoder>() { @Override protected Encoder initialValue() { try { // TODO: Get this patch into GeoTools? Encoder enc = new Encoder(xmlConfig, xmlConfig.getXSD().getSchema()) /* { { // setResuseIndex(true); } protected void resetContext(MutablePicoContainer context) { context.unregisterComponent(XSDIdRegistry.class); context.registerComponentInstance(new XSDIdRegistry()); } protected void clearContext(MutablePicoContainer context) { }; }; enc.setResuseIndex(true) */; return enc; } catch (IOException e) { throw new RuntimeException(e); } } }; // Reporting stuff private ConcurrentHashMap<Name, Statistics> serializationMillisByType; private Statistics serializationMillis; private Statistics serializationChars; private AtomicLong failures; private AtomicLong updates; private AtomicLong deletes; private Throwable lastFailure; private ObjectName name; public GMLNotificationSerializer(Catalog catalog, Configuration xmlConfig) { this.catalog = catalog; this.xmlConfig = xmlConfig; resetStats(); } public void setObjectName(String name) { try { this.name = ObjectName.getInstance(name); } catch (MalformedObjectNameException e) { LOG.warn("Invalid ObjectName:", e); } } public void init() { registerMBean(); } public void destroy() { unregisterMBean(); } private final void registerMBean() { if (name == null) { return; } for (MBeanServer server : MBeanServerFactory.findMBeanServer(null)) { try { server.registerMBean(this, name); } catch (JMException e) { LOG.warn("Unable to register in JMX:", e); } } } private final void unregisterMBean() { if (name == null) return; for (MBeanServer server : MBeanServerFactory.findMBeanServer(null)) { try { server.unregisterMBean(name); } catch (JMException e) { LOG.warn("Unable to unregister from JMX:", e); } } } public String getDeleteRawMessage(QName typeName, Identifier id) { StringBuilder builder = new StringBuilder(1024); builder.append("<wfs:Delete xmlns:wfs=\"http://www.opengis.net/wfs\" xmlns:").append(typeName.getPrefix()) .append("=\"").append(typeName.getNamespaceURI()).append("\" typeName=\"") .append(typeName.getPrefix()).append(":").append(typeName.getLocalPart()).append("\">") .append(id.getID()).append("</wfs:Delete>"); return builder.toString(); } /* * Just borrowing a couple of methods from GeoServer... */ private static void loadNamespaceBindings(NamespaceSupport nss, Feature f, XSDSchema exclude) { XSDTypeDefinition type = (XSDTypeDefinition) f.getType().getUserData().get(XSDTypeDefinition.class); if (type == null) return; loadNamespaceBindings(nss, type.getSchema(), exclude); } private static void loadNamespaceBindings(NamespaceSupport nss, XSDSchema schema, XSDSchema exclude) { Map excludePrefixes = exclude.getQNamePrefixToNamespaceMap(); for (Map.Entry<String, String> e : ((Map<String, String>) schema.getQNamePrefixToNamespaceMap()) .entrySet()) { if (excludePrefixes.containsKey(e.getKey()) || excludePrefixes.containsValue(e.getValue())) { continue; } String pref = e.getKey(); nss.declarePrefix(pref == null ? "" : pref, e.getValue()); } } String getInsertUpdateRawMessage(Feature feature) { final StringBuilderWriter sw = new StringBuilderWriter(); long start = System.nanoTime(); boolean success = true; try { long modCount = catalogModCount.get(); if (modCount != catalogModCountLocal.get().longValue()) { catalogModCountLocal.set(modCount); encoder.remove(); } final Encoder encoder = this.encoder.get(); TransformerHandler handler = STF.newTransformerHandler(); handler.setResult(new StreamResult(sw)); encoder.setInline(true); loadNamespaceBindings(encoder.getNamespaces(), feature, xmlConfig.getXSD().getSchema()); encoder.encode(feature, GML._Feature, handler); return sw.toString(); } catch (Exception e) { success = false; throw new WFSException(e); } finally { if (success) { double millis = ((double) System.nanoTime() - start) / 1000000; Name name = WFSNotify.getTypeName(feature); serializationMillis.add(millis); serializationChars.add(sw.getBuilder().length()); Statistics statsByType = serializationMillisByType.get(name); if (statsByType == null) { Statistics newStats = new Statistics(); statsByType = serializationMillisByType.put(name, newStats); if (statsByType == null) { statsByType = newStats; } } statsByType.add(millis); } } } /* (non-Javadoc) * @see com.fsi.geoserver.wfs.NotificationSerializer#serializeInsertOrUpdate(org.opengis.feature.Feature) */ public String serializeInsertOrUpdate(Feature f) { try { String rawMessage; rawMessage = getInsertUpdateRawMessage(f); debugMessage(rawMessage); return rawMessage; } catch (Throwable t) { onFailure(t); } finally { updates.incrementAndGet(); } return null; } public void debugMessage(String rawMessage) throws TransformerFactoryConfigurationError { if (isDebug) { LOG.debug(rawMessage); try { TransformerFactory.newInstance().newTransformer().transform( new StreamSource(new StringReader(rawMessage)), new SAXResult(new DefaultHandler())); } catch (TransformerException e) { LOG.error("Error parsing result", e); } } } /* (non-Javadoc) * @see com.fsi.geoserver.wfs.NotificationSerializer#serializeDelete(org.opengis.feature.type.Name, org.opengis.filter.identity.Identifier) */ public String serializeDelete(Name typeName, Identifier id) { try { QName qname = nameToQName(typeName); String rawMessage = getDeleteRawMessage(qname, id); debugMessage(rawMessage); return rawMessage; } catch (Throwable t) { onFailure(t); } finally { deletes.incrementAndGet(); } return null; } protected void onFailure(Throwable t) { if (LOG.isWarnEnabled()) { LOG.warn("Unexpected exception while notifying.", t); } failures.incrementAndGet(); lastFailure = t; } public QName nameToQName(Name typeName) { QName qname = new QName(typeName.getNamespaceURI(), typeName.getLocalPart(), catalog.getNamespaceByURI(typeName.getNamespaceURI()).getPrefix()); return qname; } @Override public void handleAddEvent(CatalogAddEvent event) throws CatalogException { } @Override public void handleModifyEvent(CatalogModifyEvent event) throws CatalogException { } @Override public void handlePostModifyEvent(CatalogPostModifyEvent event) throws CatalogException { catalogModCount.incrementAndGet(); } @Override public void handleRemoveEvent(CatalogRemoveEvent event) throws CatalogException { catalogModCount.incrementAndGet(); } @Override public void reloaded() { catalogModCount.incrementAndGet(); } @Override public void resetStats() { serializationMillisByType = new ConcurrentHashMap<Name, Statistics>(); serializationMillis = new Statistics(); serializationChars = new Statistics(); failures = new AtomicLong(); updates = new AtomicLong(); deletes = new AtomicLong(); lastFailure = null; } @Override public long getUpdates() { return updates.get(); } @Override public long getDeletes() { return deletes.get(); } @Override public double getAverageSerializationTime() { return serializationMillis.mean(); } @Override public double getMinimumSerializationTime() { return serializationMillis.minimum(); } @Override public double getMaximumSerializationTime() { return serializationMillis.maximum(); } @Override public double getTotalSerializationTime() { return serializationMillis.mean() * serializationMillis.count(); } @Override public long getAverageMessageSize() { return (long) serializationChars.mean(); } @Override public long getMinimumMessageSize() { return (long) serializationChars.minimum(); } @Override public long getMaximumMessageSize() { return (long) serializationChars.maximum(); } @Override public long getTotalMessageSize() { return (long) (serializationChars.mean() * serializationChars.count()); } @Override public String createSerializationTimeHistogram() { StringBuilder sb = new StringBuilder(); sb.append( "<table><thead><tr><td>Type name</td><td>Count</td><td>Min</td><td>Max</td><td>Avg</td><td>Total</td></tr></thead>"); for (Entry<Name, Statistics> e : serializationMillisByType.entrySet()) { Statistics st = e.getValue(); sb.append("<tr><td>").append(e.getKey()).append("</td><td>").append(st.count()).append("</td><td>") .append(st.minimum()).append("</td><td>").append(st.maximum()).append("</td><td>") .append(st.mean()).append("</td><td>").append(st.mean() * st.count()).append("</td></tr>"); } sb.append("</table>"); return sb.toString(); } @Override public long getFailures() { return failures.get(); } @Override public Throwable getLastFailure() { return lastFailure; } }