blob: a516de3cc8481ec0191ee77fd0f5f52168d29592 [file] [log] [blame]
/*
* Copyright 2004,2005 The Apache Software Foundation.
* Copyright 2006 International Business Machines Corp.
*
* 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.axis2.jaxws.description;
import java.net.URL;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Map;
import javax.xml.namespace.QName;
import javax.xml.ws.WebServiceException;
import javax.jws.WebService;
import javax.wsdl.Definition;
import javax.wsdl.Port;
import javax.wsdl.Service;
import javax.wsdl.WSDLException;
import org.apache.axis2.AxisFault;
import org.apache.axis2.client.ServiceClient;
import org.apache.axis2.context.ConfigurationContext;
import org.apache.axis2.deployment.DeploymentException;
import org.apache.axis2.description.AxisService;
import org.apache.axis2.description.OutInAxisOperation;
import org.apache.axis2.description.OutOnlyAxisOperation;
import org.apache.axis2.description.Parameter;
import org.apache.axis2.description.RobustOutOnlyAxisOperation;
import org.apache.axis2.description.WSDL11ToAllAxisServicesBuilder;
import org.apache.axis2.description.WSDL11ToAxisServiceBuilder;
import org.apache.axis2.engine.AbstractDispatcher;
import org.apache.axis2.jaxws.ClientConfigurationFactory;
import org.apache.axis2.jaxws.ExceptionFactory;
import org.apache.axis2.jaxws.i18n.Messages;
import org.apache.axis2.jaxws.util.WSDL4JWrapper;
import org.apache.axis2.jaxws.util.WSDLWrapper;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* The JAX-WS Service metadata and root of the JAX-WS Descritpion hierachy.
*/
/*
Working-design information.
Description hierachy
ServiceDescription
EndpointDescription[]
EndpointInterfaceDescription
OperationDescription[]
ParameterDescription Input[]
ParameterDescription Output[]
FaultDescription
ServiceDescription:
Corresponds to the generated Service class [client]; TBD [server]
Java Name: Generated service class or null if dynamically configured service [client]; null [server]
Axis2 Delegate: None (AxisService corresponds to a port which corresponds to the EndpointDescription)
JSR-181 Annotations:
@HandlerChain(file, name) [per JAXWS p. 105] Affects all proxies and dispatches created using any port on this service
TBD
WSDL Elements:
<service
JAX-WS Annotations:
@WebServiceClient(name, targetNamespace, wsdlLocation)
@WebEndpoint(name) This is specified on the getPortName() methods on the service
TBD
Properties available to JAXWS runtime:
getEndpointDescription(QName port) Needed by HandlerResolver
TBD
*/
/**
* ServiceDescription contains the metadata (e.g. WSDL, annotations) relating to a Service on both the
* service-requester (aka client) and service-provider (aka server) sides.
*
*/
public class ServiceDescription {
private ClientConfigurationFactory clientConfigFactory;
private ConfigurationContext configContext;
private URL wsdlURL;
private QName serviceQName;
// Only ONE of the following will be set in a ServiceDescription, depending on whether this Description
// was created from a service-requester or service-provider flow.
private Class serviceClass; // A service-requester generated service or generic service class
// TODO: Possibly remove Definition and delegate to the Defn on the AxisSerivce set as a paramater by WSDLtoAxisServicBuilder?
private WSDLWrapper wsdlWrapper;
private Hashtable<QName, EndpointDescription> endpointDescriptions = new Hashtable<QName, EndpointDescription>();
private static final Log log = LogFactory.getLog(ServiceDescription.class);
/**
* This is (currently) the client-side-only constructor
* Construct a service description hierachy based on WSDL (may be null), the Service class, and
* a service QName.
*
* @param wsdlURL The WSDL file (this may be null).
* @param serviceQName The name of the service in the WSDL. This can not be null since a
* javax.xml.ws.Service can not be created with a null service QName.
* @param serviceClass The JAX-WS service class. This could be an instance of
* javax.xml.ws.Service or a generated service subclass thereof. This will not be null.
*/
ServiceDescription(URL wsdlURL, QName serviceQName, Class serviceClass) {
if (serviceQName == null) {
throw ExceptionFactory.makeWebServiceException(Messages.getMessage("serviceDescErr0"));
}
if (serviceClass == null) {
throw ExceptionFactory.makeWebServiceException(Messages.getMessage("serviceDescErr1", "null"));
}
if (!javax.xml.ws.Service.class.isAssignableFrom(serviceClass)) {
throw ExceptionFactory.makeWebServiceException(Messages.getMessage("serviceDescErr1", serviceClass.getName()));
}
this.wsdlURL = wsdlURL;
// TODO: The serviceQName needs to be verified between the argument/WSDL/Annotation
this.serviceQName = serviceQName;
this.serviceClass = serviceClass;
setupWsdlDefinition();
}
/**
* This is (currently) the service-provider-side-only constructor.
* Create a service Description based on a service implementation class
*
* @param serviceImplClass
*/
// NOTE: Taking an axisService on the call is TEMPORARY! Eventually the AxisService should be constructed
// based on the annotations in the ServiceImpl class.
// TODO: Remove axisService as paramater when the AxisService can be constructed from the annotations
ServiceDescription(Class serviceImplClass, AxisService axisService) {
// Create the EndpointDescription hierachy from the service impl annotations; Since the PortQName is null,
// it will be set to the annotation value.
EndpointDescription endpointDescription = new EndpointDescription(serviceImplClass, null, axisService, this);
addEndpointDescription(endpointDescription);
// TODO: The ServiceQName instance variable should be set based on annotation or default
}
/*=======================================================================*/
/*=======================================================================*/
// START of public accessor methods
/**
* Update or create an EndpointDescription. Updates to existing
* EndpointDescriptons will be based on the SEI class and its annotations. Both declared
* ports and dynamic ports can be updated. A declared port is one that is defined (e.g. in WSDL or
* via annotations); a dyamic port is one that is not defined (e.g. not via WSDL or annotations) and
* has been added via Serivce.addPort.
*
* Notes on how an EndpointDescription can be updated or created:
* 1) Service.createDispatch can create a Dispatch client for either a declared or dynamic port
* 2) Note that creating a Dispatch does not associate an SEI with an endpoint
* 3) Service.getPort will associate an SEI with a port
* 4) A getPort on an endpoint which was originally created for a Distpatch will update that
* EndpointDescription with the SEI provided on the getPort
* 5) Service.getPort can not be called on a dynamic port (per the JAX-WS spec)
* 6) Service.addPort can not be called for a declared port
*
* @param sei
* This will be non-null if the update is of type GET_PORT; it
* will be null if the update is ADD_PORT or CREATE_DISPATCH
* @param portQName
* @param updateType
* Indicates what is causing the update GET_PORT is an attempt to
* get a declared SEI-based port ADD_PORT is an attempt to add a
* previously non-existent dynamic port CREATE_DISPATCH is an
* attempt to create a Dispatch-based client to either a declared
* port or a pre-existing dynamic port.
*/
public enum UpdateType {GET_PORT, ADD_PORT, CREATE_DISPATCH}
public void updateEndpointDescription(Class sei, QName portQName, UpdateType updateType) {
// TODO: Add support: portQName can be null when called from Service.getPort(Class)
if (portQName == null) {
throw new UnsupportedOperationException("ServiceDescription.updateEndpointDescription null PortQName not supported");
}
EndpointDescription endpointDescription = getEndpointDescription(portQName);
boolean isPortDeclared = isPortDeclared(portQName);
switch (updateType) {
case ADD_PORT:
// Port must NOT be declared (e.g. can not already exist in WSDL)
// If an EndpointDesc doesn't exist; create it as long as it doesn't exist in the WSDL
// TODO: This test can be simplified once isPortDeclared(QName) understands annotations and WSDL as ways to declare a port.
if (getWSDLWrapper() != null && isPortDeclared) {
// TODO: RAS & NLS
throw ExceptionFactory.makeWebServiceException("ServiceDescription.updateEndpointDescription: Can not do an addPort with a PortQN that exists in the WSDL. PortQN: " + portQName.toString());
}
else if (endpointDescription == null) {
// Use the SEI Class and its annotations to finish creating the Description hierachy. Note that EndpointInterface, Operations, Parameters, etc.
// are not created for dynamic ports. It would be an error to later do a getPort against a dynamic port (per the JAX-WS spec)
endpointDescription = new EndpointDescription(sei, portQName, true, this);
addEndpointDescription(endpointDescription);
}
else {
// All error check above passed, the EndpointDescription already exists and needs no updating
}
break;
case GET_PORT:
// If an endpointDesc doesn't exist, and the port exists in the WSDL, create it
// If an endpointDesc already exists and has an associated SEI already, make sure they match
// If an endpointDesc already exists and was created for Dispatch (no SEI), update that with the SEI provided on the getPort
// Port must be declared (e.g. in WSDL or via annotations)
// TODO: Once isPortDeclared understands annotations and not just WSDL, the 2nd part of this check can possibly be removed.
// Although consider the check below that updates an existing EndpointDescritpion with an SEI.
if (!isPortDeclared || (endpointDescription != null && endpointDescription.isDynamicPort())) {
// This guards against the case where an addPort was done previously and now a getPort is done on it.
// TODO: RAS & NLS
throw ExceptionFactory.makeWebServiceException("ServiceDescription.updateEndpointDescription: Can not do a getPort on a port added via addPort(). PortQN: " + portQName.toString());
}
else if (sei == null) {
// TODO: RAS & NLS
throw ExceptionFactory.makeWebServiceException("ServiceDescription.updateEndpointDescription: Can not do a getPort with a null SEI. PortQN: " + portQName.toString());
}
else if (endpointDescription == null) {
// Use the SEI Class and its annotations to finish creating the Description hierachy: Endpoint, EndpointInterface, Operations, Parameters, etc.
// TODO: Need to create the Axis Description objects after we have all the config info (i.e. from this SEI)
endpointDescription = new EndpointDescription(sei, portQName, this);
addEndpointDescription(endpointDescription);
}
else if (getEndpointSEI(portQName) == null && !endpointDescription.isDynamicPort()) {
// Existing endpointDesc from a declared port needs to be updated with an SEI
// Note that an EndpointDescritption created from an addPort (i.e. a dynamic port) can not do this.
endpointDescription.updateWithSEI(sei);
}
else if (getEndpointSEI(portQName) != sei) {
// TODO: RAS & NLS
throw ExceptionFactory.makeWebServiceException("ServiceDescription.updateEndpointDescription: Can't do a getPort() specifiying a different SEI than the previous getPort(). PortQN: "
+ portQName + "; current SEI: " + sei + "; previous SEI: " + getEndpointSEI(portQName));
}
else {
// All error check above passed, the EndpointDescription already exists and needs no updating
}
break;
case CREATE_DISPATCH:
// Port may or may not exist in WSDL.
// If an endpointDesc doesn't exist and it is in the WSDL, it can be created
// Otherwise, it is an error.
if (endpointDescription != null) {
// The EndpoingDescription already exists; nothing needs to be done
}
else if (sei != null) {
// The Dispatch should not have an SEI associated with it on the update call.
// REVIEW: Is this a valid check?
throw ExceptionFactory.makeWebServiceException("ServiceDescription.updateEndpointDescription: Can not specify an SEI when creating a Dispatch. PortQN: " + portQName);
}
else if (isPortDeclared) {
// EndpointDescription doesn't exist and this is a declared Port, so create one
// Use the SEI Class and its annotations to finish creating the Description hierachy. Note that EndpointInterface, Operations, Parameters, etc.
// are not created for Dipsatch-based ports, but might be updated later if a getPort is done against the same declared port.
// TODO: Need to create the Axis Description objects after we have all the config info (i.e. from this SEI)
endpointDescription = new EndpointDescription(sei, portQName, this);
addEndpointDescription(endpointDescription);
}
else {
// The port is not a declared port and it does not have an EndpointDescription, meaning an addPort has not been done for it
// This is an error.
// TODO: RAS & NLS
throw ExceptionFactory.makeWebServiceException("ServiceDescription.updateEndpointDescription: Attempt to create a Dispatch for a non-existant Dynamic por PortQN: " + portQName);
}
break;
}
}
private Class getEndpointSEI(QName portQName) {
Class endpointSEI = null;
EndpointInterfaceDescription endpointInterfaceDesc = getEndpointDescription(portQName).getEndpointInterfaceDescription();
if (endpointInterfaceDesc != null ) {
endpointSEI = endpointInterfaceDesc.getSEIClass();
}
return endpointSEI;
}
private boolean isPortDeclared(QName portQName) {
// TODO: This needs to account for declaration of the port via annotations in addition to just WSDL
// TODO: Add logic to check the portQN namespace against the WSDL Definition NS
boolean portIsDeclared = false;
if (getWSDLWrapper() != null) {
Definition wsdlDefn = getWSDLWrapper().getDefinition();
Service wsdlService = wsdlDefn.getService(serviceQName);
Port wsdlPort = wsdlService.getPort(portQName.getLocalPart());
portIsDeclared = (wsdlPort != null);
}
else {
// TODO: Add logic to determine if port is declared via annotations when no WSDL is present. For now, we have to assume it is declared
// so getPort(...) and createDispatch(...) calls work when there is no WSDL.
portIsDeclared = true;
}
return portIsDeclared;
}
public EndpointDescription[] getEndpointDescriptions() {
return endpointDescriptions.values().toArray(new EndpointDescription[0]);
}
public EndpointDescription getEndpointDescription(QName portQName) {
return endpointDescriptions.get(portQName);
}
/**
* Return the EndpointDescriptions corresponding to the SEI class. Note that
* Dispatch endpoints will never be returned because they do not have an associated SEI.
* @param seiClass
* @return
*/
public EndpointDescription[] getEndpointDescription(Class seiClass) {
EndpointDescription[] returnEndpointDesc = null;
ArrayList<EndpointDescription> matchingEndpoints = new ArrayList<EndpointDescription>();
Enumeration<EndpointDescription> endpointEnumeration = endpointDescriptions.elements();
while (endpointEnumeration.hasMoreElements()) {
EndpointDescription endpointDescription = endpointEnumeration.nextElement();
EndpointInterfaceDescription endpointInterfaceDesc = endpointDescription.getEndpointInterfaceDescription();
// Note that Dispatch endpoints will not have an endpointInterface because the do not have an associated SEI
if (endpointInterfaceDesc != null) {
Class endpointSEIClass = endpointInterfaceDesc.getSEIClass();
if (endpointSEIClass != null && endpointSEIClass.equals(seiClass)) {
matchingEndpoints.add(endpointDescription);
}
}
}
if (matchingEndpoints.size() > 0) {
returnEndpointDesc = matchingEndpoints.toArray(new EndpointDescription[0]);
}
return returnEndpointDesc;
}
// END of public accessor methods
/*=======================================================================*/
/*=======================================================================*/
private void addEndpointDescription(EndpointDescription endpoint) {
endpointDescriptions.put(endpoint.getPortQName(), endpoint);
}
private void setupWsdlDefinition() {
// Note that there may be no WSDL provided, for example when called from
// Service.create(QName serviceName).
if (wsdlURL != null) {
try {
wsdlWrapper = new WSDL4JWrapper(this.wsdlURL);
} catch (WSDLException e) {
throw ExceptionFactory.makeWebServiceException(Messages.getMessage("wsdlException", e.getMessage()), e);
}
}
}
// TODO: Remove these and replace with appropraite get* methods for WSDL information
public WSDLWrapper getWSDLWrapper() {
return wsdlWrapper;
}
public URL getWSDLLocation() {
return wsdlURL;
}
public ConfigurationContext getAxisConfigContext() {
if (configContext == null) {
configContext = getClientConfigurationFactory().getClientConfigurationContext();
}
return configContext;
}
ClientConfigurationFactory getClientConfigurationFactory() {
if (clientConfigFactory == null ) {
clientConfigFactory = ClientConfigurationFactory.newInstance();
}
return clientConfigFactory;
}
public ServiceClient getServiceClient(QName portQName) {
// TODO: RAS if no portQName found
return getEndpointDescription(portQName).getServiceClient();
}
public QName getServiceQName() {
return serviceQName;
}
}