View Javadoc

1   package com.atlassian.scheduler.core.util;
2   
3   import java.io.ByteArrayInputStream;
4   import java.io.IOException;
5   import java.io.InputStream;
6   import java.io.ObjectInputStream;
7   import java.io.ObjectStreamClass;
8   
9   import static com.atlassian.util.concurrent.Assertions.notNull;
10  
11  /**
12   * An object input stream that uses the provided {@code ClassLoader} in preference
13   * to any other.
14   */
15  public class ClassLoaderAwareObjectInputStream extends ObjectInputStream {
16      private final ClassLoader classLoader;
17  
18      /**
19       * @param classLoader the class loader to use for loading classes
20       * @param parameters  the byte array to be deserialized; must not be {@code null} and is not guarded against
21       *                    modification for efficiency reasons
22       * @throws IOException if {@link ObjectInputStream#ObjectInputStream(InputStream)} itself does
23       */
24      public ClassLoaderAwareObjectInputStream(ClassLoader classLoader, byte[] parameters) throws IOException {
25          super(new ByteArrayInputStream(notNull("parameters", parameters)));
26          this.classLoader = notNull("classLoader", classLoader);
27      }
28  
29      @Override
30      protected Class<?> resolveClass(final ObjectStreamClass desc) throws IOException, ClassNotFoundException {
31          try {
32              return Class.forName(desc.getName(), false, classLoader);
33          } catch (ClassNotFoundException originalEx) {
34              try {
35                  // Desperation.  Maybe we shouldn't even be trying?  If it succeeds, we may be getting the
36                  // "wrong" class anyway because ObjectInputStream selects the ClassLoader from the "nearest"
37                  // one it finds on the Thread's stack that is not the system ClassLoader.  This will *probably*
38                  // be the webapp's ClassLoader, so we are *probably* ok.  If this becomes a problem and we
39                  // decide to drop this delegation, then we should reproduce the logic that it has for mapping
40                  // primitive types; e.g., "int" -> Integer.TYPE
41                  return super.resolveClass(desc);
42              } catch (ClassNotFoundException ignoredEx) {
43                  // Prefer the exception that we got from our own ClassLoader to avoid confusion, since that is
44                  // the one we are expected to be using.
45                  throw originalEx;
46              }
47          }
48      }
49  }