/*=============================================================================*
* Copyright 2004 The Apache Software Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*=============================================================================*/
package org.apache.ws.notification.base.impl;
import EDU.oswego.cs.dl.util.concurrent.PooledExecutor;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.ws.Soap1_1Constants;
import org.apache.ws.addressing.EndpointReference;
import org.apache.ws.addressing.XmlBeansEndpointReference;
import org.apache.ws.addressing.v2003_03.AddressingConstants;
import org.apache.ws.notification.base.NotificationProducerResource;
import org.apache.ws.notification.base.Subscription;
import org.apache.ws.notification.base.SubscriptionManager;
import org.apache.ws.notification.base.v2004_06.BaseNotificationConstants;
import org.apache.ws.notification.base.v2004_06.impl.SubscriptionHome;
import org.apache.ws.notification.base.v2004_06.porttype.NotificationProducerPortType;
import org.apache.ws.notification.topics.Topic;
import org.apache.ws.notification.topics.TopicsTypeWriter;
import org.apache.ws.notification.topics.expression.InvalidTopicExpressionException;
import org.apache.ws.notification.topics.expression.TopicExpression;
import org.apache.ws.notification.topics.expression.TopicExpressionException;
import org.apache.ws.notification.topics.expression.TopicPathDialectUnknownException;
import org.apache.ws.notification.topics.v2004_06.TopicsConstants;
import org.apache.ws.pubsub.emitter.EmitterTask;
import org.apache.ws.resource.faults.FaultException;
import org.apache.ws.resource.properties.ResourcePropertySet;
import org.apache.ws.resource.properties.query.InvalidQueryExpressionException;
import org.apache.ws.resource.properties.query.QueryEngine;
import org.apache.ws.resource.properties.query.QueryEvaluationErrorException;
import org.apache.ws.resource.properties.query.QueryExpression;
import org.apache.ws.resource.properties.query.UnknownQueryExpressionDialectException;
import org.apache.ws.resource.properties.query.impl.QueryEngineImpl;
import org.apache.ws.util.JaxpUtils;
import org.apache.ws.util.XmlBeanUtils;
import org.apache.ws.util.thread.NamedThread;
import org.apache.xmlbeans.XmlObject;
import org.apache.xmlbeans.XmlOptions;
import org.oasisOpen.docs.wsn.x2004.x06.wsnWSBaseNotification12Draft01.NotificationMessageHolderType;
import org.oasisOpen.docs.wsn.x2004.x06.wsnWSBaseNotification12Draft01.NotifyDocument;
import org.oasisOpen.docs.wsn.x2004.x06.wsnWSBaseNotification12Draft01.TopicExpressionType;
import org.w3c.dom.Document;
import org.xml.sax.SAXException;
import org.xmlsoap.schemas.ws.x2003.x03.addressing.EndpointReferenceType;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.soap.MessageFactory;
import javax.xml.soap.SOAPBody;
import javax.xml.soap.SOAPElement;
import javax.xml.soap.SOAPEnvelope;
import javax.xml.soap.SOAPFactory;
import javax.xml.soap.SOAPHeader;
import javax.xml.soap.SOAPHeaderElement;
import javax.xml.soap.SOAPMessage;
import java.io.IOException;
import java.net.URL;
import java.util.Calendar;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
/**
* A helper class for a notification producer resource that keeps track of subscriptions and publishes messages.
* Note, this class is specification version neutral.
*
* @author Ian Springer
*/
public class NotificationProducerHelper
{
private static Log LOG =
LogFactory.getLog( NotificationProducerHelper.class.getName( ) );
private static final QueryEngine QUERY_ENGINE = new QueryEngineImpl( );
private static final PooledExecutor EMITTER_POOL;
static
{
EMITTER_POOL = new PooledExecutor( 100 );
// make sure the threads are non-daemon threads so they have time to complete even if the JVM wants to shut down
EMITTER_POOL.setThreadFactory( new NamedThread.ConcurrentThreadFactory( "notifmgr-emitter", false ) );
}
private NotificationProducerResource m_producerResource;
/*
* Maps {@link Topic}s to {@link Object}s respresenting the most recent messages
* published to those Topics.
*/
private Map m_currentMsgMap = Collections.synchronizedMap( new HashMap( ) );
/**
* Creates a new {@link NotificationProducerHelper} object.
*
* @param producerResource DOCUMENT_ME
*/
public NotificationProducerHelper( NotificationProducerResource producerResource )
{
m_producerResource = producerResource;
}
/**
*
* @param topicExpr a topic expression describing EXACTLY ONE topic
*
* @return the current message
*/
public Object getCurrentMessage( TopicExpression topicExpr )
throws InvalidTopicExpressionException,
TopicNotSupportedException,
NoCurrentMessageOnTopicException
{
Topic topic = toTopic( topicExpr );
Object currentMsg = m_currentMsgMap.get( topic );
if ( currentMsg == null )
{
throw new NoCurrentMessageOnTopicException( topic );
}
return currentMsg;
}
/**
* Publishes the given message to the topic identified by the given topic expression.
*
* @param topicExpr a topic expression describing EXACTLY ONE topic
* @param msg the notification message to be published - may be a {@link org.w3c.dom.Node} or an {@link
* org.apache.xmlbeans.XmlObject}
*/
public void publish( TopicExpression topicExpr,
Object msg )
throws TopicNotSupportedException,
InvalidTopicExpressionException,
MessageTypeNotSupportedException
{
Topic topic = toTopic( topicExpr );
m_currentMsgMap.put( topic, msg );
Subscription[] subscriptions =
SubscriptionManager.getInstance( ).getSubscriptions( m_producerResource, topic );
LOG.debug( "Topic " + topic + " matched " + subscriptions.length + " subscriptions." );
XmlObject msgXBean;
try
{
msgXBean = XmlBeanUtils.toXmlObject( msg );
}
catch ( Exception e )
{
throw new MessageTypeNotSupportedException( "Unable to convert " + msg.getClass( ).getName( )
+ " object into an XmlObject.", e );
}
for ( int i = 0; i < subscriptions.length; i++ )
{
Subscription subscription = subscriptions[i];
try
{
notify( subscription, topicExpr, msgXBean );
}
catch ( Exception e )
{
e.printStackTrace( );
}
}
}
/**
* Subscribe to the topic(s) indicated by the specified topic expression.
* Notifications for the subscription will be sent to the specified
* consumer EPR. Other optional parameters may also be specified.
*
* @param consumerEPR
* @param topicExpr
* @param useNotify
* @param precondition
* @param selector
* @param policy
* @param initialTerminationTime
*
* @return the EPR of the newly created subscription
*/
public EndpointReference subscribe( EndpointReference consumerEPR,
TopicExpression topicExpr,
Boolean useNotify,
QueryExpression precondition,
QueryExpression selector,
Object policy,
Calendar initialTerminationTime )
throws SubscribeCreationFailedException,
InvalidTopicExpressionException,
TopicPathDialectUnknownException
{
if ( consumerEPR == null )
{
throw new IllegalArgumentException( "The consumer EPR parameter may not be null." );
}
if ( topicExpr == null )
{
throw new IllegalArgumentException( "The topic expression parameter may not be null." );
}
Topic[] topics = evaluateTopicExpression( topicExpr );
if ( topics.length == 0 )
{
throw new InvalidTopicExpressionException( "Given TopicExpression did not match any Topics supported by this NotificationProducer - the WS-BaseN spec mandates that it match at least one." );
}
// TODO: SubscriptionHome class is specific to WSN 2004/06 - should be replaced by a WSN-version-neutral SubscriptionHome interface
SubscriptionHome subscriptionHome =
(SubscriptionHome) SubscriptionManager.getInstance( ).getSubscriptionManagerHome( m_producerResource );
Subscription subscription = null;
try
{
subscription =
subscriptionHome.create( m_producerResource.getEndpointReference( ),
consumerEPR,
topicExpr );
subscription.setTerminationTime( initialTerminationTime );
subscription.setUseNotify( ( useNotify == null ) || useNotify.booleanValue( ) );
subscription.setSelector( selector );
subscription.setPrecondition( precondition );
subscription.setPolicy( policy );
SubscriptionManager.getInstance( ).addSubscription( subscription, topics );
}
catch ( Exception e )
{
LOG.error( "Subscribe failed due to internal error: " + e );
if ( LOG.isDebugEnabled( ) )
{
e.printStackTrace( );
}
throw new SubscribeCreationFailedException( e );
}
return subscription.getEndpointReference( );
}
private void addMessageAddressingPropertiesToHeader( Object[] props,
SOAPHeader header )
throws Exception
{
if ( props != null )
{
for ( int i = 0; i < props.length; i++ )
{
XmlObject refProp = (XmlObject) props[i];
// TODO: *SJC* ideally, the below logic would handle refParams that are complexTypes...
// TODO (ips): see addSOAPBodyElements() in ResourceHandler for a fairly easy way to do it
SOAPElement soapElem = XmlBeanUtils.toSOAPElement( refProp );
SOAPHeaderElement headerElem = header.addHeaderElement( soapElem.getElementName( ) );
headerElem.addTextNode( soapElem.getValue( ) );
}
}
}
private void addReferenceParametersToHeader( SOAPHeader header,
EndpointReference consumerEPR )
throws Exception
{
addMessageAddressingPropertiesToHeader( consumerEPR.getReferenceParameters( ),
header );
}
private void addReferencePropertiestoHeader( SOAPHeader header,
EndpointReference consumerEPR )
throws Exception
{
addMessageAddressingPropertiesToHeader( consumerEPR.getReferenceProperties( ),
header );
}
/*
* Add WS-Addressing headers to a notification.
*
* @param header the header to which to add the WS-Addressing headers.
* @param consumerEPR the EPR to the consumer of this notification.
* @throws Exception
*/
private void addWSAHeaders( SOAPHeader header,
EndpointReference consumerEPR )
throws Exception
{
SOAPFactory factory = SOAPFactory.newInstance( );
// TODO: *SJC* this should not be hard-coded to use WSA 2003/03. Once a new version of WSN is implemented we will need to support multiple versions
SOAPHeaderElement headerElem =
header.addHeaderElement( factory.createName( org.apache.ws.addressing.v2003_03.AddressingConstants.TO,
org.apache.ws.addressing.v2003_03.AddressingConstants.NSPREFIX_ADDRESSING_SCHEMA,
org.apache.ws.addressing.v2003_03.AddressingConstants.NSURI_ADDRESSING_SCHEMA ) );
headerElem.addTextNode( consumerEPR.getAddress( ) );
headerElem =
header.addHeaderElement( factory.createName( AddressingConstants.ACTION,
AddressingConstants.NSPREFIX_ADDRESSING_SCHEMA,
AddressingConstants.NSURI_ADDRESSING_SCHEMA ) );
headerElem.addTextNode( BaseNotificationConstants.NOTIFY_ACTION_URI );
addReferencePropertiestoHeader( header, consumerEPR );
addReferenceParametersToHeader( header, consumerEPR );
}
private SOAPMessage buildSOAPMessage( Document fullMsgBodyElem,
EndpointReference consumerEPR )
throws Exception
{
SOAPMessage msg = MessageFactory.newInstance( ).createMessage( );
SOAPEnvelope envelope = msg.getSOAPPart( ).getEnvelope( );
SOAPBody body = envelope.getBody( );
body.addDocument( fullMsgBodyElem );
SOAPHeader header = msg.getSOAPHeader( );
addWSAHeaders( header, consumerEPR );
return msg;
}
private boolean evaluatePrecondition( QueryExpression precondition,
ResourcePropertySet propSet )
throws Exception
{
boolean result;
if ( precondition == null )
{
result = true;
}
else
{
Object queryResult = QUERY_ENGINE.executeQuery( precondition, propSet );
try
{
result = ( (Boolean) queryResult ).booleanValue( );
LOG.debug( "Notification precondition '" + precondition + "' evaluated to " + result );
}
catch ( RuntimeException re )
{
result = false;
LOG.error( "Notification precondition '" + precondition
+ "' did not evaluate to a Boolean at notification time." );
}
}
return result;
}
private boolean evaluateSelector( QueryExpression selector,
XmlObject msg )
throws UnknownQueryExpressionDialectException,
QueryEvaluationErrorException,
InvalidQueryExpressionException
{
boolean result;
if ( selector == null )
{
result = true;
}
else
{
Object queryResult = QUERY_ENGINE.executeQuery( selector, msg );
try
{
result = ( (Boolean) queryResult ).booleanValue( );
}
catch ( RuntimeException re )
{
result = false;
LOG.error( "Notification selector '" + selector
+ "' did not evaluate to a Boolean at notification time." );
}
LOG.debug( "Notification selector '" + selector + "' evaluated to " + result );
}
return result;
}
private Topic[] evaluateTopicExpression( TopicExpression topicExpr )
throws TopicPathDialectUnknownException
{
try
{
return m_producerResource.getTopicSet( ).evaluateTopicExpression( topicExpr );
}
catch ( TopicPathDialectUnknownException tpdue )
{
throw tpdue;
}
catch ( TopicExpressionException tee )
{
throw new FaultException( Soap1_1Constants.FAULT_CLIENT,
tee.getLocalizedMessage( ) );
}
}
private void notify( Subscription subscription,
TopicExpression topicExpr,
XmlObject msgXBean )
throws Exception
{
synchronized ( subscription )
{
if ( !subscription.isPaused( ) )
{
LOG.debug( "Notification being sent for subscription with id " + subscription.getID( )
+ "; message value: " + msgXBean );
if ( evaluateSelector( subscription.getSelector( ),
msgXBean )
&& evaluatePrecondition( subscription.getPrecondition( ),
subscription.getProducerResource( ).getResourcePropertySet( ) ) )
{
if ( subscription.getUseNotify( ) )
{
msgXBean = wrapMessageWithNotify( msgXBean, topicExpr );
}
Document msgDom = toDomDocument( msgXBean );
EndpointReference consumerEPR = subscription.getConsumerReference( );
SOAPMessage soapMsg = buildSOAPMessage( msgDom, consumerEPR );
EMITTER_POOL.execute( EmitterTask.createEmitterTask( soapMsg,
new URL( consumerEPR.getAddress( ).toString( ) ) ) );
}
}
}
}
private Document toDomDocument( XmlObject notifyDoc )
throws ParserConfigurationException,
SAXException,
IOException
{
Document dom;
if ( XmlBeanUtils.isDocument( notifyDoc ) )
{
dom = (Document) notifyDoc.newDomNode( );
}
else
{
String notifyDocAsString = notifyDoc.xmlText( new XmlOptions( ).setSaveOuter( ) );
dom = JaxpUtils.toDocument( notifyDocAsString );
}
return dom;
}
private Topic toTopic( TopicExpression topicExpr )
throws InvalidTopicExpressionException,
TopicNotSupportedException
{
Topic[] topics = new org.apache.ws.notification.topics.Topic[0];
try
{
topics = m_producerResource.getTopicSet( ).evaluateTopicExpression( topicExpr );
}
catch ( TopicExpressionException tee )
{
throw new InvalidTopicExpressionException( tee.getLocalizedMessage( ) );
}
if ( topics.length == 0 )
{
throw new TopicNotSupportedException( "Given TopicExpression '" + topicExpr
+ "' did not match any Topics supported by this NotificationProducer - the WS-BaseN spec mandates that it match exactly one." );
}
if ( topics.length > 1 )
{
throw new InvalidTopicExpressionException( "Given TopicExpression matched more than one Topic supported by this NotificationProducer - the WS-BaseN spec mandates that it match exactly one." );
}
return topics[0];
}
private XmlObject wrapMessageWithNotify( XmlObject msgXBean,
TopicExpression topicExpr )
{
// TODO (ips, 09/28/05): creation of Notify xbean should be spec-version-sensitive
NotifyDocument notifyDoc = NotifyDocument.Factory.newInstance( );
NotifyDocument.Notify notify = notifyDoc.addNewNotify( );
NotificationMessageHolderType notifMsgHolder = notify.addNewNotificationMessage( );
notifMsgHolder.setMessage( msgXBean );
EndpointReference producerEPR = m_producerResource.getEndpointReference( );
XmlBeansEndpointReference xBeansProducerEPR = ( (XmlBeansEndpointReference) producerEPR );
notifMsgHolder.setProducerReference( (EndpointReferenceType) xBeansProducerEPR.getXmlObject( org.apache.ws.addressing.v2003_03.AddressingConstants.NSURI_ADDRESSING_SCHEMA ) );
TopicExpressionType topicExprType =
(TopicExpressionType) TopicsTypeWriter.newInstance( TopicsConstants.NSURI_WSTOP_SCHEMA ).toXmlObject( topicExpr,
NotificationProducerPortType.PROP_QNAME_TOPIC );
notifMsgHolder.setTopic( topicExprType );
return notifyDoc;
}
}