com.sun.xml.ws.server.StatefulInstanceResolver Maven / Gradle / Ivy
Show all versions of jaxws-rt Show documentation
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright (c) 1997-2011 Oracle and/or its affiliates. All rights reserved.
*
* The contents of this file are subject to the terms of either the GNU
* General Public License Version 2 only ("GPL") or the Common Development
* and Distribution License("CDDL") (collectively, the "License"). You
* may not use this file except in compliance with the License. You can
* obtain a copy of the License at
* https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html
* or packager/legal/LICENSE.txt. See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each
* file and include the License file at packager/legal/LICENSE.txt.
*
* GPL Classpath Exception:
* Oracle designates this particular file as subject to the "Classpath"
* exception as provided by Oracle in the GPL Version 2 section of the License
* file that accompanied this code.
*
* Modifications:
* If applicable, add the following below the License Header, with the fields
* enclosed by brackets [] replaced by your own identifying information:
* "Portions Copyright [year] [name of copyright owner]"
*
* Contributor(s):
* If you wish your version of this file to be governed by only the CDDL or
* only the GPL Version 2, indicate your decision by adding "[Contributor]
* elects to include this software in this distribution under the [CDDL or GPL
* Version 2] license." If you don't indicate a single choice of license, a
* recipient has the option to distribute your version of this file under
* either the CDDL, the GPL Version 2 or to extend the choice of license to
* its licensees as provided above. However, if you add GPL Version 2 code
* and therefore, elected the GPL Version 2 license, then the option applies
* only if the new code is made subject to such option by the copyright
* holder.
*/
package com.sun.xml.ws.server;
import com.sun.istack.NotNull;
import com.sun.istack.Nullable;
import com.sun.xml.bind.marshaller.SAX2DOMEx;
import com.sun.xml.ws.api.ha.HighAvailabilityProvider;
import com.sun.xml.ws.api.ha.HighAvailabilityProvider.StoreType;
import com.sun.xml.ws.api.message.Header;
import com.sun.xml.ws.api.message.HeaderList;
import com.sun.xml.ws.api.message.Packet;
import com.sun.xml.ws.api.server.InstanceResolver;
import com.sun.xml.ws.api.server.WSEndpoint;
import com.sun.xml.ws.api.server.WSWebServiceContext;
import com.sun.xml.ws.developer.EPRRecipe;
import com.sun.xml.ws.developer.StatefulWebServiceManager;
import com.sun.xml.ws.resources.ServerMessages;
import com.sun.xml.ws.util.DOMUtil;
import com.sun.xml.ws.util.xml.XmlUtil;
import org.glassfish.ha.store.api.BackingStore;
import org.glassfish.ha.store.api.Storeable;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;
import javax.xml.namespace.QName;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.dom.DOMResult;
import javax.xml.transform.sax.SAXResult;
import javax.xml.ws.EndpointReference;
import javax.xml.ws.WebServiceContext;
import javax.xml.ws.WebServiceException;
import javax.xml.ws.wsaddressing.W3CEndpointReference;
import java.io.*;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.*;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* {@link InstanceResolver} that looks at JAX-WS cookie header to
* determine the instance to which a message will be routed.
*
*
* See {@link StatefulWebServiceManager} for more about user-level semantics.
*
* @author Kohsuke Kawaguchi
* @author Jitendra Kotamraju (added high availability)
*/
public final class StatefulInstanceResolver extends AbstractMultiInstanceResolver implements StatefulWebServiceManager {
/**
* This instance is used for serving messages that have no cookie
* or cookie value that the server doesn't recognize.
*/
private volatile @Nullable T fallback;
private HAMap haMap;
// time out control. 0=disabled
private volatile long timeoutMilliseconds = 0;
private volatile Callback timeoutCallback;
/**
* Timer that controls the instance time out. Lazily created.
*/
private volatile Timer timer;
// Application classloader(typically web app classloader), needed for
// deserialization of web service class
private final ClassLoader appCL;
private final boolean haEnabled;
// Used for {@link BackingStore#load()} and {@link BackingStore#save()}
// Keep this a static class, otherwise enclosed object will be pulled in
// during serialization
private static final class HAInstance implements Storeable {
transient @NotNull T instance;
private byte[] buf;
private long lastAccess = 0L;
private boolean isNew = false;
// unless the request gives a version somehow, this cannot be used
// to find out the dirty active cache entry
private long version = -1;
private long maxIdleTime;
public HAInstance() {
// Storeable objects require public no-arg constructor
}
public HAInstance(T instance, long timeout) {
this.instance = instance;
lastAccess = System.currentTimeMillis();
maxIdleTime = timeout;
}
public T getInstance(final ClassLoader cl) {
if (instance == null) {
try {
ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(buf)) {
@Override
protected Class> resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException {
Class> clazz = cl.loadClass(desc.getName());
if (clazz == null) {
clazz = super.resolveClass(desc);
}
return clazz;
}
};
instance = (T) in.readObject();
in.close();
} catch (Exception ioe) {
throw new WebServiceException(ioe);
}
}
return instance;
}
public long _storeable_getVersion() {
return version;
}
public void _storeable_setVersion(long version) {
this.version = version;
}
public long _storeable_getLastAccessTime() {
return lastAccess;
}
public void _storeable_setLastAccessTime(long time) {
lastAccess = time;
}
public long _storeable_getMaxIdleTime() {
return maxIdleTime;
}
public void _storeable_setMaxIdleTime(long time) {
maxIdleTime = time;
}
public String[] _storeable_getAttributeNames() {
return new String[0];
}
public boolean[] _storeable_getDirtyStatus() {
return new boolean[0];
}
public void _storeable_writeState(OutputStream os) throws IOException {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream boos = new ObjectOutputStream(bos);
boos.writeObject(instance);
boos.close();
this.buf = bos.toByteArray(); // convert instance to byte[]
ObjectOutputStream oos = new ObjectOutputStream(os);
oos.writeLong(version);
oos.writeLong(lastAccess);
oos.writeLong(maxIdleTime);
oos.writeBoolean(isNew);
oos.writeInt(buf.length);
oos.write(buf);
oos.close();
}
public void _storeable_readState(InputStream is) throws IOException {
ObjectInputStream ois = new ObjectInputStream(is);
version = ois.readLong();
lastAccess = ois.readLong();
maxIdleTime = ois.readLong();
isNew = ois.readBoolean();
int len = ois.readInt();
buf = new byte[len];
ois.readFully(buf);
ois.close();
}
}
/**
* Maintains the stateful service instance and its time-out timer.
*/
private final class Instance {
final @NotNull T instance;
TimerTask task;
public Instance(T instance) {
this.instance = instance;
}
/**
* Resets the timer.
*/
public synchronized void restartTimer() {
cancel();
if (timeoutMilliseconds == 0) return; // no timer
task = new TimerTask() {
public void run() {
try {
Callback cb = timeoutCallback;
if (cb != null) {
if (logger.isLoggable(Level.FINEST)) {
logger.finest("Invoking timeout callback for instance/timeouttask = [ " + instance + " / " + this + " ]");
}
cb.onTimeout(instance, StatefulInstanceResolver.this);
return;
}
// default operation is to unexport it.
unexport(instance);
} catch (Throwable e) {
// don't let an error in the code kill the timer thread
logger.log(Level.SEVERE, "time out handler failed", e);
}
}
};
timer.schedule(task, timeoutMilliseconds);
}
/**
* Cancels the timer.
*/
public synchronized void cancel() {
if (task != null) {
boolean result = task.cancel();
if (logger.isLoggable(Level.FINEST)) {
logger.finest("Timeout callback CANCELED for instance/timeouttask/cancel result = [ " + instance + " / " + this + " / " + result + " ]");
}
} else {
if (logger.isLoggable(Level.FINEST)) {
logger.finest("Timeout callback NOT CANCELED for instance = [ " + instance + " ]; task is null ...");
}
}
task = null;
}
}
@SuppressWarnings("unchecked")
public StatefulInstanceResolver(Class clazz) {
super(clazz);
appCL = clazz.getClassLoader();
boolean ha = false;
if (HighAvailabilityProvider.INSTANCE.isHaEnvironmentConfigured()) {
if (Serializable.class.isAssignableFrom(clazz)) {
logger.warning(clazz + " doesn't implement Serializable. High availibility is disabled i.e." +
"if a failover happens, stateful instance state is not failed over.");
ha = true;
}
}
haEnabled = ha;
}
@Override
public @NotNull T resolve(Packet request) {
HeaderList headers = request.getMessage().getHeaders();
Header header = headers.get(COOKIE_TAG, true);
String id = null;
if (header != null) {
// find the instance
id = header.getStringContent();
Instance o = haMap.get(id);
if (o != null) {
if (logger.isLoggable(Level.FINEST)) {
logger.finest("Restarting timer for objectId/Instance = [ " + id + " / " + o + " ]");
}
o.restartTimer();
return o.instance;
}
// huh? what is this ID?
logger.log(Level.INFO, "Request had an unrecognized object ID " + id);
} else {
logger.fine("No objectId header received");
}
// need to fallback
T fallback = this.fallback;
if (fallback != null)
return fallback;
if (id == null)
throw new WebServiceException(ServerMessages.STATEFUL_COOKIE_HEADER_REQUIRED(COOKIE_TAG));
else
throw new WebServiceException(ServerMessages.STATEFUL_COOKIE_HEADER_INCORRECT(COOKIE_TAG, id));
}
/*
* Changed stateful web service instance is pushed to backing store
* after the invocation of service.
*/
@Override
public void postInvoke(@NotNull Packet request, @NotNull T servant) {
haMap.put(servant);
}
@Override
public void start(WSWebServiceContext wsc, WSEndpoint endpoint) {
super.start(wsc, endpoint);
haMap = new HAMap();
if (endpoint.getBinding().getAddressingVersion() == null)
// addressing is not enabled.
throw new WebServiceException(ServerMessages.STATEFUL_REQURES_ADDRESSING(clazz));
// inject StatefulWebServiceManager.
for (Field field : clazz.getDeclaredFields()) {
if (field.getType() == StatefulWebServiceManager.class) {
if (!Modifier.isStatic(field.getModifiers()))
throw new WebServiceException(ServerMessages.STATIC_RESOURCE_INJECTION_ONLY(StatefulWebServiceManager.class, field));
new FieldInjectionPlan(field).inject(null, this);
}
}
for (Method method : clazz.getDeclaredMethods()) {
Class[] paramTypes = method.getParameterTypes();
if (paramTypes.length != 1)
continue; // not what we are looking for
if (paramTypes[0] == StatefulWebServiceManager.class) {
if (!Modifier.isStatic(method.getModifiers()))
throw new WebServiceException(ServerMessages.STATIC_RESOURCE_INJECTION_ONLY(StatefulWebServiceManager.class, method));
new MethodInjectionPlan(method).inject(null, this);
}
}
}
@Override
public void dispose() {
synchronized (haMap) {
for (Instance t : haMap.values()) {
t.cancel();
dispose(t.instance);
}
haMap.destroy();
}
if (fallback != null)
dispose(fallback);
fallback = null;
stopTimer();
}
@NotNull
public W3CEndpointReference export(T o) {
return export(W3CEndpointReference.class, o);
}
@NotNull
public EPR export(Class epr, T o) {
return export(epr, o, null);
}
public EPR export(Class epr, T o, EPRRecipe recipe) {
return export(epr, InvokerTube.getCurrentPacket(), o, recipe);
}
@NotNull
public EPR export(Class epr, WebServiceContext context, T o) {
if (context instanceof WSWebServiceContext) {
WSWebServiceContext wswsc = (WSWebServiceContext) context;
return export(epr, wswsc.getRequestPacket(), o);
}
throw new WebServiceException(ServerMessages.STATEFUL_INVALID_WEBSERVICE_CONTEXT(context));
}
@NotNull
public EPR export(Class adrsVer, @NotNull Packet currentRequest, T o) {
return export(adrsVer, currentRequest, o, null);
}
public EPR export(Class adrsVer, @NotNull Packet currentRequest, T o, EPRRecipe recipe) {
return export(adrsVer, currentRequest.webServiceContextDelegate.getEPRAddress(currentRequest, owner),
currentRequest.webServiceContextDelegate.getWSDLAddress(currentRequest, owner), o, recipe);
}
@NotNull
public EPR export(Class adrsVer, String endpointAddress, T o) {
return export(adrsVer, endpointAddress, null, o, null);
}
@NotNull
public EPR export(Class adrsVer, String endpointAddress, String wsdlAddress, T o, EPRRecipe recipe) {
if (endpointAddress == null)
throw new IllegalArgumentException("No address available");
String key = haMap.get(o);
if (key != null) return createEPR(key, adrsVer, endpointAddress, wsdlAddress, recipe);
// not exported yet.
synchronized (this) {
// double check now in the synchronization block to
// really make sure that we can export.
key = haMap.get(o);
if (key != null) return createEPR(key, adrsVer, endpointAddress, wsdlAddress, recipe);
if (o != null)
prepare(o);
key = UUID.randomUUID().toString();
Instance instance = new Instance(o);
if (logger.isLoggable(Level.FINEST)) {
logger.finest("Storing instance ID/Instance/Object/TimerTask = [ " + key + " / " + instance + " / " + instance.instance + " / " + instance.task + " ]");
}
haMap.put(key, instance);
if (timeoutMilliseconds != 0)
instance.restartTimer();
}
return createEPR(key, adrsVer, endpointAddress, wsdlAddress, recipe);
}
/*
* Creates an EPR that has the right key.
*/
private EPR createEPR(String key,
Class eprClass, String address, String wsdlAddress, EPRRecipe recipe) {
List referenceParameters = new ArrayList();
List metadata = new ArrayList();
Document doc = DOMUtil.createDom();
Element cookie =
doc.createElementNS(COOKIE_TAG.getNamespaceURI(),
COOKIE_TAG.getPrefix() + ":" + COOKIE_TAG.getLocalPart());
cookie.setAttributeNS("http://www.w3.org/2000/xmlns/", "xmlns:"
+ COOKIE_TAG.getPrefix(), COOKIE_TAG.getNamespaceURI());
cookie.setTextContent(key);
referenceParameters.add(cookie);
if (recipe != null) {
for (Header h : recipe.getReferenceParameters()) {
doc = DOMUtil.createDom();
SAX2DOMEx s2d = new SAX2DOMEx(doc);
try {
h.writeTo(s2d, XmlUtil.DRACONIAN_ERROR_HANDLER);
referenceParameters.add((Element) doc.getLastChild());
} catch (SAXException e) {
throw new WebServiceException("Unable to write EPR Reference parameters " + h, e);
}
}
Transformer t = XmlUtil.newTransformer();
for (Source s : recipe.getMetadata()) {
try {
DOMResult r = new DOMResult();
t.transform(s, r);
Document d = (Document) r.getNode();
metadata.add(d.getDocumentElement());
} catch (TransformerException e) {
throw new IllegalArgumentException("Unable to write EPR metadata " + s, e);
}
}
}
return
eprClass.cast(((WSEndpointImpl) owner).getEndpointReference(eprClass, address, wsdlAddress,
metadata, referenceParameters));
}
/*
private EPR createEPR(String key, Class eprClass, String address, EPRRecipe recipe) {
AddressingVersion adrsVer = AddressingVersion.fromSpecClass(eprClass);
try {
StreamWriterBufferCreator w = new StreamWriterBufferCreator();
w.writeStartDocument();
w.writeStartElement("wsa","EndpointReference", adrsVer.nsUri);
w.writeNamespace("wsa",adrsVer.nsUri);
w.writeStartElement("wsa","Address",adrsVer.nsUri);
w.writeCharacters(address);
w.writeEndElement();
w.writeStartElement("wsa","ReferenceParameters",adrsVer.nsUri);
w.writeStartElement(COOKIE_TAG.getPrefix(), COOKIE_TAG.getLocalPart(), COOKIE_TAG.getNamespaceURI());
w.writeCharacters(key);
w.writeEndElement();
if(recipe!=null) {
for (Header h : recipe.getReferenceParameters())
h.writeTo(w);
}
w.writeEndElement();
if(recipe!=null) {
List