package org.infinispan.commons.marshall.jboss; import static org.infinispan.commons.util.ReflectionUtil.EMPTY_CLASS_ARRAY; import static org.infinispan.commons.util.Util.EMPTY_OBJECT_ARRAY; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.io.ObjectInput; import java.io.ObjectOutput; import java.io.OutputStream; import java.io.Serializable; import java.lang.reflect.Method; import java.net.URL; import org.infinispan.commons.io.ByteBuffer; import org.infinispan.commons.io.ByteBufferImpl; import org.infinispan.commons.io.ExposedByteArrayOutputStream; import org.infinispan.commons.logging.Log; import org.infinispan.commons.logging.LogFactory; import org.infinispan.commons.marshall.AbstractMarshaller; import org.infinispan.commons.marshall.StreamingMarshaller; import org.infinispan.commons.util.concurrent.ConcurrentWeakKeyHashMap; import org.jboss.marshalling.ExceptionListener; import org.jboss.marshalling.Marshalling; import org.jboss.marshalling.MarshallingConfiguration; import org.jboss.marshalling.TraceInformation; import org.jboss.marshalling.Unmarshaller; /** * Common parent for both embedded and standalone JBoss Marshalling-based marshallers. * * @author Galder ZamarreƱo * @author Sanne Grinovero * @author Dan Berindei * @since 5.0 */ public abstract class AbstractJBossMarshaller extends AbstractMarshaller implements StreamingMarshaller { protected static final Log log = LogFactory.getLog(AbstractJBossMarshaller.class); protected static final boolean trace = log.isTraceEnabled(); protected static final JBossMarshallerFactory factory = new JBossMarshallerFactory(); protected static final int DEF_INSTANCE_COUNT = 16; protected static final int DEF_CLASS_COUNT = 8; private static final int PER_THREAD_REUSABLE_INSTANCES = 6; private static final int RIVER_INTERNAL_BUFFER = 512; protected final MarshallingConfiguration baseCfg; /** * Marshaller thread local. In non-internal marshaller usages, such as Java * Hot Rod client, this is a singleton shared by all so no urgent need for * static here. JBMAR clears pretty much any state during finish(), so no * urgent need to clear the thread local since it shouldn't be leaking. * It might take a long time to warmup and pre-initialize all needed instances! */ private final ConcurrentWeakKeyHashMap<Thread, PerThreadInstanceHolder> marshallerTL = new ConcurrentWeakKeyHashMap<Thread, PerThreadInstanceHolder>(); public AbstractJBossMarshaller() { // Class resolver now set when marshaller/unmarshaller will be created baseCfg = new MarshallingConfiguration(); baseCfg.setExceptionListener(new DebuggingExceptionListener()); baseCfg.setClassExternalizerFactory(new SerializeWithExtFactory()); baseCfg.setInstanceCount(DEF_INSTANCE_COUNT); baseCfg.setClassCount(DEF_CLASS_COUNT); baseCfg.setVersion(3); } @Override final public void objectToObjectStream(final Object obj, final ObjectOutput out) throws IOException { out.writeObject(obj); } @Override final protected ByteBuffer objectToBuffer(final Object o, final int estimatedSize) throws IOException { ExposedByteArrayOutputStream baos = new ExposedByteArrayOutputStream(estimatedSize); ObjectOutput marshaller = startObjectOutput(baos, false, estimatedSize); try { objectToObjectStream(o, marshaller); } finally { finishObjectOutput(marshaller); } return new ByteBufferImpl(baos.getRawBuffer(), 0, baos.size()); } @Override final public ObjectOutput startObjectOutput(final OutputStream os, final boolean isReentrant, final int estimatedSize) throws IOException { PerThreadInstanceHolder instanceHolder = getPerThreadInstanceHolder(); org.jboss.marshalling.Marshaller marshaller = instanceHolder.getMarshaller(estimatedSize); marshaller.start(Marshalling.createByteOutput(os)); return marshaller; } @Override final public void finishObjectOutput(final ObjectOutput oo) { try { if (trace) log.trace("Stop marshaller"); ((org.jboss.marshalling.Marshaller) oo).finish(); } catch (IOException ignored) { } } @Override final public Object objectFromByteBuffer(final byte[] buf, final int offset, final int length) throws IOException, ClassNotFoundException { ByteArrayInputStream is = new ByteArrayInputStream(buf, offset, length); ObjectInput unmarshaller = startObjectInput(is, false); Object o = null; try { o = objectFromObjectStream(unmarshaller); } finally { finishObjectInput(unmarshaller); } return o; } @Override final public ObjectInput startObjectInput(final InputStream is, final boolean isReentrant) throws IOException { PerThreadInstanceHolder instanceHolder = getPerThreadInstanceHolder(); Unmarshaller unmarshaller = instanceHolder.getUnmarshaller(); if (trace) log.tracef("Start unmarshaller after retrieving marshaller from %s", isReentrant ? "factory" : "thread local"); unmarshaller.start(Marshalling.createByteInput(is)); return unmarshaller; } @Override final public Object objectFromObjectStream(final ObjectInput in) throws IOException, ClassNotFoundException { return in.readObject(); } @Override final public void finishObjectInput(final ObjectInput oi) { try { if (trace) log.trace("Stop unmarshaller"); if (oi != null) ((Unmarshaller) oi).finish(); } catch (IOException ignored) { } } @Override public boolean isMarshallable(Object o) throws Exception { Class<?> clazz = o.getClass(); boolean containsMarshallable = marshallableTypeHints.isKnownMarshallable(clazz); if (containsMarshallable) { boolean marshallable = marshallableTypeHints.isMarshallable(clazz); if (trace) log.tracef("Marshallable type '%s' known and is marshallable=%b", clazz.getName(), marshallable); return marshallable; } else { if (isMarshallableCandidate(o)) { boolean isMarshallable = true; try { objectToBuffer(o); } catch (Exception e) { isMarshallable = false; throw e; } finally { marshallableTypeHints.markMarshallable(clazz, isMarshallable); } return isMarshallable; } return false; } } @Override public void start() { // No-op } @Override public void stop() { // Clear class cache marshallableTypeHints.clear(); marshallerTL.clear(); } protected boolean isMarshallableCandidate(Object o) { return o instanceof Serializable; } private PerThreadInstanceHolder getPerThreadInstanceHolder() { final Thread thread = Thread.currentThread(); PerThreadInstanceHolder holder = marshallerTL.get(thread); if (holder == null) { holder = new PerThreadInstanceHolder(baseCfg.clone()); if (marshallerTL.put(thread, holder) != null) { throw new IllegalStateException(); } } return holder; } protected static final class DebuggingExceptionListener implements ExceptionListener { private static final URL[] EMPTY_URLS = {}; @Override public void handleMarshallingException(final Throwable problem, final Object subject) { if (log.isDebugEnabled()) { TraceInformation.addUserInformation(problem, "toString = " + subject.toString()); } } @Override public void handleUnmarshallingException(final Throwable problem, final Class<?> subjectClass) { if (log.isDebugEnabled()) { StringBuilder builder = new StringBuilder(); ClassLoader cl = subjectClass.getClassLoader(); builder.append("classloader hierarchy:"); ClassLoader parent = cl; while (parent != null) { if (parent.equals(cl)) { builder.append("\n\t\t-> type classloader = ").append(parent); } else { builder.append("\n\t\t-> parent classloader = ").append(parent); } URL[] urls = getClassLoaderURLs(parent); if (urls != null) { for (URL u : urls) builder.append("\n\t\t->...").append(u); } parent = parent.getParent(); } TraceInformation.addUserInformation(problem, builder.toString()); } } @Override public void handleUnmarshallingException(Throwable problem) { // no-op } private static URL[] getClassLoaderURLs(final ClassLoader cl) { URL[] urls = EMPTY_URLS; try { Class<?> returnType = urls.getClass(); Class<?>[] parameterTypes = EMPTY_CLASS_ARRAY; Method getURLs = cl.getClass().getMethod("getURLs", parameterTypes); if (returnType.isAssignableFrom(getURLs.getReturnType())) { Object[] args = EMPTY_OBJECT_ARRAY; urls = (URL[]) getURLs.invoke(cl, args); } } catch (Exception ignore) { } return urls; } } private static final class PerThreadInstanceHolder implements RiverCloseListener { final MarshallingConfiguration configuration; final ExtendedRiverMarshaller[] reusableMarshaller = new ExtendedRiverMarshaller[PER_THREAD_REUSABLE_INSTANCES]; int availableMarshallerIndex = 0; final ExtendedRiverUnmarshaller[] reusableUnMarshaller = new ExtendedRiverUnmarshaller[PER_THREAD_REUSABLE_INSTANCES]; int availableUnMarshallerIndex = 0; PerThreadInstanceHolder(final MarshallingConfiguration threadDedicatedConfiguration) { this.configuration = threadDedicatedConfiguration; } Unmarshaller getUnmarshaller() throws IOException { //as opposing to getMarshaller(int), in this case we don't have a good hint about initial buffer sizing if (availableUnMarshallerIndex == PER_THREAD_REUSABLE_INSTANCES) { //we're above the pool threshold: make a throw-away-after usage Marshaller configuration.setBufferSize(512);//reset to default as it might be changed by getMarshaller return factory.createUnmarshaller(configuration); } else { ExtendedRiverUnmarshaller unMarshaller = reusableUnMarshaller[availableUnMarshallerIndex]; if (unMarshaller != null) { availableUnMarshallerIndex++; return unMarshaller; } else { configuration.setBufferSize(RIVER_INTERNAL_BUFFER);//reset to default as it might be changed by getMarshaller unMarshaller = factory.createUnmarshaller(configuration); unMarshaller.setCloseListener(this); reusableUnMarshaller[availableUnMarshallerIndex] = unMarshaller; availableUnMarshallerIndex++; return unMarshaller; } } } ExtendedRiverMarshaller getMarshaller(int estimatedSize) throws IOException { if (availableMarshallerIndex == PER_THREAD_REUSABLE_INSTANCES) { //we're above the pool threshold: make a throw-away-after usage Marshaller //setting the buffer as cheap as possible: configuration.setBufferSize(estimatedSize); return factory.createMarshaller(configuration); } else { ExtendedRiverMarshaller marshaller = reusableMarshaller[availableMarshallerIndex]; if (marshaller != null) { availableMarshallerIndex++; return marshaller; } else { //we're going to pool this one, make sure the buffer size is set to a reasonable value //as we might have changed it previously: configuration.setBufferSize(RIVER_INTERNAL_BUFFER); marshaller = factory.createMarshaller(configuration); marshaller.setCloseListener(this); reusableMarshaller[availableMarshallerIndex] = marshaller; availableMarshallerIndex++; return marshaller; } } } @Override public void closeMarshaller() { availableMarshallerIndex--; } @Override public void closeUnmarshaller() { availableUnMarshallerIndex--; } } }