blob: 944dd388df638b3b27c9614d5fed94566943ea1f [file] [log] [blame]
/*
* Copyright (c) 1997, 2017, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code 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
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.sun.xml.internal.ws.transport.http.server;
import com.sun.istack.internal.Nullable;
import com.sun.xml.internal.stream.buffer.XMLStreamBufferResult;
import com.sun.xml.internal.ws.api.Component;
import com.sun.xml.internal.ws.api.WSBinding;
import com.sun.xml.internal.ws.api.BindingID;
import com.sun.xml.internal.ws.api.databinding.MetadataReader;
import com.sun.xml.internal.ws.api.message.Packet;
import com.sun.xml.internal.ws.binding.BindingImpl;
import com.sun.xml.internal.ws.api.server.*;
import com.sun.xml.internal.ws.server.EndpointFactory;
import com.sun.xml.internal.ws.server.ServerRtException;
import com.sun.xml.internal.ws.util.xml.XmlUtil;
import com.sun.xml.internal.ws.transport.http.HttpAdapterList;
import com.sun.xml.internal.ws.transport.http.HttpAdapter;
import com.sun.istack.internal.NotNull;
import java.net.MalformedURLException;
import javax.xml.namespace.QName;
import javax.xml.transform.Source;
import javax.xml.transform.TransformerException;
import javax.xml.ws.*;
import javax.xml.ws.spi.http.HttpContext;
import javax.xml.ws.wsaddressing.W3CEndpointReference;
import javax.xml.parsers.ParserConfigurationException;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Executor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import org.xml.sax.EntityResolver;
import org.xml.sax.SAXException;
import org.w3c.dom.Element;
/**
* Implements {@link Endpoint}.
*
* This class accumulates the information necessary to create
* {@link WSEndpoint}, and then when {@link #publish} method
* is called it will be created.
*
* This object also allows accumulated information to be retrieved.
*
* @author Jitendra Kotamraju
*/
public class EndpointImpl extends Endpoint {
private static final WebServicePermission ENDPOINT_PUBLISH_PERMISSION =
new WebServicePermission("publishEndpoint");
/**
* Once the service is published, this field will
* be set to the {@link HttpEndpoint} instance.
* <p/>
* But don't declare the type as {@link HttpEndpoint}
* to avoid static type dependency that cause the class loading to
* fail if the LW HTTP server doesn't exist.
*/
private Object actualEndpoint;
// information accumulated for creating WSEndpoint
private final WSBinding binding;
private @Nullable final Object implementor;
private List<Source> metadata;
private Executor executor;
private Map<String, Object> properties = Collections.emptyMap(); // always non-null
private boolean stopped;
private @Nullable EndpointContext endpointContext;
private @NotNull final Class<?> implClass;
private final Invoker invoker;
private Container container;
public EndpointImpl(@NotNull BindingID bindingId, @NotNull Object impl,
WebServiceFeature ... features) {
this(bindingId, impl, impl.getClass(),
InstanceResolver.createSingleton(impl).createInvoker(), features);
}
public EndpointImpl(@NotNull BindingID bindingId, @NotNull Class implClass,
javax.xml.ws.spi.Invoker invoker,
WebServiceFeature ... features) {
this(bindingId, null, implClass, new InvokerImpl(invoker), features);
}
private EndpointImpl(@NotNull BindingID bindingId, Object impl, @NotNull Class implClass,
Invoker invoker, WebServiceFeature ... features) {
binding = BindingImpl.create(bindingId, features);
this.implClass = implClass;
this.invoker = invoker;
this.implementor = impl;
}
/**
* Wraps an already created {@link WSEndpoint} into an {@link EndpointImpl},
* and immediately publishes it with the given context.
*
* @param wse created endpoint
* @param serverContext supported http context
* @deprecated This is a backdoor method. Don't use it unless you know what you are doing.
*/
public EndpointImpl(WSEndpoint wse, Object serverContext) {
this(wse, serverContext, null);
}
/**
* Wraps an already created {@link WSEndpoint} into an {@link EndpointImpl},
* and immediately publishes it with the given context.
*
* @param wse created endpoint
* @param serverContext supported http context
* @param ctxt endpoint context
* @deprecated This is a backdoor method. Don't use it unless you know what you are doing.
*/
public EndpointImpl(WSEndpoint wse, Object serverContext, EndpointContext ctxt) {
endpointContext = ctxt;
actualEndpoint = new HttpEndpoint(null, getAdapter(wse, ""));
((HttpEndpoint) actualEndpoint).publish(serverContext);
binding = wse.getBinding();
implementor = null; // this violates the semantics, but hey, this is a backdoor.
implClass = null;
invoker = null;
}
/**
* Wraps an already created {@link WSEndpoint} into an {@link EndpointImpl},
* and immediately publishes it with the given context.
*
* @param wse created endpoint
* @param address endpoint address
* @deprecated This is a backdoor method. Don't use it unless you know what you are doing.
*/
public EndpointImpl(WSEndpoint wse, String address) {
this(wse, address, null);
}
/**
* Wraps an already created {@link WSEndpoint} into an {@link EndpointImpl},
* and immediately publishes it with the given context.
*
* @param wse created endpoint
* @param address endpoint address
* @param ctxt endpoint context
* @deprecated This is a backdoor method. Don't use it unless you know what you are doing.
*/
public EndpointImpl(WSEndpoint wse, String address, EndpointContext ctxt) {
URL url;
try {
url = new URL(address);
} catch (MalformedURLException ex) {
throw new IllegalArgumentException("Cannot create URL for this address " + address);
}
if (!url.getProtocol().equals("http")) {
throw new IllegalArgumentException(url.getProtocol() + " protocol based address is not supported");
}
if (!url.getPath().startsWith("/")) {
throw new IllegalArgumentException("Incorrect WebService address=" + address +
". The address's path should start with /");
}
endpointContext = ctxt;
actualEndpoint = new HttpEndpoint(null, getAdapter(wse, url.getPath()));
((HttpEndpoint) actualEndpoint).publish(address);
binding = wse.getBinding();
implementor = null; // this violates the semantics, but hey, this is a backdoor.
implClass = null;
invoker = null;
}
@Override
public Binding getBinding() {
return binding;
}
@Override
public Object getImplementor() {
return implementor;
}
@Override
public void publish(String address) {
canPublish();
URL url;
try {
url = new URL(address);
} catch (MalformedURLException ex) {
throw new IllegalArgumentException("Cannot create URL for this address " + address);
}
if (!url.getProtocol().equals("http")) {
throw new IllegalArgumentException(url.getProtocol() + " protocol based address is not supported");
}
if (!url.getPath().startsWith("/")) {
throw new IllegalArgumentException("Incorrect WebService address=" + address +
". The address's path should start with /");
}
createEndpoint(url.getPath());
((HttpEndpoint) actualEndpoint).publish(address);
}
@Override
public void publish(Object serverContext) {
canPublish();
if (!com.sun.net.httpserver.HttpContext.class.isAssignableFrom(serverContext.getClass())) {
throw new IllegalArgumentException(serverContext.getClass() + " is not a supported context.");
}
createEndpoint(((com.sun.net.httpserver.HttpContext)serverContext).getPath());
((HttpEndpoint) actualEndpoint).publish(serverContext);
}
@Override
public void publish(HttpContext serverContext) {
canPublish();
createEndpoint(serverContext.getPath());
((HttpEndpoint) actualEndpoint).publish(serverContext);
}
@Override
public void stop() {
if (isPublished()) {
((HttpEndpoint) actualEndpoint).stop();
actualEndpoint = null;
stopped = true;
}
}
@Override
public boolean isPublished() {
return actualEndpoint != null;
}
@Override
public List<Source> getMetadata() {
return metadata;
}
@Override
public void setMetadata(java.util.List<Source> metadata) {
if (isPublished()) {
throw new IllegalStateException("Cannot set Metadata. Endpoint is already published");
}
this.metadata = metadata;
}
@Override
public Executor getExecutor() {
return executor;
}
@Override
public void setExecutor(Executor executor) {
this.executor = executor;
}
@Override
public Map<String, Object> getProperties() {
return new HashMap<>(properties);
}
@Override
public void setProperties(Map<String, Object> map) {
this.properties = new HashMap<>(map);
}
/*
* Checks the permission of "publishEndpoint" before accessing HTTP classes.
* Also it checks if there is an available HTTP server implementation.
*/
private void createEndpoint(String urlPattern) {
// Checks permission for "publishEndpoint"
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
sm.checkPermission(ENDPOINT_PUBLISH_PERMISSION);
}
// See if HttpServer implementation is available
try {
Class.forName("com.sun.net.httpserver.HttpServer");
} catch (Exception e) {
throw new UnsupportedOperationException("Couldn't load light weight http server", e);
}
container = getContainer();
MetadataReader metadataReader = EndpointFactory.getExternalMetadatReader(implClass, binding);
WSEndpoint wse = WSEndpoint.create(
implClass, true,
invoker,
getProperty(QName.class, Endpoint.WSDL_SERVICE),
getProperty(QName.class, Endpoint.WSDL_PORT),
container,
binding,
getPrimaryWsdl(metadataReader),
buildDocList(),
(EntityResolver) null,
false
);
// Don't load HttpEndpoint class before as it may load HttpServer classes
actualEndpoint = new HttpEndpoint(executor, getAdapter(wse, urlPattern));
}
private <T> T getProperty(Class<T> type, String key) {
Object o = properties.get(key);
if (o == null) return null;
if (type.isInstance(o))
return type.cast(o);
else
throw new IllegalArgumentException("Property " + key + " has to be of type " + type); // i18n
}
/**
* Convert metadata sources using identity transform. So that we can
* reuse the Source object multiple times.
*/
private List<SDDocumentSource> buildDocList() {
List<SDDocumentSource> r = new ArrayList<>();
if (metadata != null) {
for (Source source : metadata) {
try {
XMLStreamBufferResult xsbr = XmlUtil.identityTransform(source, new XMLStreamBufferResult());
String systemId = source.getSystemId();
r.add(SDDocumentSource.create(new URL(systemId), xsbr.getXMLStreamBuffer()));
} catch (TransformerException | IOException | SAXException | ParserConfigurationException te) {
throw new ServerRtException("server.rt.err", te);
}
}
}
return r;
}
/**
* Gets wsdl from @WebService or @WebServiceProvider
*/
private @Nullable SDDocumentSource getPrimaryWsdl(MetadataReader metadataReader) {
// Takes care of @WebService, @WebServiceProvider's wsdlLocation
EndpointFactory.verifyImplementorClass(implClass, metadataReader);
String wsdlLocation = EndpointFactory.getWsdlLocation(implClass, metadataReader);
if (wsdlLocation != null) {
return SDDocumentSource.create(implClass, wsdlLocation);
}
return null;
}
private void canPublish() {
if (isPublished()) {
throw new IllegalStateException(
"Cannot publish this endpoint. Endpoint has been already published.");
}
if (stopped) {
throw new IllegalStateException(
"Cannot publish this endpoint. Endpoint has been already stopped.");
}
}
@Override
public EndpointReference getEndpointReference(Element...referenceParameters) {
return getEndpointReference(W3CEndpointReference.class, referenceParameters);
}
@Override
public <T extends EndpointReference> T getEndpointReference(Class<T> clazz, Element...referenceParameters) {
if (!isPublished()) {
throw new WebServiceException("Endpoint is not published yet");
}
return ((HttpEndpoint)actualEndpoint).getEndpointReference(clazz,referenceParameters);
}
@Override
public void setEndpointContext(EndpointContext ctxt) {
this.endpointContext = ctxt;
}
private HttpAdapter getAdapter(WSEndpoint endpoint, String urlPattern) {
HttpAdapterList adapterList = null;
if (endpointContext != null) {
if (endpointContext instanceof Component) {
adapterList = ((Component) endpointContext).getSPI(HttpAdapterList.class);
}
if (adapterList == null) {
for(Endpoint e : endpointContext.getEndpoints()) {
if (e.isPublished() && e != this) {
adapterList = ((HttpEndpoint)(((EndpointImpl)e).actualEndpoint)).getAdapterOwner();
assert adapterList != null;
break;
}
}
}
}
if (adapterList == null) {
adapterList = new ServerAdapterList();
}
return adapterList.createAdapter("", urlPattern, endpoint);
}
/**
* Endpoints within a EndpointContext get the same container.
*/
private Container getContainer() {
if (endpointContext != null) {
if (endpointContext instanceof Component) {
Container c = ((Component) endpointContext).getSPI(Container.class);
if (c != null)
return c;
}
for(Endpoint e : endpointContext.getEndpoints()) {
if (e.isPublished() && e != this) {
return ((EndpointImpl)e).container;
}
}
}
return new ServerContainer();
}
private static class InvokerImpl extends Invoker {
private javax.xml.ws.spi.Invoker spiInvoker;
InvokerImpl(javax.xml.ws.spi.Invoker spiInvoker) {
this.spiInvoker = spiInvoker;
}
@Override
public void start(@NotNull WSWebServiceContext wsc, @NotNull WSEndpoint endpoint) {
try {
spiInvoker.inject(wsc);
} catch (IllegalAccessException | InvocationTargetException e) {
throw new WebServiceException(e);
}
}
@Override
public Object invoke(@NotNull Packet p, @NotNull Method m, @NotNull Object... args) throws InvocationTargetException, IllegalAccessException {
return spiInvoker.invoke(m, args);
}
}
}