Java tutorial
/* * | . _ . _ * |(|| )|(_) - A P2P Technology Based Grid Framework * / * --- * * Copyright (c) 2005 Alexander Papaspyrou * * * This file is part of kinjo. * * kinjo is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * kinjo 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 kinjo; if not, write to the Free Software * Foundation, 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ package com.wallabystreet.kinjo.common.transport.ws; import java.io.IOException; import java.io.InputStream; import java.net.URL; import java.util.Collection; import java.util.Enumeration; import java.util.InvalidPropertiesFormatException; import java.util.Map; import java.util.Properties; import java.util.StringTokenizer; import javax.wsdl.Definition; import javax.wsdl.Port; import javax.wsdl.Service; import javax.wsdl.WSDLException; import javax.wsdl.factory.WSDLFactory; import javax.wsdl.xml.WSDLReader; import javax.xml.namespace.QName; import javax.xml.parsers.ParserConfigurationException; import org.apache.axis.utils.XMLUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.w3c.dom.Element; import org.xml.sax.SAXException; import com.wallabystreet.kinjo.common.transport.protocol.MalformedTransportException; import com.wallabystreet.kinjo.common.transport.protocol.TransportDescriptor; /** * Describes a web service used in the kinjo Grid Management Framework. * * <p /> * A <code>ServiceDescriptor</code> consists of * <ul> * <li>a name, which corresponds to the <wsdl:service name""> value,</li> * <li>a package, which contains the service's configuration, interface, stub * and locator,</li> * <li>WS-related data, that is * <ol> * <li>a representation of the service's WSDL. The file containing it is * expected to be found in the service's package folder under the name * <i>service.wsdl,</i></li> * <li>a representation of the service's deployment WSDD. The file containing * it is expected to be found in the service's package folder under the name * <i>deploy.wsdd and</i></li> * <li>a representation of the service's undeployment WSDD. The file containing * it is expected to be found in the service's package folder under the name * <i>undeploy.wsdd,</i></li> * </ol> * </li> * <li>a property map containing additional configuration. It's file * representation expected to be found in the service's package folder under the * name <i>properties.xml</i>; the format must correspond to the <a * href="http://java.sun.com/dtd/properties.dtd">Java Property File DTD<a/>.</li> * </ul> * * <p /> * At the moment, only a single <code>Service</code> per WSDL and a single * <code>Port</code> per service are supported. Furthermore, the port name * <b>must</b> be equal to the service name. * * <p /> * Each service must support at least one transport. The supported transports * are specified in the service's configuration file; for this purpose, the * following property names are reserved: * <ul> * <li><code>transport</code>, which contains a colon(:)-delimited list of * supported transports</li> * <li><code>transport.</code><i>name</i>, which – for each <i>name</i> * specified in <code>transport</code> – contains the package name in * which an implementor of the <code>TransportDescriptor</code> interface * is expected under the name <code>Transport</code>. * </ul> * * <p /> * The reserved names are subject to change in future versions of this type. * Hence, when putting custom properties into the configuration map, one should * consider using a unique namespace for the property key name; for example, * Chapter 7.7 (Unique Package Names) from the <a * href="http://java.sun.com/docs/books/jls/">Java Language Specification</a>. * <p /> * <code>ServiceDescriptor</code>s cannot be instantiated directly; they must * be obtained from the * {@link com.wallabystreet.kinjo.common.transport.ws.ServiceDescriptorFactory}. * * @see com.wallabystreet.kinjo.common.transport.ws.ServiceDescriptorFactory * @see com.wallabystreet.kinjo.common.transport.protocol.TransportDescriptor * * @author Alexander Papaspyrou * @version $Revision$, $Date$ */ final public class ServiceDescriptor { /** * The default log facility for this class, using the <a * href="http://jakarta.apache.org/commons/logging/">Jakarta Commons Logging * API</a>. * * @see org.apache.commons.logging.Log * @see org.apache.commons.logging.LogFactory */ final private static Log log = LogFactory.getLog(ServiceDescriptor.class); /** * Contains the name of the described service, which is derived from the * <wsdl:service name=""> attribute in the service's WSDL description. */ private String name; /** * Contains the full qualified package name of the service implementation. * This is used as the path reference to load various configuration files. */ private String pkg; /** * Stores the WSDL definition of the described service in a * <code>Definition</code> model. * * @see javax.wsdl.Definition */ private Definition wsdl; /** * Stores the deployment WSDD of the described service in an * <code>Element</code> model. * * @see org.w3c.dom.Element */ private Element deploymentWSDD; /** * Stores the undeployment WSDD of the described service in an * <code>Element</code> model. * * @see org.w3c.dom.Element */ private Element undeploymentWSDD; /** * Holds configuration parameters of the described service. Currently, the * following keys are reserved: * <ul> * <li><code>transport</code></li> * <li><code>transport.</code><i>name</i> for the transport packages * ending with "name"</li> * </ul> * Please note that the values stored along with a key can be of arbitrary * type. * <p /> * Besides that, only <code>java.lang.String</code> values should be * retrieved using the * {@link java.util.Properties#getProperty(java.lang.String)} method; for * other types, the result will always be <code>null</code> (regardless of * the real value). * * @see java.lang.Properties */ private Properties properties; /** * Creates a new instance of this type, using the given parameters. * * @param name * The name of this service. Not portable. * @param pkg * The name of the package this service resides at. * @throws MalformedServiceException, * IIF an error occures during service creation. * * @deprecated Setting the service name by hand is not portable and * dangerous. */ ServiceDescriptor(String name, String pkg) throws MalformedServiceException { /* * FIXME: broken service name handling. * * At the moment, we allow to set the service descriptor's "name" field * to some obscure value, which -- by chance -- must correspond to the * name of the web service's only port. * * Alas, this is an evil hack (tm). * * The current solution to this problem is the deprecation of this * constructor. Users of the service descriptor class should use * getPort() to retrieve the port name of the service, which -- at least * at the moment is equal to the "name" field; internally we force * <wsdl:service name=""> == <wsdl:port name"">. * * Unfortunately, this simple approach does not address the * "single-port-per-service" problem. * * A clean implementation (and that's what we really want to do -- at * least at some point) would provide accessor methods for both service * and port names. This would also imply that each service may have * multiple ports. * * (Note that this does not seem to be necessary -- I have no idea how a * service interface has to look like to make java2wsdl map it to * multiple ports. Besides that, we then would have to change the * handling of service listener threads to consider multiple ports.) */ this.name = name; this.pkg = pkg; this.wsdl = this.loadWSDL(); this.deploymentWSDD = this.loadWSDD(true); this.undeploymentWSDD = this.loadWSDD(false); this.properties = this.loadProperties(); this.loadTransports(); } /** * Creates a new instance of this type, using the given parameters. * * @param pkg * The name of the package this service resides at. * @throws MalformedServiceException, * IIF an error occures during service creation. */ ServiceDescriptor(String pkg) throws MalformedServiceException { this.pkg = pkg; this.wsdl = this.loadWSDL(); this.deploymentWSDD = this.loadWSDD(true); this.undeploymentWSDD = this.loadWSDD(false); this.name = this.getServiceNameFromWSDL(); this.properties = this.loadProperties(); this.loadTransports(); } /** * Creates a new instance of this type, using the given parameters. * * @param pkg * The name of the package this service resides at. * @param props * A list of additional properties this service should store. * @throws MalformedServiceException, * IIF an error occures during service creation. */ ServiceDescriptor(String pkg, Properties props) throws MalformedServiceException { /* create standard service descriptor */ this(pkg); /* load additional properties */ Enumeration e = props.propertyNames(); while (e.hasMoreElements()) { String property = (String) e.nextElement(); this.properties.setProperty(property, props.getProperty(property)); } } /** * Accessor method for the <code>pkg</code> field. * * @return The <code>pkg</code> field of this type. */ public String getPackage() { return this.pkg; } /** * Accessor method for the <code>name</code> field. * * @return The <code>name</code> field of this type. */ public String getName() { return this.name; } /** * Returns the only WSDL port of the only WSDL service represented by this * service descriptor's <code>Definition</code>. * * <p /> * This method assumes that the name of the port is equal to the name of the * service. If this is not the case, it will return <code>null</code>. * * @return The service's WSDL port as a <code>Port</code> instance. * * @see javax.wsdl.Definition * @see javax.wsdl.Port */ public Port getPort() { Service s = this.wsdl.getService(new QName(this.name)); return s.getPort(s.getQName().toString()); } /** * Accessor method for the <code>wsdl</code> field. * * @return The <code>wsdl</code> field of this type. * * @see javax.wsdl.Definition */ public Definition getWSDL() { return this.wsdl; } /** * Accessor method for the <code>deploymentWSDD</code> field. * * @return The <code>deploymentWSDD</code> field of this type. */ public Element getDeploymentWSDD() { return this.deploymentWSDD; } /** * Accessor method for the <code>undeploymentWSDD</code> field. * * @return The <code>undeploymentWSDD</code> field of this type. * * @see org.w3c.dom.Element */ public Element getUndeploymentWSDD() { return this.undeploymentWSDD; } /** * Accessor method for the <code>properties</code> field. * * @return The <code>properties</code> field of this type. * * @see org.w3c.dom.Element */ Properties getProperties() { return this.properties; } /** * Mutator method for the <code>properties</code> field. * * @param properties * The new content of the <code>properties</code> field. */ void setProperties(Properties properties) { this.properties = properties; } /** * Returns – if existant – the property identified by the given * key. Otherwise, the result is <code>null</code>. * * @param key * The requested property's key. * @return The property identified by the given key, IIF the key exists; * <code>null</code>, otherwise. */ public Object getProperty(String key) { return this.properties.get(key); } /** * Retrieves the first (and only) <code>Service</code> from this service * description's WSDL and returns its name. * * @return The <code><wsdl:service name=""></code> value of the only * service in the WSDL. * @throws MalformedServiceException, * IIF the WSDL service definition's number of specified * services is <code>!= 1</code>. * * @see javax.wsdl.Service */ private String getServiceNameFromWSDL() throws MalformedServiceException { String name = null; Map m = this.wsdl.getServices(); if (m.size() != 1) { String msg = "only single-service WSDLs are supported: " + this.pkg; throw new MalformedServiceException(msg); } Collection c = m.values(); for (Object entry : c) { Map.Entry e = (Map.Entry) entry; Service s = (Service) e.getValue(); name = s.getQName().toString(); } return name; } /** * Loads the WSDL of the described service, assuming that the file is stored * in the service package's folder with the file name <i>service.wsdl</i>. * * @return The WSDL of this service as a <code>javax.wsdl.Definition</code> * instance. * @throws MalformedServiceException, * IIF the WSDL file is missing or invalid. * * @see javax.wsdl.Definition */ private Definition loadWSDL() throws MalformedServiceException { // constant for the WSDL file name final String WSDL_FILENAME = "service.wsdl"; Definition d = null; try { WSDLReader r = WSDLFactory.newInstance().newWSDLReader(); URL u = ClassLoader.getSystemResource(this.pkg.replace('.', '/') + "/" + WSDL_FILENAME); d = r.readWSDL(u.toString()); } catch (WSDLException e) { String msg = "failed to load the WSDL file"; if (log.isErrorEnabled()) { log.error(msg, e); } throw new MalformedServiceException(msg, e); } return d; } /** * Loads the WSDD of the described service, assuming that the file is stored * in the service package's folder with the file name * <ul> * <li><i>deploy.wsdd</i> for the deployment WSDD and</li> * <li><i>undeploy.wsdd</i> for the undeployment WSDD,</li> * </ul> * depending on the passed argument. * * @param deploy * Set this parameter to * <ul> * <li><code>true</code> for the <b>deployment</b> WSDD</li> * <li><code>false</code> for the <b>undeployment</b> WSDD</li> * </ul> * @return The (un)deployment WSDD of this service as an * <code>org.w3c.dom.Element</code> * @throws MalformedServiceException, * IIF the requested WSDD file is missing or invalid. * * @see org.w3c.dom.Element */ private Element loadWSDD(boolean deploy) throws MalformedServiceException { // constants for the (un)deployment WSDD file names final String DEPLOY_WSDD_FILENAME = "deploy.wsdd"; final String UNDEPLOY_WSDD_FILENAME = "undeploy.wsdd"; String wsddFileName = null; if (deploy) { wsddFileName = DEPLOY_WSDD_FILENAME; } else { wsddFileName = UNDEPLOY_WSDD_FILENAME; } InputStream is = null; try { is = ClassLoader.getSystemResourceAsStream(pkg.replace('.', '/') + "/" + wsddFileName); return XMLUtils.newDocument(is).getDocumentElement(); } catch (ParserConfigurationException e) { String msg = "parser instantiation failed"; if (log.isErrorEnabled()) { log.error(msg, e); } throw new MalformedServiceException(msg, e); } catch (SAXException e) { String msg = "deployment descriptor has errors: " + this.pkg; if (log.isErrorEnabled()) { log.error(msg, e); } throw new MalformedServiceException(msg, e); } catch (IOException e) { String msg = "failed to find " + wsddFileName + " for service \"" + this.pkg + "\""; if (log.isErrorEnabled()) { log.error(msg, e); } throw new MalformedServiceException(msg, e); } finally { try { is.close(); } catch (IOException e) { if (log.isWarnEnabled()) { log.warn("InputStream.close() failed", e); } } } } /** * Loads the configuration of the described service, assuming that the file * is stored in the service package's folder with the file name * <i>properties.xml</i>. It is expected that the file corresponds to the * <a href="http://java.sun.com/dtd/properties.dtd">Java Property File DTD<a/>. * * @return The configuration of this service as a * <code>java.util.Properties</code> instance. * @throws MalformedServiceException, * IIF the property file is missing or invalid. * * @see java.util.Properties */ private Properties loadProperties() throws MalformedServiceException { // constant for the property file name final String PROPERTIES_FILENAME = "properties.xml"; Properties p = new Properties(); try { p.loadFromXML( ClassLoader.getSystemResourceAsStream(this.pkg.replace('.', '/') + "/" + PROPERTIES_FILENAME)); /* * due to the stoopid implementation of * Properties#loadFromXML(InputStream), leading and trailing * whitespace of values is preserved; we have to fix this to prevent * ClassLoader#getSystemResource(String) and * ClassLoader#getSystemResourceAsStream(String) from wreckage. */ Enumeration e = p.propertyNames(); while (e.hasMoreElements()) { String key = (String) e.nextElement(); Object o = p.getProperty(key); if (o instanceof String) { String value = (String) o; p.setProperty(key, value.trim()); } } /* return the demoronized result */ return p; } catch (InvalidPropertiesFormatException e) { String msg = "invalid configuration file: " + this.pkg.replace('.', '/') + "/" + PROPERTIES_FILENAME; if (log.isErrorEnabled()) { log.error(msg, e); } throw new MalformedServiceException(msg, e); } catch (IOException e) { String msg = "could not open configuration file: " + this.pkg.replace('.', '/') + "/" + PROPERTIES_FILENAME; if (log.isErrorEnabled()) { log.error(msg, e); } throw new MalformedServiceException(msg, e); } } /** * Loads the transports for the described service, using the values in the * <code>properties</code> field. * <p /> * The methods expects to find the supported transports in the * <code>transport</code> property of the aforementioned field, delimited * by a colon (":"). Then, for every token found, it reads the * <code>transport.</code><i>token</i> property. Finally, the value * – which is expected to be a package name – is passed to the * {@link #loadTransportSpecificConfiguration(String)} method. * * @throws MalformedServiceException, * IIF * <ol> * <li>the "transport" property is missing, empty or invalid</li> * <li>none of the found transports could be configured * properly</li> * </ol> * * @see #loadTransportSpecificConfiguration(String) */ private void loadTransports() throws MalformedServiceException { // constant for the "transport" property token delimiter final String TRANSPORT_PROPERTY = "transport"; final String TRANSPORT_DELIMITER = ":"; /* get the "transport" property */ String supportedTransports = null; supportedTransports = this.properties.getProperty(TRANSPORT_PROPERTY); if (supportedTransports == null) { String msg = "property not found: \"" + this.properties.getProperty(TRANSPORT_PROPERTY) + "\""; if (log.isErrorEnabled()) { log.error(msg); } throw new MalformedServiceException(msg); } /* tokenize it using the delimiter constant defined above */ StringTokenizer t = new StringTokenizer(supportedTransports, TRANSPORT_DELIMITER); int transportCount = t.countTokens(); /* * check if we have at least one transport name (however, this doesn't * check whether the property is valid) */ if (transportCount < 1) { String msg = "no transports specified: " + this.pkg; if (log.isErrorEnabled()) { log.error(msg); } throw new MalformedServiceException(msg); } /* put the transport(s) into an array */ String[] transports = new String[t.countTokens()]; for (int i = 0; i < transports.length; i++) { transports[i] = t.nextToken(); } int validTransports = 0; for (String transport : transports) { String transportPackage = this.properties.getProperty(TRANSPORT_PROPERTY + "." + transport); if (transportPackage != null) { try { this.loadTransportSpecificConfiguration(transportPackage); validTransports++; } catch (MalformedTransportException e) { if (log.isWarnEnabled()) { String msg = "could not load transport-specific configuration for " + transportPackage; log.warn(msg, e); } } } else { if (log.isWarnEnabled()) { String msg = "no package specified for transport \"" + transport + "\""; log.warn(msg); } } } if (validTransports == 0) { String msg = "no transports available: " + this.pkg; if (log.isErrorEnabled()) { log.error(msg); } throw new MalformedServiceException(msg); } } /** * Loads the transport-specific service-related properties and stores them * in the service descriptor's <code>properties</code> field. * <p /> * This method expects a class * <ul> * <li>named <code>Transport</code> * <li>with a public default (no parameter, that is) constructor</li> * <li>which implements the <code>TransportDescriptor</code> interface * </ul> * in the package name specified in the argument. It creates a new instance * of this class, calls the * {@link com.wallabystreet.kinjo.common.transport.protocol.TransportDescriptor#getServiceRelatedProperties(String, String)} * method on it and copies all key/value pairs from the result into the * service descriptor's <code>properties</code> field. * * @param transportPackage * The package containing the <code>Transport</code> class used * for configuration. * @throws MalformedTransportException, * IIF the <code>Transport</code> class is missing, * inaccessible or incompatible. * * @see com.wallabystreet.kinjo.common.transport.protocol.TransportDescriptor * @see com.wallabystreet.kinjo.common.transport.protocol.TransportDescriptor#getServiceRelatedProperties(String, * String) */ private void loadTransportSpecificConfiguration(String transportPackage) throws MalformedTransportException { // constant for the transport class and package name final String transportPackageName = transportPackage.trim(); final String transportClassName = "Transport"; TransportDescriptor d = null; try { d = (TransportDescriptor) Class.forName(transportPackageName + "." + transportClassName).newInstance(); } catch (InstantiationException e) { String msg = "instantiation failed: " + transportPackageName + "." + transportClassName; if (log.isWarnEnabled()) { log.warn(msg, e); } throw new MalformedTransportException(msg); } catch (IllegalAccessException e) { String msg = "illegal access: " + transportPackageName + "." + transportClassName; if (log.isWarnEnabled()) { log.warn(msg, e); } throw new MalformedTransportException(msg); } catch (ClassNotFoundException e) { String msg = "class not found: " + transportPackageName + "." + transportClassName; if (log.isWarnEnabled()) { log.warn(msg, e); } throw new MalformedTransportException(msg); } Properties p = d.getServiceRelatedProperties(this.name, this.pkg); Enumeration e = p.propertyNames(); while (e.hasMoreElements()) { String property = (String) e.nextElement(); /* * since we don't know whether the returned properties are * java.lang.String instances, we have to use the generic * java.util.Hashtable methods to get and set the properties */ this.properties.put(property, p.get(property)); } } }