PrototypeFactory.java

  1. /*
  2.  * Licensed to the Apache Software Foundation (ASF) under one or more
  3.  * contributor license agreements.  See the NOTICE file distributed with
  4.  * this work for additional information regarding copyright ownership.
  5.  * The ASF licenses this file to You under the Apache License, Version 2.0
  6.  * (the "License"); you may not use this file except in compliance with
  7.  * the License.  You may obtain a copy of the License at
  8.  *
  9.  *      http://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  * Unless required by applicable law or agreed to in writing, software
  12.  * distributed under the License is distributed on an "AS IS" BASIS,
  13.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  * See the License for the specific language governing permissions and
  15.  * limitations under the License.
  16.  */
  17. package org.apache.commons.collections4.functors;

  18. import java.io.ByteArrayInputStream;
  19. import java.io.ByteArrayOutputStream;
  20. import java.io.IOException;
  21. import java.io.ObjectInputStream;
  22. import java.io.ObjectOutputStream;
  23. import java.io.Serializable;
  24. import java.lang.reflect.InvocationTargetException;
  25. import java.lang.reflect.Method;

  26. import org.apache.commons.collections4.Factory;
  27. import org.apache.commons.collections4.FunctorException;

  28. /**
  29.  * Factory implementation that creates a new instance each time based on a prototype.
  30.  * <p>
  31.  * <strong>WARNING:</strong> from v4.1 onwards {@link Factory} instances returned by
  32.  * {@link #prototypeFactory(Object)} will <strong>not</strong> be serializable anymore in order
  33.  * to prevent potential remote code execution exploits. Please refer to
  34.  * <a href="https://issues.apache.org/jira/browse/COLLECTIONS-580">COLLECTIONS-580</a>
  35.  * for more details.
  36.  * </p>
  37.  *
  38.  * @since 3.0
  39.  */
  40. public class PrototypeFactory {

  41.     /**
  42.      * PrototypeCloneFactory creates objects by copying a prototype using the clone method.
  43.      *
  44.      * @param <T> the type of results supplied by this supplier.
  45.      */
  46.     static class PrototypeCloneFactory<T> implements Factory<T> {

  47.         /** The object to clone each time */
  48.         private final T iPrototype;
  49.         /** The method used to clone */
  50.         private transient Method iCloneMethod;

  51.         /**
  52.          * Constructor to store prototype.
  53.          */
  54.         private PrototypeCloneFactory(final T prototype, final Method method) {
  55.             iPrototype = prototype;
  56.             iCloneMethod = method;
  57.         }

  58.         /**
  59.          * Creates an object by calling the clone method.
  60.          *
  61.          * @return the new object
  62.          */
  63.         @Override
  64.         @SuppressWarnings("unchecked")
  65.         public T create() {
  66.             // needed for post-serialization
  67.             if (iCloneMethod == null) {
  68.                 findCloneMethod();
  69.             }

  70.             try {
  71.                 return (T) iCloneMethod.invoke(iPrototype, (Object[]) null);
  72.             } catch (final IllegalAccessException ex) {
  73.                 throw new FunctorException("PrototypeCloneFactory: Clone method must be public", ex);
  74.             } catch (final InvocationTargetException ex) {
  75.                 throw new FunctorException("PrototypeCloneFactory: Clone method threw an exception", ex);
  76.             }
  77.         }

  78.         /**
  79.          * Find the Clone method for the class specified.
  80.          */
  81.         private void findCloneMethod() {
  82.             try {
  83.                 iCloneMethod = iPrototype.getClass().getMethod("clone", (Class[]) null);
  84.             } catch (final NoSuchMethodException ex) {
  85.                 throw new IllegalArgumentException("PrototypeCloneFactory: The clone method must exist and be public ");
  86.             }
  87.         }
  88.     }

  89.     /**
  90.      * PrototypeSerializationFactory creates objects by cloning a prototype using serialization.
  91.      *
  92.      * @param <T> the type of results supplied by this supplier.
  93.      */
  94.     static class PrototypeSerializationFactory<T extends Serializable> implements Factory<T> {

  95.         /** The object to clone via serialization each time */
  96.         private final T iPrototype;

  97.         /**
  98.          * Constructor to store prototype
  99.          */
  100.         private PrototypeSerializationFactory(final T prototype) {
  101.             iPrototype = prototype;
  102.         }

  103.         /**
  104.          * Creates an object using serialization.
  105.          *
  106.          * @return the new object
  107.          */
  108.         @Override
  109.         @SuppressWarnings("unchecked")
  110.         public T create() {
  111.             final ByteArrayOutputStream baos = new ByteArrayOutputStream(512);
  112.             ByteArrayInputStream bais = null;
  113.             try {
  114.                 final ObjectOutputStream out = new ObjectOutputStream(baos);
  115.                 out.writeObject(iPrototype);

  116.                 bais = new ByteArrayInputStream(baos.toByteArray());
  117.                 final ObjectInputStream in = new ObjectInputStream(bais);
  118.                 return (T) in.readObject();

  119.             } catch (final ClassNotFoundException | IOException ex) {
  120.                 throw new FunctorException(ex);
  121.             } finally {
  122.                 try {
  123.                     if (bais != null) {
  124.                         bais.close();
  125.                     }
  126.                 } catch (final IOException ex) { //NOPMD
  127.                     // ignore
  128.                 }
  129.                 try {
  130.                     baos.close();
  131.                 } catch (final IOException ex) { //NOPMD
  132.                     // ignore
  133.                 }
  134.             }
  135.         }
  136.     }

  137.     /**
  138.      * Factory method that performs validation.
  139.      * <p>
  140.      * Creates a Factory that will return a clone of the same prototype object
  141.      * each time the factory is used. The prototype will be cloned using one of these
  142.      * techniques (in order):
  143.      * </p>
  144.      *
  145.      * <ul>
  146.      * <li>public clone method</li>
  147.      * <li>public copy constructor</li>
  148.      * <li>serialization clone</li>
  149.      * </ul>
  150.      *
  151.      * @param <T>  the type the factory creates
  152.      * @param prototype  the object to clone each time in the factory
  153.      * @return the {@code prototype} factory, or a {@link ConstantFactory#NULL_INSTANCE} if
  154.      * the {@code prototype} is {@code null}
  155.      * @throws IllegalArgumentException if the prototype cannot be cloned
  156.      */
  157.     @SuppressWarnings("unchecked")
  158.     public static <T> Factory<T> prototypeFactory(final T prototype) {
  159.         if (prototype == null) {
  160.             return ConstantFactory.<T>constantFactory(null);
  161.         }
  162.         try {
  163.             final Method method = prototype.getClass().getMethod("clone", (Class[]) null);
  164.             return new PrototypeCloneFactory<>(prototype, method);

  165.         } catch (final NoSuchMethodException ex) {
  166.             try {
  167.                 prototype.getClass().getConstructor(prototype.getClass());
  168.                 return new InstantiateFactory<>(
  169.                     (Class<T>) prototype.getClass(),
  170.                     new Class<?>[] { prototype.getClass() },
  171.                     new Object[] { prototype });
  172.             } catch (final NoSuchMethodException ex2) {
  173.                 if (prototype instanceof Serializable) {
  174.                     return (Factory<T>) new PrototypeSerializationFactory<>((Serializable) prototype);
  175.                 }
  176.             }
  177.         }
  178.         throw new IllegalArgumentException("The prototype must be cloneable via a public clone method");
  179.     }

  180.     /**
  181.      * Restricted constructor.
  182.      */
  183.     private PrototypeFactory() {
  184.     }

  185. }