/*
============================================================================
The Apache Software License, Version 1.1
============================================================================
Copyright (C) 1999-2003 The Apache Software Foundation. All rights reserved.
Redistribution and use in source and binary forms, with or without modifica-
tion, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. The end-user documentation included with the redistribution, if any, must
include the following acknowledgment: "This product includes software
developed by the Apache Software Foundation (http://www.apache.org/)."
Alternately, this acknowledgment may appear in the software itself, if
and wherever such third-party acknowledgments normally appear.
4. The names "Apache Cocoon" and "Apache Software Foundation" must not be
used to endorse or promote products derived from this software without
prior written permission. For written permission, please contact
apache@apache.org.
5. Products derived from this software may not be called "Apache", nor may
"Apache" appear in their name, without prior written permission of the
Apache Software Foundation.
THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
APACHE SOFTWARE FOUNDATION OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLU-
DING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
This software consists of voluntary contributions made by many individuals
on behalf of the Apache Software Foundation and was originally created by
Stefano Mazzocchi <stefano@apache.org>. For more information on the Apache
Software Foundation, please see <http://www.apache.org/>.
*/
package org.apache.cocoon.components.axis;
import java.io.File;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.avalon.framework.activity.Initializable;
import org.apache.avalon.framework.activity.Startable;
import org.apache.avalon.framework.component.Component;
import org.apache.avalon.framework.component.ComponentException;
import org.apache.avalon.framework.component.ComponentManager;
import org.apache.avalon.framework.component.Composable;
import org.apache.avalon.framework.configuration.Configurable;
import org.apache.avalon.framework.configuration.Configuration;
import org.apache.avalon.framework.configuration.ConfigurationException;
import org.apache.avalon.framework.context.Context;
import org.apache.avalon.framework.context.ContextException;
import org.apache.avalon.framework.context.Contextualizable;
import org.apache.avalon.framework.logger.AbstractLogEnabled;
import org.apache.avalon.framework.thread.ThreadSafe;
import org.apache.axis.AxisEngine;
import org.apache.axis.Constants;
import org.apache.axis.EngineConfiguration;
import org.apache.axis.MessageContext;
import org.apache.axis.configuration.FileProvider;
import org.apache.axis.deployment.wsdd.WSDDDeployment;
import org.apache.axis.deployment.wsdd.WSDDDocument;
import org.apache.axis.deployment.wsdd.WSDDService;
import org.apache.axis.security.servlet.ServletSecurityProvider;
import org.apache.axis.server.AxisServer;
import org.apache.axis.transport.http.HTTPConstants;
import org.apache.axis.transport.http.HTTPTransport;
import org.apache.axis.transport.http.ServletEndpointContextImpl;
import org.apache.axis.utils.XMLUtils;
import org.apache.cocoon.components.axis.providers.AvalonProvider;
import org.apache.cocoon.util.IOUtils;
import org.apache.excalibur.source.Source;
import org.apache.excalibur.source.SourceResolver;
import org.apache.excalibur.xml.dom.DOMParser;
import org.w3c.dom.Document;
import org.xml.sax.InputSource;
/**
* SOAP Server Implementation
*
* <p>
* This server accepts a SOAP Request, and generates the resultant
* response as output. Essentially, this reader allows you to serve SOAP
* requests from your Cocoon application.
* </p>
*
* <p>
* Code originates from the Apache
* <a href="http://xml.apache.org/axis">AXIS</a> project,
* <code>org.apache.axis.http.transport.AxisServlet</code>.
* </p>
*
* Ported to Cocoon by:
*
* @author <a href="mailto:crafterm@apache.org">Marcus Crafter</a>
*
* Original <code>AxisServlet</code> authors:
*
* @author <a href="mailto:">Steve Loughran</a>
* @author <a href="mailto:dug@us.ibm.com">Doug Davis</a>
*
* @version CVS $Id: SoapServerImpl.java,v 1.2 2003/03/24 14:33:58 stefano Exp $
*/
public class SoapServerImpl extends AbstractLogEnabled
implements SoapServer, Composable, Configurable, Contextualizable, Initializable,
Startable, ThreadSafe
{
/**
* Constant describing the default location of the server configuration file
*/
public static final String DEFAULT_SERVER_CONFIG
= "resource://org/apache/axis/server/server-config.wsdd";
// transport name
private String m_transportName;
// security provider reference
private ServletSecurityProvider m_securityProvider;
// JWS output directory
private String m_jwsClassDir;
// per-instance cache of the axis server
private AxisServer m_axisServer;
// axis server configuration
private FileProvider m_engineConfig;
// location of attachments
private String m_attachmentDir;
// server configuration
private Source m_serverWSDD;
// array containing locations to descriptors this reader should manage
private WSDDDocument[] m_descriptors;
// context reference
private Context m_context;
// component manager reference
private ComponentManager m_manager;
/**
* Contextualize this Reader.
*
* @param context a <code>Context</code> instance
* @exception ContextException if an error occurs
*/
public void contextualize(final Context context)
throws ContextException
{
m_context = context;
}
/**
* Compose this server
*
* @param manager a <code>ComponentManager</code> value
* @exception ComponentException if an error occurs
*/
public void compose(ComponentManager manager)
throws ComponentException
{
m_manager = manager;
}
/**
* Configures this reader.
*
* <p>
* Sets the following optional configuration settings:
*
* <ul>
* <li>Server WSDD configuration
* <li>Attachment directory
* <li>JWS directory
* <li>Security provider
* <li>Transport name
* <li>Mananged services
* </ul>
* </p>
*
* <p>
* The following format is used:
* <pre>
* <soap-server>
* <server-wsdd src="..."/>
* <attachment-dir src="..."/>
* <jws-dir src="..."/>
* <security-provider enabled="..."/>
* <transport name="..."/>
* <managed-services>
* <descriptor src="..."/>
* <descriptor src="..."/>
* </managed-services>
* </soap-server>
* </pre>
* </p>
*
* @param config a <code>Configuration</code> instance
* @exception ConfigurationException if an error occurs
*/
public void configure(final Configuration config)
throws ConfigurationException
{
try {
setServerConfig(config);
setAttachmentDir(config);
setJWSDir(config);
setSecurityProvider(config);
setTransportName(config);
setManagedServices(config);
if (getLogger().isDebugEnabled()) {
getLogger().debug("SoapServerImpl.configure() complete");
}
} catch (final Exception e) {
throw new ConfigurationException("Error during configuration", e);
}
}
/**
* Helper method to set the axis server configuration.
*
* @param config a <code>Configuration</code> instance
* @exception Exception if an error occurs
*/
public void setServerConfig(final Configuration config)
throws Exception
{
final Configuration wsdd = config.getChild("server-wsdd");
SourceResolver resolver = null;
try
{
resolver = (SourceResolver) m_manager.lookup(SourceResolver.ROLE);
m_serverWSDD =
resolver.resolveURI(
wsdd.getAttribute("src", DEFAULT_SERVER_CONFIG)
);
}
finally
{
if (resolver != null) m_manager.release(resolver);
}
}
/**
* Helper method to set the attachment dir. If no attachment directory has
* been specified, then its set up to operate out of the Cocoon workarea.
*
* @param config a <code>Configuration</code> instance
* @exception ConfigurationException if a configuration error occurs
* @exception ContextException if a context error occurs
*/
private void setAttachmentDir(final Configuration config)
throws ConfigurationException, ContextException
{
final Configuration dir = config.getChild("attachment-dir");
m_attachmentDir = dir.getAttribute("src", null);
if (m_attachmentDir == null)
{
File workDir =
(File) m_context.get(org.apache.cocoon.Constants.CONTEXT_WORK_DIR);
File attachmentDir =
IOUtils.createFile(workDir, "attachments" + File.separator);
m_attachmentDir = IOUtils.getFullFilename(attachmentDir);
}
if (getLogger().isDebugEnabled())
{
getLogger().debug("attachment directory = " + m_attachmentDir);
}
}
/**
* Helper method to set the JWS class dir. If no directory is specified then
* the directory <i>axis-jws</i> is used, under the Cocoon workarea.
*
* @param config a <code>Configuration</code> instance
* @exception ConfigurationException if a configuration error occurs
* @exception ContextException if a context error occurs
*/
private void setJWSDir(final Configuration config)
throws ConfigurationException, ContextException
{
final Configuration dir = config.getChild("jws-dir");
m_jwsClassDir = dir.getAttribute("src", null);
if (m_jwsClassDir == null)
{
File workDir =
(File) m_context.get(org.apache.cocoon.Constants.CONTEXT_WORK_DIR);
File jwsClassDir =
IOUtils.createFile(workDir, "axis-jws" + File.separator);
m_jwsClassDir = IOUtils.getFullFilename(jwsClassDir);
}
if (getLogger().isDebugEnabled())
{
getLogger().debug("jws class directory = " + m_jwsClassDir);
}
}
/**
* Helper method to set the security provider.
*
* @param config a <code>Configuration</code> instance
* @exception ConfigurationException if an error occurs
*/
private void setSecurityProvider(final Configuration config)
throws ConfigurationException
{
final Configuration secProvider =
config.getChild("security-provider", false);
if (secProvider != null)
{
final String attr = secProvider.getAttribute("enabled");
final boolean providerIsEnabled =
"true".equalsIgnoreCase(attr) || "yes".equalsIgnoreCase(attr);
if (providerIsEnabled)
m_securityProvider = new ServletSecurityProvider();
}
if (getLogger().isDebugEnabled())
{
getLogger().debug("security provider = " + m_securityProvider);
}
}
/**
* Helper method to set the transport name
*
* @param config a <code>Configuration</code> instance
* @exception ConfigurationException if an error occurs
*/
private void setTransportName(final Configuration config)
throws ConfigurationException
{
final Configuration name = config.getChild("transport");
m_transportName =
name.getAttribute("name", HTTPTransport.DEFAULT_TRANSPORT_NAME);
}
/**
* Helper method to obtain a list of managed services from the given
* configuration (ie. locations of deployement descriptors to be
* deployed).
*
* @param config a <code>Configuration</code> value
* @exception Exception if an error occurs
*/
private void setManagedServices(final Configuration config)
throws Exception
{
final Configuration m = config.getChild("managed-services", false);
final List descriptors = new ArrayList();
if (m != null)
{
SourceResolver resolver = null;
DOMParser parser = null;
try
{
final Configuration[] services = m.getChildren("descriptor");
resolver = (SourceResolver) m_manager.lookup(SourceResolver.ROLE);
parser = (DOMParser) m_manager.lookup(DOMParser.ROLE);
for (int i = 0; i < services.length; ++i)
{
final String location = services[i].getAttribute("src");
Source source = resolver.resolveURI(location);
final Document d =
parser.parseDocument(
new InputSource(
new InputStreamReader(source.getInputStream())
)
);
descriptors.add(new WSDDDocument(d));
}
}
finally
{
if (resolver != null) m_manager.release(resolver);
if (parser != null) m_manager.release((Component)parser);
}
}
// convert the list of descriptors to an array, for easier iteration
m_descriptors =
(WSDDDocument[]) descriptors.toArray(new WSDDDocument[]{});
}
/**
* Initialize this reader, creates AXIS server engine.
*
* @exception Exception if an error occurs
*/
public void initialize()
throws Exception
{
m_axisServer = createEngine();
if (getLogger().isDebugEnabled())
{
getLogger().debug("SoapServerImpl.initialize() complete");
}
}
/**
* Starts this reader. Deploys all managed services as specified at
* configuration time.
*
* @exception Exception if an error occurs
*/
public void start()
throws Exception
{
// deploy all configured services
for (int i = 0; i < m_descriptors.length; ++i)
{
WSDDDeployment deployment = m_engineConfig.getDeployment();
m_descriptors[i].deploy(deployment);
if (getLogger().isDebugEnabled())
{
getLogger().debug(
"Deployed Descriptor:\n" +
XMLUtils.DocumentToString(m_descriptors[i].getDOMDocument())
);
}
}
if (getLogger().isDebugEnabled())
{
getLogger().debug("SoapServerImpl.start() complete");
}
}
/**
* Stops this reader. Undeploys all managed services this reader
* currently manages (includes services dynamically added to the reader
* during runtime).
*
* @exception Exception if an error occurs
*/
public void stop()
throws Exception
{
WSDDDeployment deployment = m_engineConfig.getDeployment();
WSDDService[] services = deployment.getServices();
// undeploy all deployed services
for (int i = 0; i < services.length; ++i)
{
deployment.undeployService(services[i].getQName());
if (getLogger().isDebugEnabled())
{
getLogger().debug("Undeployed: " + services[i].toString());
}
}
if (getLogger().isDebugEnabled())
{
getLogger().debug("SoapServerImpl.stop() complete");
}
}
public void invoke(MessageContext message)
throws Exception
{
m_axisServer.invoke(message);
}
/**
* Place the Request message in the MessagContext object - notice
* that we just leave it as a 'ServletRequest' object and let the
* Message processing routine convert it - we don't do it since we
* don't know how it's going to be used - perhaps it might not
* even need to be parsed.
*/
public MessageContext createMessageContext(
HttpServletRequest req,
HttpServletResponse res,
ServletContext con
)
{
MessageContext msgContext = new MessageContext(m_axisServer);
String webInfPath = con.getRealPath("/WEB-INF");
String homeDir = con.getRealPath("/");
// Set the Transport
msgContext.setTransportName(m_transportName);
// Add Avalon specifics to MessageContext
msgContext.setProperty(LOGGER, getLogger());
msgContext.setProperty(AvalonProvider.COMPONENT_MANAGER, m_manager);
// Save some HTTP specific info in the bag in case someone needs it
msgContext.setProperty(Constants.MC_JWS_CLASSDIR, m_jwsClassDir);
msgContext.setProperty(Constants.MC_HOME_DIR, homeDir);
msgContext.setProperty(Constants.MC_RELATIVE_PATH, req.getServletPath());
msgContext.setProperty(HTTPConstants.MC_HTTP_SERVLET, this );
msgContext.setProperty(HTTPConstants.MC_HTTP_SERVLETREQUEST, req );
msgContext.setProperty(HTTPConstants.MC_HTTP_SERVLETRESPONSE, res );
msgContext.setProperty(HTTPConstants.MC_HTTP_SERVLETLOCATION, webInfPath);
msgContext.setProperty(HTTPConstants.MC_HTTP_SERVLETPATHINFO,
req.getPathInfo() );
msgContext.setProperty(HTTPConstants.HEADER_AUTHORIZATION,
req.getHeader(HTTPConstants.HEADER_AUTHORIZATION));
msgContext.setProperty(Constants.MC_REMOTE_ADDR, req.getRemoteAddr());
// Set up a javax.xml.rpc.server.ServletEndpointContext
ServletEndpointContextImpl sec = new ServletEndpointContextImpl();
msgContext.setProperty(Constants.MC_SERVLET_ENDPOINT_CONTEXT, sec);
// Save the real path
String realpath = con.getRealPath(req.getServletPath());
if (realpath != null)
{
msgContext.setProperty(Constants.MC_REALPATH, realpath);
}
msgContext.setProperty(Constants.MC_CONFIGPATH, webInfPath);
if (m_securityProvider != null)
msgContext.setProperty("securityProvider", m_securityProvider);
// write out the contents of the message context for debugging purposes
if (getLogger().isDebugEnabled())
{
debugMessageContext(msgContext);
}
return msgContext;
}
/**
* Helper method to log the contents of a given message context
*
* @param context a <code>MessageContext</code> instance
*/
private void debugMessageContext(final MessageContext context)
{
for (final Iterator i = context.getPropertyNames();
i.hasNext();
)
{
final String key = (String) i.next();
getLogger().debug(
"MessageContext: Key:" + key + ": Value: " + context.getProperty(key)
);
}
}
/**
* This is a uniform method of initializing AxisServer in a servlet
* context.
*/
public AxisServer createEngine()
throws Exception
{
AxisServer engine = AxisServer.getServer(getEngineEnvironment());
if (getLogger().isDebugEnabled())
{
getLogger().debug("Axis engine created");
}
return engine;
}
protected Map getEngineEnvironment()
throws Exception
{
Map env = new HashMap();
// use FileProvider directly with a Avalon Source object instead of going
// through the EngineConfigurationFactoryServlet class
m_engineConfig = new FileProvider(m_serverWSDD.getInputStream());
env.put(EngineConfiguration.PROPERTY_NAME, m_engineConfig);
env.put(AxisEngine.ENV_ATTACHMENT_DIR, m_attachmentDir);
// REVISIT(MC): JNDI Factory support ?
//env.put(AxisEngine.ENV_SERVLET_CONTEXT, context);
return env;
}
/*
* Helper method to convert a <code>Message</code> structure
* into a <code>String</code>.
*
* @param msg a <code>Message</code> value
* @return a <code>String</code> value
*/
/* FIXME (SM): this method appears to be unused, should we remove it?
private String messageToString(final Message msg)
{
try
{
OutputStream os = new ByteArrayOutputStream();
msg.writeTo(os);
return os.toString();
}
catch (Exception e)
{
if (getLogger().isWarnEnabled())
{
getLogger().warn(
"Warning, could not convert message (" + msg + ") into string", e
);
}
return null;
}
} */
}