/** * 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.lang.reflect.Method; import java.util.Collection; import java.util.Date; import java.util.Iterator; import java.util.List; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import javax.xml.namespace.QName; import org.apache.muse.core.routing.MessageHandler; import org.apache.muse.util.ReflectUtils; import org.apache.muse.util.xml.XmlSerializable; 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.SoapFault; import org.apache.muse.ws.notification.Filter; import org.apache.muse.ws.notification.NotificationConsumer; 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.faults.SubscribeCreationFailedFault; import org.apache.muse.ws.notification.faults.TopicNotSupportedFault; import org.apache.muse.ws.notification.faults.UnacceptableInitialTerminationTimeFault; import org.apache.muse.ws.notification.impl.FilterCollection; import org.apache.muse.ws.notification.impl.SimpleNotificationProducer; import org.apache.muse.ws.notification.topics.Topic; import org.apache.muse.ws.notification.topics.WstConstants; import org.apache.muse.ws.resource.WsResource; import org.n52.ses.api.IFilterEngine; import org.n52.ses.api.common.FreeResourceListener; import org.n52.ses.api.common.GlobalConstants; import org.n52.ses.api.ws.ISubscriptionManager; import org.n52.ses.api.ws.SESFilterCollection; import org.n52.ses.requestlogger.RequestLoggerWrapper; import org.n52.ses.util.common.ConfigurationRegistry; import org.n52.ses.util.concurrent.NamedThreadFactory; import org.n52.ses.wsbr.SesTopicFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.w3c.dom.Element; /** * * @author Matthes Rieke <m.rieke@uni-muenster.de> * */ public class SESNotificationProducer extends SimpleNotificationProducer implements FreeResourceListener { public static final String CONTEXT_PATH = GlobalConstants.NOTIFICATION_PRODUCER_CONTEXT_PATH; private static final Logger logger = LoggerFactory.getLogger(SESNotificationProducer.class); private static Object FIRST_RUN_MUTEX = new Object(); IFilterEngine _filterEngine; private ExecutorService notfiyPool; private static boolean FIRST_RUN = true; @Override public void freeResources() { if (this.notfiyPool != null) this.notfiyPool.shutdownNow(); } @Override public void publish(QName topicName, XmlSerializable content) throws SoapFault { publish(topicName, new XmlSerializable[]{ content }); } @Override public void publish(QName topicName, XmlSerializable[] content) throws SoapFault { Element[] contentXML = new Element[content.length]; for (int n = 0; n < content.length; ++n) contentXML[n] = content[n].toXML(); publish(topicName, contentXML); } @Override protected MessageHandler createSubscribeHandler() { MessageHandler handler = new SubscribeWithPolicyHandler(); Method method = ReflectUtils.getFirstMethod(getClass(), "subscribe"); handler.setMethod(method); return handler; } /** * @throws SoapFault if an error occurred on publishing */ @Override public void publish(QName topicName, Element[] content) throws SoapFault { // // construct the message/payload // NotificationMessage message = createNotificationMessage(); for (int n = 0; n < content.length; ++n) message.addMessageContent(content[n]); /* * for genesis a Concrete topic (XPath expression) must * be used. */ ConfigurationRegistry config = ConfigurationRegistry.getInstance(); boolean genesisMode = false; Object gm = config.getPropertyForKey( ConfigurationRegistry.USE_FOR_GENESIS); if (gm != null) { genesisMode = Boolean.parseBoolean(gm.toString()); } if (genesisMode) { String genesisNS = config.getPropertyForKey(ConfigurationRegistry.GENESIS_NAMESPACE); String topicExpression = config.getPropertyForKey(ConfigurationRegistry.GENESIS_TOPIC); String genesisPrefix = topicExpression.substring(0, topicExpression.indexOf(":")); message.setTopicDialect(WstConstants.CONCRETE_TOPIC_URI); message.setTopicExpression(genesisNS, genesisPrefix, topicExpression); } else { message.setTopic(topicName); } // // give the message to each subscription so it can decide if it // should be sent or not // /* * * check if SubscriptionManager without SESConstraintFilter exists * and forward message to them. * */ Iterator<?> i = getSubscriptions().iterator(); while (i.hasNext()) { SESSubscriptionManager sub = (SESSubscriptionManager)i.next(); if (!sub.isHasConstraintFilter()) { sub.publish(message); } } /* * * Entrancepoint for the Esper engine * * (normal SubscriptionManager call is not needed any longer) * * NOT NEEDED ANY LONGER */ //this._filterEngine.filter(message); /* * * End of entrancepoint for the Esper engine * */ // // if a topic was used, record the message as the 'current' message // for the topic (will be returned by getCurrentMessage()) // if (topicName != null) { Topic topic = getTopic(topicName); if (topic != null) topic.setCurrentMessage(message); } } @Override public WsResource subscribe(EndpointReference er, Filter filter, Date date, Policy policy) throws TopicNotSupportedFault, UnacceptableInitialTerminationTimeFault, SubscribeCreationFailedFault { if (logger.isDebugEnabled()) logger.debug("subscribing... "+ er.getAddress()); if (ConfigurationRegistry.getInstance() != null && er.getAddress().toString().equals( ConfigurationRegistry.getInstance().getSesPortTypeEPR().getAddress().toString())) { /* * this would create an infinit loop!! */ SESNotificationProducer.logger.warn("There was an attempt to create an infinite loop. The EndpointReference" + " linked to this service's PortType. Subscription rejected."); throw new SubscribeCreationFailedFault("There was an attempt to create an infinite loop. The EndpointReference" + " linked to this service's PortType. Subscription rejected."); } Filter f = getSESFilterCollectionFromCollection(filter); /* * create SESSubscriptionManager */ WsResource result = super.subscribe(er, f, date, policy); //add metadata element to endpoint //TODO: get from wsdl? result.getEndpointReference().setMetadata(new QName("http://www.opengis.net/ses/0.0", "SubscriptionManager", "sesinst"), new QName("http://www.opengis.net/ses/0.0", "SubscriptionManagerService", "sesinst"), "SubscriptionManagerPort", ConfigurationRegistry.getInstance().getSubMgrWsdl()); SESSubscriptionManager subMgr = (SESSubscriptionManager) result.getCapability(WsnConstants.SUBSCRIPTION_MGR_URI); //check for SESConstraintFilter and registers at IFilterEngine if (subMgr.isHasConstraintFilter()) { try { if (this._filterEngine == null) { synchronized (FIRST_RUN_MUTEX) { if (this._filterEngine == null) { this._filterEngine = ConfigurationRegistry.getInstance().getFilterEngine(); } } } if (this._filterEngine == null) { throw new SubscribeCreationFailedFault("Could not access an instanceof IFilterEngine! Subscriptions may not match!"); } else { this._filterEngine.registerFilter(subMgr); } } catch (Exception e) { try { /* * remove (delete router entry) the * resource because creation has failed */ result.shutdown(); } catch (SoapFault e1) { SESNotificationProducer.logger.warn("Could not remove false resource '"+ XmlUtils.toString(result.getEndpointReference().getParameter( WsaConstants.DEFAULT_RESOURCE_ID_QNAME)) +"'.\n" + "Please remove manually from the router-entries\\" + SESSubscriptionManager.CONTEXT_PATH +" folder or it " + "will throw an exception at restart."); } throw new SubscribeCreationFailedFault(e); } } return result; } /** * parses a SES filter collection from a notification filter * * @param filter the filter from the XML request * @return the SES filter collection */ public static Filter getSESFilterCollectionFromCollection(Filter filter) { //check if we have a SESConstraintFilter if (filter instanceof FilterCollection) { return createSESFilterCollection((FilterCollection) filter); } return filter; } private static Filter createSESFilterCollection(FilterCollection filter) { SESFilterCollection sesFilterColl = new SESFilterCollection(); addToSESFilterCollection(filter, sesFilterColl); // SESConstraintFilter sesConstraint = null; // // for (Filter f : sesFilterColl.getFilters()) { // if (f instanceof SESConstraintFilter) { // sesConstraint = (SESConstraintFilter) f; // break; // } // } // // if (sesConstraint != null) { // IEML eml = sesConstraint.getEml(); // if (eml != null) { // SESConstraintFilter scf = new SESConstraintFilter(eml); // // try { // sesFilterColl.addFilter(scf); // } catch (Throwable e) { // logger.warn(e.getMessage() + "\n" + e); // } // } // // } else { // if (logger.isDebugEnabled()) // logger.debug("subscription without SESConstraintFilter.."); // } //all filters are now in SESFilterCollection return sesFilterColl; } private static void addToSESFilterCollection( Filter filter, SESFilterCollection sesFilterColl) { if (filter instanceof FilterCollection) { //add this one later for (Object innerFilter : ((FilterCollection) filter).getFilters()) { addToSESFilterCollection((Filter) innerFilter, sesFilterColl); } } else { try { sesFilterColl.addFilter(filter); } catch (Throwable e) { logger.warn(e.getMessage()); } } } @Override public void shutdown() throws SoapFault { super.shutdown(); this.notfiyPool.shutdownNow(); } @Override public void initialize() throws SoapFault { logger.info("initialising SESNotificationProducer.."); super.initialize(); synchronized (FIRST_RUN_MUTEX) { if (FIRST_RUN) { /* * filter engine and unit converter */ ConfigurationRegistry conf = ConfigurationRegistry.getInstance(); this._filterEngine = conf.getFilterEngine(); conf.registerFreeResourceListener(this); /* * request logger */ RequestLoggerWrapper.init(conf); /* add default topics */ SesTopicFactory.addDefaultTopics(this); /* * init the threadpool */ this.notfiyPool = Executors.newFixedThreadPool(Integer.parseInt( conf.getPropertyForKey(ConfigurationRegistry.MAX_THREADS)), new NamedThreadFactory("NotifyHandlerPool")); /* * create a new thread which waits for the persistent Publishers * to be reloaded. * New thread needed, because the current thread would be blocked forever * as it is purposed to reload Publishers as well. */ new Thread(new Runnable() { @Override public void run() { /* * we need to wait for all * persistent publisher to be registered. before, some statement * creations could fail due to the unavailability of data type * definitions. */ ConfigurationRegistry.getInstance().waitForAllPersistentPublishers(); /* re-register submgrs */ List<ISubscriptionManager> res = ConfigurationRegistry.getInstance().getReresubs(); for (ISubscriptionManager sm : res) { if (sm instanceof SESSubscriptionManager) { SESSubscriptionManager sessm = (SESSubscriptionManager) sm; addSubscription(sessm.getWsResource()); /* check if registering at esper is need */ if (sessm.getFilter() instanceof SESFilterCollection) { SESFilterCollection sfc = (SESFilterCollection) sessm.getFilter(); if (sfc.getConstraintFilter() != null) { try { ConfigurationRegistry.getInstance().getFilterEngine().registerFilter(sessm); } catch (Exception e) { logger.warn(e.getMessage(), e); } } } } } } }).start(); FIRST_RUN = false; } } } @Override public void initializeCompleted() throws SoapFault { super.initializeCompleted(); NotificationConsumer consumer = (NotificationConsumer) getResource().getCapability(WsnConstants.CONSUMER_URI); /* * add Listener to Consumer (needed for notify messages) */ consumer.addMessageListener(new SESMessageListener(this)); } public void publishCompleteMessage(final NotificationMessage message) throws SoapFault { /* * * entrance point for the Esper engine * */ if (this._filterEngine == null) { synchronized (FIRST_RUN_MUTEX) { if (this._filterEngine == null) { this._filterEngine = ConfigurationRegistry.getInstance().getFilterEngine(); } } } if (this._filterEngine == null) { logger.warn("Could not access an instanceof IFilterEngine! Subscriptions may not match!"); } else { this._filterEngine.filter(message); } /* * call other wsnt:Filters */ if (this.notfiyPool == null) return; this.notfiyPool.submit(new Runnable() { @Override public void run() { /* * other */ Collection<?> contentNames = message.getMessageContentNames(); for (Iterator<?> iterator = contentNames.iterator(); iterator.hasNext();) { QName qn = (QName) iterator.next(); Element content = message.getMessageContent(qn); if (content != null) { try { publish(message.getTopic(), content); } catch (SoapFault e) { logger.warn(e.getMessage(), e); } } } } }); } }