/* * Copyright (C) 2000 - 2008 TagServlet Ltd * * This file is part of Open BlueDragon (OpenBD) CFML Server Engine. * * OpenBD is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * Free Software Foundation,version 3. * * OpenBD 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. * * You should have received a copy of the GNU General Public License * along with OpenBD. If not, see http://www.gnu.org/licenses/ * * Additional permission under GNU GPL version 3 section 7 * * If you modify this Program, or any covered work, by linking or combining * it with any of the JARS listed in the README.txt (or a modified version of * (that library), containing parts covered by the terms of that JAR, the * licensors of this Program grant you additional permission to convey the * resulting work. * README.txt @ http://www.openbluedragon.org/license/README.txt * * http://www.openbluedragon.org/ */ /* * Created on Oct 6, 2003 * * To change the template for this generated file go to * Window>Preferences>Java>Code Generation>Code and Comments */ package com.naryx.tagfusion.cfm.xml.ws; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.util.Map; import javax.servlet.ServletContext; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.xml.namespace.QName; import javax.xml.rpc.encoding.TypeMapping; import javax.xml.soap.SOAPConstants; import javax.xml.soap.SOAPException; import org.apache.axis.AxisFault; import org.apache.axis.Constants; import org.apache.axis.MessageContext; import org.apache.axis.constants.Scope; import org.apache.axis.constants.Style; import org.apache.axis.constants.Use; import org.apache.axis.description.ServiceDesc; import org.apache.axis.encoding.ser.BeanDeserializerFactory; import org.apache.axis.encoding.ser.BeanSerializerFactory; import org.apache.axis.handlers.BasicHandler; import org.apache.axis.handlers.soap.SOAPService; import org.apache.axis.providers.BasicProvider; import org.apache.axis.providers.java.JavaProvider; import org.apache.axis.transport.http.HTTPConstants; import com.nary.util.FastMap; import com.naryx.tagfusion.cfm.engine.ComponentFactory; import com.naryx.tagfusion.cfm.engine.cfCatchData; import com.naryx.tagfusion.cfm.engine.cfComponentData; import com.naryx.tagfusion.cfm.engine.cfSession; import com.naryx.tagfusion.cfm.engine.cfWebServices; import com.naryx.tagfusion.cfm.engine.cfmAccessForbiddenException; import com.naryx.tagfusion.cfm.engine.cfmBadFileException; import com.naryx.tagfusion.cfm.engine.cfmRunTimeException; import com.naryx.tagfusion.cfm.file.cfFile; import com.naryx.tagfusion.cfm.tag.cfCOMPONENT; import com.naryx.tagfusion.cfm.xml.ws.dynws.CFCDescriptor; import com.naryx.tagfusion.cfm.xml.ws.dynws.CFCInvoker; import com.naryx.tagfusion.cfm.xml.ws.dynws.ContextRegistrar; import com.naryx.tagfusion.cfm.xml.ws.dynws.DynamicCacheClassLoader; import com.naryx.tagfusion.cfm.xml.ws.dynws.DynamicWebServiceTypeGenerator; import com.naryx.tagfusion.cfm.xml.ws.encoding.ser.QueryBean; import com.naryx.tagfusion.cfm.xml.ws.encoding.ser.StructMap; import com.naryx.tagfusion.cfm.xml.ws.encoding.ser.StructMapItem; public class cfcHandler extends BasicHandler { private static final long serialVersionUID = 1L; private Map soapServices = null; private TypeMapping tm = null; private DynamicWebServiceTypeGenerator gen = null; /** * Default constructor. */ public cfcHandler() { // Setup caches this.soapServices = new FastMap(); // Create our type generator this.gen = new DynamicWebServiceTypeGenerator(cfWebServices.getJavaCacheDir()); } /** * Invokes the web service. * * @param msgContext * MessageContext for the current request. */ public void invoke(MessageContext msgContext) throws AxisFault { setupService(msgContext); } /** * Generates and returns WSDL xml data for the web service. * * @param msgContext * MessageContext for the current request. */ public void generateWSDL(MessageContext msgContext) throws AxisFault { setupService(msgContext); } /** * Initializes the default TypeMappingRegistry. * * @param msgContext * MessageContext for the current request. */ private void initializeTypeMapping(MessageContext msgContext) { // Initialize our TypeMapping if needed this.tm = msgContext.getAxisEngine().getTypeMappingRegistry().createTypeMapping(); msgContext.getAxisEngine().getTypeMappingRegistry().register(SOAPConstants.URI_NS_SOAP_ENCODING, this.tm); } /** * If our path ends in the right file extension (*.cfc), handle all the work * necessary to compile the source file if it needs it, and set up the "proxy" * RPC service surrounding it as the MessageContext's active service. * */ protected void setupService(MessageContext msgContext) throws AxisFault { // FORCE the targetService to be CFC if the URL is right. String realpath = msgContext.getStrProp(Constants.MC_REALPATH); if (realpath != null && realpath.length() > 4 && realpath.substring(realpath.length() - 4).equalsIgnoreCase(".cfc")) { // Reset the request stream resetRequestStream(msgContext); cfSession cfSes = getSession(msgContext); try { // Need to push the active file cfFile svrFile = cfSes.getRequestFile(); // Need to run the application.cfm cfSes.onRequestStart(svrFile); // Initialize the TypeMapping if necessary if (this.tm == null) initializeTypeMapping(msgContext); // Setup the dynamic class Class kls = null; cfComponentData cfc = null; String clsName = null; String compName = getComponentName(svrFile); synchronized (DynamicCacheClassLoader.SKEL_MUTEX) { // Check for modifications first kls = DynamicCacheClassLoader.findLoadedClass(DynamicWebServiceTypeGenerator.getFQName(compName), DynamicCacheClassLoader.SKEL_CLASSES); if (kls != null) { // Validate that all the involved classes are still valid DynamicCacheClassLoader dcl = (DynamicCacheClassLoader) kls.getClassLoader(); if (!dcl.areClassesValid()) { // Get rid of the service cache soapServices.clear(); // Unregister the classes corresponding to the CFC from the TMR unregisterClasses(msgContext, dcl); // Clear the loaded classes dcl.invalidate(); } } // Create the CFC svrFile.setComponentName(ComponentFactory.normalizeComponentName(compName)); cfc = new cfComponentData(cfSes, svrFile); CFCDescriptor cfcDescriptor; try { cfcDescriptor = new CFCDescriptor(cfc.getMetaData(), cfSes); } catch (IllegalStateException ise) { // If a function is missing a returnType attribute then the // CFCDescriptor // will throw an IllegalStateException. Catch it and re-throw as // runtime exception. // This is the fix for bug #2934. cfCatchData cd = new cfCatchData(); cd.setMessage("General Runtime Error"); cd.setDetail(ise.getMessage()); throw new cfmRunTimeException(cd); } clsName = gen.generateType(cfcDescriptor, msgContext); // Register the classes corresponding to the CFC in the TMR (if // necessary) registerClasses(msgContext, gen.getClassLoader(clsName)); } // Register the CFCInvoker for this Thread CFCInvoker inv = new CFCInvoker(cfc, cfSes); CFCInvoker.associate(Thread.currentThread(), inv); ClassLoader cl = gen.getClassLoader(clsName); msgContext.setClassLoader(cl); // Create a new RPCProvider - this will be the "service" // that we invoke. SOAPService rpc = null; rpc = (SOAPService) soapServices.get(compName); if (rpc == null) { rpc = new SOAPService(new cfcProvider(cfc)); rpc.setOption(JavaProvider.OPTION_CLASSNAME, clsName); rpc.setEngine(msgContext.getAxisEngine()); // Support specification of "allowedMethods" as a parameter. String allowed = (String) getOption(JavaProvider.OPTION_ALLOWEDMETHODS); if (allowed == null) allowed = "*"; rpc.setOption(JavaProvider.OPTION_ALLOWEDMETHODS, allowed); // Take the setting for the scope option from the handler // parameter named "scope" String scope = (String) getOption(JavaProvider.OPTION_SCOPE); if (scope == null) scope = Scope.DEFAULT.getName(); rpc.setOption(JavaProvider.OPTION_SCOPE, scope); // Set up service description ServiceDesc sd = rpc.getServiceDescription(); if (cfc.getMetaData().get(cfCOMPONENT.WSDLFILE) != null) { sd.setWSDLFile(cfc.getMetaData().get(cfCOMPONENT.WSDLFILE).toString().trim()); } else { if (cfc.getMetaData().get(cfCOMPONENT.HINT) != null) sd.setDocumentation(cfc.getMetaData().get(cfCOMPONENT.HINT).toString()); if (cfc.getMetaData().get(cfCOMPONENT.STYLE) != null && cfc.getMetaData().get(cfCOMPONENT.STYLE).toString().equalsIgnoreCase("document")) { sd.setStyle(Style.DOCUMENT); sd.setUse(Use.LITERAL); } else if (cfc.getMetaData().get(cfCOMPONENT.STYLE) != null && cfc.getMetaData().get(cfCOMPONENT.STYLE).toString().equalsIgnoreCase("document-wrapped")) { sd.setStyle(Style.WRAPPED); sd.setUse(Use.LITERAL); } else { sd.setStyle(Style.RPC); sd.setUse(Use.ENCODED); } // Update any specified names/strings for the WSDL if (cfc.getMetaData().get(cfCOMPONENT.DISPLAYNAME) != null && !cfc.getMetaData().get(cfCOMPONENT.DISPLAYNAME).toString().trim().equals("")) { rpc.setOption(BasicProvider.OPTION_WSDL_SERVICEELEMENT, cfc.getMetaData().get(cfCOMPONENT.DISPLAYNAME).toString().trim()); } if (cfc.getMetaData().get(cfCOMPONENT.SERVICEPORTNAME) != null && !cfc.getMetaData().get(cfCOMPONENT.SERVICEPORTNAME).toString().trim().equals("")) { rpc.setOption(BasicProvider.OPTION_WSDL_SERVICEPORT, cfc.getMetaData().get(cfCOMPONENT.SERVICEPORTNAME).toString().trim()); } if (cfc.getMetaData().get(cfCOMPONENT.PORTTYPENAME) != null && !cfc.getMetaData().get(cfCOMPONENT.PORTTYPENAME).toString().trim().equals("")) { rpc.setOption(BasicProvider.OPTION_WSDL_PORTTYPE, cfc.getMetaData().get(cfCOMPONENT.PORTTYPENAME).toString().trim()); } if (cfc.getMetaData().get(cfCOMPONENT.BINDINGNAME) != null && !cfc.getMetaData().get(cfCOMPONENT.BINDINGNAME).toString().trim().equals("")) { rpc.setOption(cfcProvider.OPTION_WSDL_BINDINGNAME, cfc.getMetaData().get(cfCOMPONENT.BINDINGNAME).toString().trim()); } } // Update some of the namespaces String modLoc = null; if (cfc.getMetaData().get(cfCOMPONENT.NAMESPACE) != null && !cfc.getMetaData().get(cfCOMPONENT.NAMESPACE).toString().trim().equals("")) { modLoc = cfc.getMetaData().get(cfCOMPONENT.NAMESPACE).toString().trim(); } else { HttpServletRequest req = (HttpServletRequest) msgContext.getProperty(HTTPConstants.MC_HTTP_SERVLETREQUEST); modLoc = req.getScheme() + ":/"; if (req.getServletPath() == null || req.getServletPath().trim().equals("")) modLoc += req.getRequestURI().substring(req.getRequestURI().lastIndexOf("/")); else if (!req.getServletPath().startsWith("/")) modLoc += "/" + req.getServletPath(); else modLoc += req.getServletPath(); } msgContext.setProperty(MessageContext.WSDLGEN_INTFNAMESPACE, modLoc); rpc.setOption(BasicProvider.OPTION_WSDL_TARGETNAMESPACE, modLoc); // Update the TypeMappings sd.setTypeMappingRegistry(msgContext.getAxisEngine().getTypeMappingRegistry()); sd.setTypeMapping(msgContext.getTypeMapping()); // Necessary to "seed" with these types as Axis will // use the package as the default targetNamespace for // these types. The specified namespaces here correspond // to the the ones defined in the classes themselves. QName qn = null; qn = new QName("http://wstypes.newatlanta.com", "QueryBean"); msgContext.getTypeMapping().register(QueryBean.class, qn, new BeanSerializerFactory(QueryBean.class, qn), new BeanDeserializerFactory(QueryBean.class, qn)); qn = new QName("http://wstypes.newatlanta.com", "StructMap"); msgContext.getTypeMapping().register(StructMap.class, qn, new BeanSerializerFactory(StructMap.class, qn), new BeanDeserializerFactory(StructMap.class, qn)); qn = new QName("http://wstypes.newatlanta.com", "StructMapItem"); msgContext.getTypeMapping().register(StructMapItem.class, qn, new BeanSerializerFactory(StructMapItem.class, qn), new BeanDeserializerFactory(StructMapItem.class, qn)); rpc.getInitializedServiceDesc(msgContext); soapServices.put(compName, rpc); } // Set engine, which hooks up type mappings. rpc.setEngine(msgContext.getAxisEngine()); rpc.init(); // ?? // OK, this is now the destination service! msgContext.setService(rpc); // Process onRequestEndFile cfSes.onRequestEnd(svrFile.getURI()); } catch (cfmAccessForbiddenException e) { processException(e, cfSes); throw new AxisFault("Access Forbidden", e); } catch (cfmBadFileException e) { // This exception is thrown by cfSession.getRequestFile() when the // request is for // an application.cfm or onrequestend.cfm page processException(e, cfSes); throw new AxisFault("Could not get requested file.", e); } catch (cfmRunTimeException e) { processException(e, cfSes); throw new AxisFault("Error processing request.", e); } catch (Exception e) { processException(e, cfSes); throw AxisFault.makeFault(e); } finally { cfSes.close(); } } } /** * Registers all the classes associated with the specified CFC generated class * in the specified MessageContext's TypeMapping, for use by Axis. Assumes the * specified DynamicCacheClassLoader is the one responsible for the type/class * being generated and that it has already been linked/associated to dependent * DynamicCacheClassLoader instances. * * @param msgContext * @param dcl */ private void registerClasses(MessageContext msgContext, DynamicCacheClassLoader dcl) { String scm = ((HttpServletRequest) msgContext.getProperty(HTTPConstants.MC_HTTP_SERVLETREQUEST)).getScheme(); dcl.registerClasses(new ContextRegistrar(msgContext, scm)); } /** * Registers all the classes associated with the specified CFC generated class * in the specified MessageContext's TypeMapping, for use by Axis. Assumes the * specified DynamicCacheClassLoader is the one responsible for the type/class * being generated and that it has already been linked/associated to dependent * DynamicCacheClassLoader instances. * * @param msgContext * @param dcl */ private void unregisterClasses(MessageContext msgContext, DynamicCacheClassLoader dcl) { String scm = ((HttpServletRequest) msgContext.getProperty(HTTPConstants.MC_HTTP_SERVLETREQUEST)).getScheme(); dcl.unregisterClasses(new ContextRegistrar(msgContext, scm)); } /** * Handles rendering the caught Exception in a manner suitable to the * invocation context. * * @param ex * Exception to handle. * @param session * cfSession for the current request */ private void processException(Exception ex, cfSession session) { ex.printStackTrace(); if (session != null && session.REQ.getMethod().equalsIgnoreCase("GET")) { if (ex instanceof cfmRunTimeException) ((cfmRunTimeException) ex).handleException(session); else new cfmRunTimeException(session, ex).handleException(session); } } /** * Resets the request message by writing it out completely (thereby resetting * the buffer read position). * * @param msgContext * MessageContext for the current request. * @throws AxisFault */ private void resetRequestStream(MessageContext msgContext) throws AxisFault { try { // For some reason part of the message has been read. To // avoid a "missing root document element" exception, we // reset the message stream by writing it out to a throw // away buffer. if (msgContext != null && msgContext.getRequestMessage() != null) msgContext.getRequestMessage().writeTo(new ByteArrayOutputStream()); } catch (IOException ex) { throw new AxisFault("Could not reset message request.", ex); } catch (SOAPException ex) { throw new AxisFault("Could not reset message request.", ex); } } /** * Returns a new cfSession from the specified MessageContext. * * @param msgContext * MessageContext for the current request. * @return a new cfSession from the specified MessageContext */ private cfSession getSession(MessageContext msgContext) { HttpServletRequest req = (HttpServletRequest) msgContext.getProperty(HTTPConstants.MC_HTTP_SERVLETREQUEST); HttpServletResponse res = (HttpServletResponse) msgContext.getProperty(HTTPConstants.MC_HTTP_SERVLETRESPONSE); ServletContext ctxt = req.getSession(true).getServletContext(); return new cfSession(req, res, ctxt); } /** * Returns the CFC name from the cfFile representing the component. * * @param svrFile * cfFile representing the component * @return CFC name from the cfFile representing the component */ protected String getComponentName(cfFile svrFile) { String compName = svrFile.getURI(); int ndx = compName.indexOf(':'); if (ndx != -1) { // This means we have a http://servername:port/path/to/cfm string // (or similar). Which means the cfSession couldn't map the request // to a real file. It's doubtful that we'll be able to find the // component this way, but at least we won't be looking for a // component that has the protocol, server name, port, etc. in it. compName = compName.substring(compName.indexOf('/', ndx)); } compName = compName.substring(1).replace('/', '.'); compName = compName.replace('\\', '.'); compName = compName.substring(0, compName.length() - 4); return compName; } }