/** * Copyright (C) 2008 - 2014 52°North Initiative for Geospatial Open Source * Software GmbH * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 as published * by the Free Software Foundation. * * If the program is linked with libraries which are licensed under one of * the following licenses, the combination of the program with the linked * library is not considered a "derivative work" of the program: * * - Apache License, version 2.0 * - Apache Software License, version 1.0 * - GNU Lesser General Public License, version 3 * - Mozilla Public License, versions 1.0, 1.1 and 2.0 * - Common Development and Distribution License (CDDL), version 1.0 * * Therefore the distribution of the program linked with libraries licensed * under the aforementioned licenses, is permitted by the copyright holders * if the distribution is compliant with both the GNU General Public * icense version 2 and the aforementioned licenses. * * 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. */ package org.n52.ses.wsn; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.net.URI; import java.net.URISyntaxException; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.UUID; import javax.net.ssl.HostnameVerifier; import javax.net.ssl.HttpsURLConnection; import javax.net.ssl.SSLSession; import javax.xml.namespace.QName; import org.apache.muse.core.ResourceManager; import org.apache.muse.core.SimpleResourceManager; import org.apache.muse.core.descriptor.ResourceDefinition; import org.apache.muse.core.routing.ResourceIdFactory; import org.apache.muse.util.xml.XmlUtils; import org.apache.muse.ws.addressing.EndpointReference; import org.apache.muse.ws.addressing.WsaConstants; import org.apache.muse.ws.addressing.soap.SimpleSoapClient; import org.apache.muse.ws.addressing.soap.SoapClient; import org.apache.muse.ws.addressing.soap.SoapFault; import org.apache.muse.ws.notification.Filter; import org.apache.muse.ws.notification.NotificationMessage; import org.apache.muse.ws.notification.Policy; import org.apache.muse.ws.notification.WsnConstants; import org.apache.muse.ws.notification.impl.FilterCollection; import org.apache.muse.ws.notification.impl.FilterFactory; import org.apache.muse.ws.notification.impl.MessagePatternFilterHandler; import org.apache.muse.ws.notification.impl.SimpleSubscriptionManager; import org.apache.muse.ws.resource.lifetime.ScheduledTermination; import org.apache.muse.ws.resource.lifetime.WsrlConstants; import org.apache.xmlbeans.XmlObject; import org.joda.time.DateTime; import org.n52.oxf.xmlbeans.parser.XMLHandlingException; import org.n52.oxf.xmlbeans.tools.XmlUtil; import org.n52.ses.api.IClassProvider; import org.n52.ses.api.IFilterEngine; import org.n52.ses.api.ISESFilePersistence; import org.n52.ses.api.common.GlobalConstants; import org.n52.ses.api.event.MapEvent; import org.n52.ses.api.event.PersistedEvent; import org.n52.ses.api.ws.INotificationMessage; import org.n52.ses.api.ws.ISubscriptionManager; import org.n52.ses.api.ws.SESFilterCollection; import org.n52.ses.common.SESResourceIdFactory; import org.n52.ses.common.environment.SESSoapClient; import org.n52.ses.common.https.AcceptAllSocketFactory; import org.n52.ses.common.https.HTTPSConnectionHandler; import org.n52.ses.filter.SESConstraintFilterHandler; import org.n52.ses.filter.dialects.SelectiveMetadataFilter; import org.n52.ses.filter.epl.EPLFilterHandler; import org.n52.ses.persistency.SESFilePersistence; import org.n52.ses.persistency.streams.AbstractStreamPersistence; import org.n52.ses.storedfilters.StoredFilterHandler; import org.n52.ses.util.common.ConfigurationRegistry; import org.n52.ses.util.unitconversion.SESUnitConverter; import org.n52.ses.util.xml.SESEventGenerator; import org.n52.ses.util.xml.XMLHelper; import org.n52.ses.wsn.contentfilter.MessageContentFiler; import org.n52.ses.wsn.contentfilter.PropertyExclusionContentFilter; import org.n52.ses.wsn.dissemination.AbstractDisseminationMethod; import org.n52.ses.wsn.dissemination.DefaultDisseminationMethod; import org.n52.ses.wsn.dissemination.DisseminationMethodFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.xml.sax.SAXException; /** * * @author Matthes Rieke <m.rieke@uni-muenster.de> * */ public class SESSubscriptionManager extends SimpleSubscriptionManager implements ISubscriptionManager { private static final Logger logger = LoggerFactory.getLogger(SESSubscriptionManager.class); /** * constant needed by the PublisherEndpoint to retrieve the instance */ public static final String CONTEXT_PATH = GlobalConstants.SUBSCRIPTION_MANAGER_CONTEXT_PATH; private static boolean FIRST_RUN = true; private static Object FIRST_RUN_MUTEX = new Object(); /** * Flag is true if the SESSubscriptionManager has a * SESConstraintFilter. */ private boolean hasConstraintFilter = false; private AbstractDisseminationMethod disseminationMethod; private Policy policy; private List<MessageContentFiler> messageContentFilters = new ArrayList<MessageContentFiler>(); private AbstractStreamPersistence streamPersistence; /** * Flag is true if the SESSubscriptionManager has a * SESConstraintFilter. * @return <code>true</code> if there is a constraint filter available */ public boolean isHasConstraintFilter() { return this.hasConstraintFilter; } @Override public void initialize() throws SoapFault { if (logger.isInfoEnabled()) logger.info("initialising SESSubscriptionManager.."); synchronized (FIRST_RUN_MUTEX) { if (FIRST_RUN) { initializeSESResources(); FIRST_RUN = false; } } if (getSubscriptionPolicy() != null) { this.disseminationMethod = DisseminationMethodFactory.createDisseminationMethodFromPolicy(getSubscriptionPolicy(), getConsumerClient(), getWsResource().getEndpointReference()); } if (this.disseminationMethod == null) { this.disseminationMethod = new DefaultDisseminationMethod(); } this.disseminationMethod.setNumberOfTries(getNumberOfTries()); /* * increment the id counter. needed if some resources * are deleted manually or by unsubscribing. */ String ids = ""; Element child = getResource().getEndpointReference().getParameter( WsaConstants.DEFAULT_RESOURCE_ID_QNAME); if (child != null && child.getFirstChild() != null) { ids = XmlUtils.toString(child.getFirstChild()); ResourceManager manager = this.getResource().getResourceManager(); if (manager instanceof SimpleResourceManager) { ResourceDefinition def = ((SimpleResourceManager) manager).getResourceDefinition( CONTEXT_PATH); ResourceIdFactory idFactory = def.getResourceIdFactory(); if (idFactory instanceof SESResourceIdFactory) { SESResourceIdFactory sesFac = (SESResourceIdFactory) idFactory; sesFac.setIdentifierCount( Integer.parseInt(ids.substring(ids.indexOf(sesFac.getPrefix()) + sesFac.getPrefix().length(), ids.length())) + 1); } } } /* * resubscribe */ if (ConfigurationRegistry.getInstance().persistencyEnabled()) { initializeResubscription(ids); } super.initialize(); } /** * Method used * * @param ids the id of the resource * @throws SoapFault */ private void initializeResubscription(String ids) throws SoapFault { Element persSub = getResource().getEndpointReference().getParameter( SESFilePersistence.SES_SUBSCRIBE_PERS_NAME); /* if no PersistentSubscribe than its an actual new subscribe */ if (persSub == null) return; if (logger.isDebugEnabled()) logger.debug("resubscribing from router-entries: " +ids); /* first get filters */ QName filQN = WsnConstants.FILTER_QNAME; NodeList filter = persSub.getElementsByTagNameNS(filQN.getNamespaceURI(), filQN.getLocalPart()); if (filter.getLength() == 1) { Filter fil = FilterFactory.getInstance().newInstance((Element) filter.item(0)); fil = SESNotificationProducer.getSESFilterCollectionFromCollection(fil); if (fil != null) { this.setFilter(fil); } } /* set Consumer */ QName consQN = WsnConstants.CONSUMER_QNAME; NodeList consumer = persSub.getElementsByTagNameNS(consQN.getNamespaceURI(), consQN.getLocalPart()); if (consumer.getLength() == 1) { this.setConsumerReference(new EndpointReference((Element) consumer.item(0))); } /* set producer */ QName prodQN = WsnConstants.PRODUCER_QNAME; NodeList producer = persSub.getElementsByTagNameNS(prodQN.getNamespaceURI(), prodQN.getLocalPart()); if (producer.getLength() == 1) { this.setProducerReference(new EndpointReference((Element) producer.item(0))); } /* subscription policy */ QName polQN = WsnConstants.POLICY_QNAME; NodeList policies = persSub.getElementsByTagNameNS(polQN.getNamespaceURI(), polQN.getLocalPart()); if (policies.getLength() == 1) { //TODO this.setSubscriptionPolicy(policy); } /* termination time */ QName timeQN = WsnConstants.INIT_TERMINATION_TIME_QNAME; NodeList time = persSub.getElementsByTagNameNS(timeQN.getNamespaceURI(), timeQN.getLocalPart()); if (time.getLength() == 1) { try { if (!time.item(0).getTextContent().equals("")) { this.setInitialTerminationTime(new DateTime(time.item(0).getTextContent()).toDate()); } } catch (Exception e) { logger.warn(e.getMessage(), e); return; } } if (this.getResource().hasCapability(WsrlConstants.SCHEDULED_TERMINATION_URI)) { ScheduledTermination wsrl = (ScheduledTermination)this.getResource() .getCapability(WsrlConstants.SCHEDULED_TERMINATION_URI); wsrl.setTerminationTime(this.getInitialTerminationTime()); } /* finally register at registry * initialization cannot be perfmored now, because esper statements need to registered * as the final step, due to availability of metadata from possible * Publishers. */ ConfigurationRegistry reg = ConfigurationRegistry.getInstance(); reg.addReregisteredSubMgr(this); } private void initializeSESResources() throws SoapFault { //add the SESConstraintFilter and EPLFilter if (logger.isInfoEnabled()) logger.info("initializing SES Resources..."); FilterFactory.getInstance().addHandler(new SESConstraintFilterHandler()); FilterFactory.getInstance().addHandler(new EPLFilterHandler()); FilterFactory.getInstance().addHandler(new StoredFilterHandler()); FilterFactory.getInstance().addHandler(new MessagePatternFilterHandler()); if (logger.isInfoEnabled()) logger.info("initializing config from file {}...", getResource().getEnvironment().getDataResource(ConfigurationRegistry.CONFIG_FILE)); InputStream config = getResource().getEnvironment().getDataResourceStream(ConfigurationRegistry.CONFIG_FILE); SESUnitConverter unitConverter = new SESUnitConverter(); /* * * init the registry * */ ConfigurationRegistry.init(config, getEnvironment(), unitConverter); // ConfigurationRegistry.getInstance().setFilePersistence(new SESFilePersistence()); SoapClient soapClient = getEnvironment().getSoapClient(); if (soapClient instanceof SESSoapClient) { ((SESSoapClient) soapClient).initialize(); } ConfigurationRegistry confReg = ConfigurationRegistry.getInstance(); // get the filter engine with reflections String feString = confReg.getPropertyForKey(ConfigurationRegistry.USED_FILTER_ENGINE); if (logger.isInfoEnabled()) logger.info("Init Filter Engine: {}", feString); IFilterEngine filterEngine = null; if (feString != null) { Class<?> c; IClassProvider prov = null; try { c = Class.forName(feString); prov = (IClassProvider) c.newInstance(); } catch (ClassNotFoundException e) { throw new SoapFault(e); } catch (InstantiationException e) { throw new SoapFault(e); } catch (IllegalAccessException e) { throw new SoapFault(e); } filterEngine = prov.getFilterEngine(unitConverter); } if (filterEngine == null) { throw new SoapFault("could not initialize FilterEngine (used IClassProvider: " + feString); } confReg.setFilterEngine(filterEngine); /* let the filter listen for new sensor registrations */ //TODO: check if needed for resource management! // getResource().getResourceManager().addListener( // new FilterListener<IPublisherEndpoint>(IPublisherEndpoint.class, filterEngine)); /* * add a HTTPSConnectionHandler */ SoapClient client = getResource().getEnvironment().getSoapClient(); if (client instanceof SimpleSoapClient) { ((SimpleSoapClient) client).setConnectionHandler( new HTTPSConnectionHandler()); } try { HostnameVerifier hv = new HostnameVerifier() { @Override public boolean verify(String urlHostName, SSLSession session) { return true; } }; HttpsURLConnection.setDefaultHostnameVerifier(hv); HttpsURLConnection.setDefaultSSLSocketFactory(AcceptAllSocketFactory.getSocketFactory()); } catch (Exception e) { logger.warn(e.getMessage(), e); } } @Override public EndpointReference getConsumerReference() { if (super.getConsumerReference() == null) { try { return new EndpointReference(new URI("http://52north.org")); } catch (URISyntaxException e) { logger.warn(e.getMessage(), e); } return null; } return super.getConsumerReference(); } @Override public void setFilter(Filter filter) { if (filter instanceof SESFilterCollection) { SESFilterCollection filterColl = (SESFilterCollection) filter; if (filterColl.getConstraintFilter() != null || filterColl.hasEPLFilter()) { this.hasConstraintFilter = true; } } lookupMessageContentFilters(filter); super.setFilter(filter); } @SuppressWarnings("unchecked") private void lookupMessageContentFilters(Filter filter) { Collection<Filter> collection; if (filter instanceof FilterCollection) { collection = ((FilterCollection) filter).getFilters(); } else if (filter instanceof SESFilterCollection) { collection = ((SESFilterCollection) filter).getFilters(); } else { return; } Iterator<Filter> it = collection.iterator(); Filter next; while (it.hasNext()) { next = it.next(); if (next instanceof SelectiveMetadataFilter) { //TODO hacked. fix it this.messageContentFilters.add(new PropertyExclusionContentFilter(((SelectiveMetadataFilter) next).getExcludedQNames())); } } } /** * Implementation of the Unsubscribe method of the SubscriptionManager. * * @throws SoapFault if an error occurred on unsubscribing */ public void unsubscribe() throws SoapFault { try { //get the ID for logging Document doc = XmlUtils.createDocument(this.getWsResource().toString()); NodeList id = doc.getDocumentElement().getElementsByTagNameNS( WsaConstants.DEFAULT_RESOURCE_ID_QNAME.getNamespaceURI(), WsaConstants.DEFAULT_RESOURCE_ID_QNAME.getLocalPart()); if (id != null && id.item(0) != null) { if (logger.isDebugEnabled()) { logger.debug("...unsubscribing... "+ id.item(0).getTextContent()); } } else { throw new RuntimeException("Detected an attemp to remove the base Endpoint resource! Rejected."); } } catch (IOException e) { throw new SoapFault(e); } catch (SAXException e) { throw new SoapFault(e); } //stop the resource this.getWsResource().shutdown(); } /** * Sends the given message to the subscription's consumer resource. * This method is not called the normal way, if this SubscriptionManager has * a constraintFilter (Level 2 or Level 3). It is only called after the Level 2/3 * filter applied, if not it is not called. * * @param message the message * */ @Override public void publish(NotificationMessage message) { //just check if paused if (isPaused()) { logger.debug("Message not sent - SubscriptionManager is paused."); return; } //check the filters except SESConstraintFilter if (!getFilter().accepts(message)) return; applyMessageContentFilters(message); if (!this.disseminationMethod.newMessage(message, getConsumerClient(), getResource().getEndpointReference(), getProducerReference(), getConsumerReference()) && isDestroyedOnFailure()) { try { getResource().shutdown(); } catch (SoapFault error) { logger.warn(error.getMessage(), error); } } } private void applyMessageContentFilters(NotificationMessage message) { for (MessageContentFiler filter : this.messageContentFilters ) { filter.filterMessage(message); } } @Override public void reRegister() { } @Override public Policy getSubscriptionPolicy() { return this.policy; } @Override public void setSubscriptionPolicy(Policy policy) { this.policy = policy; } @Override public void prepareShutdown() throws SoapFault { if (logger.isDebugEnabled()) logger.debug("shutting down. unregister statements and delete from DB."); /* * try to unregister at filter engine */ try { ConfigurationRegistry.getInstance().getFilterEngine().unregisterFilter(this); } catch (Exception e) { throw new SoapFault(e); } this.disseminationMethod.shutdown(); if (this.streamPersistence != null) { try { this.streamPersistence.destroy(); } catch (Exception e) { throw new SoapFault(e); } } super.prepareShutdown(); } @Override public void publish(INotificationMessage origMessage) { if (origMessage.getNotificationMessage() != null) { publish((NotificationMessage) origMessage.getNotificationMessage()); } } @Override public XmlObject generateSESEvent(MapEvent resultEvent) { SESEventGenerator gen = new SESEventGenerator(resultEvent); return gen.generateEventDocument(); } @Override public boolean sendSESNotificationMessge(XmlObject eventDoc) { SESNotificationMessage message = new SESNotificationMessage(); //remove duplicate gml:ids eventDoc = XMLHelper.removeIDDublications(eventDoc); //get DOM Node from Event Node node = null; try { node = XmlUtil.getDomNode(eventDoc); } catch (XMLHandlingException e) { logger.warn(e.getMessage(), e); } boolean sent = false; if (node != null) { //node successfully created message.addMessageContent((Element) node); if (logger.isDebugEnabled()) { logger.debug("message to send: " + message.toString()); } publish(message); sent = true; } return sent; } @Override public EndpointReference getEndpointReference() { return this.getWsResource() == null ? null : this.getWsResource().getEndpointReference(); } @Override public boolean isStreamPersistenceEnabled() { if (ConfigurationRegistry.isAvailable()) { Integer max = ConfigurationRegistry.getInstance().getIntegerProperty(ConfigurationRegistry.MAX_PERSISTED_EVENTS); if (max != null) { return max.intValue() > 0; } } return false; } @Override public List<PersistedEvent> getPersistedEvents() { if (streamPersistence == null) { return Collections.emptyList(); } return this.streamPersistence.getPersistedEvents(); } @Override public void persistEvent(MapEvent event, String streamName) { if (this.streamPersistence != null) { this.streamPersistence.persistEvent(event, streamName); } } @Override public String getUniqueID() { ISESFilePersistence pers = ConfigurationRegistry.getInstance().getFilePersistence(); if (pers instanceof SESFilePersistence) { SESFilePersistence sfp = (SESFilePersistence) pers; return sfp.getFileName(this.getEndpointReference()); } return UUID.randomUUID().toString(); } public void initializeStreamPersistence() { if (this.isStreamPersistenceEnabled()) { try { ISESFilePersistence pers = ConfigurationRegistry.getInstance().getFilePersistence(); File baseLocation = null; if (pers instanceof SESFilePersistence) { baseLocation = ((SESFilePersistence) pers).getBasePersistenceDirectory(); } this.streamPersistence = AbstractStreamPersistence.newInstance(this, baseLocation); } catch (Exception e) { logger.warn("Could not initialize stream persistence, it will not be available.", e); } } } }