View Javadoc
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  
19  import java.io.ByteArrayInputStream;
20  import java.io.ByteArrayOutputStream;
21  import java.io.IOException;
22  import java.io.ObjectInputStream;
23  import java.io.ObjectOutputStream;
24  import java.io.Serializable;
25  import java.lang.reflect.InvocationTargetException;
26  import java.lang.reflect.Method;
27  
28  import org.apache.commons.collections4.Factory;
29  import org.apache.commons.collections4.FunctorException;
30  
31  /**
32   * Factory implementation that creates a new instance each time based on a prototype.
33   * <p>
34   * <b>WARNING:</b> from v4.1 onwards {@link Factory} instances returned by
35   * {@link #prototypeFactory(Object)} will <b>not</b> be serializable anymore in order
36   * to prevent potential remote code execution exploits. Please refer to
37   * <a href="https://issues.apache.org/jira/browse/COLLECTIONS-580">COLLECTIONS-580</a>
38   * for more details.
39   * </p>
40   *
41   * @since 3.0
42   */
43  public class PrototypeFactory {
44  
45      /**
46       * Factory method that performs validation.
47       * <p>
48       * Creates a Factory that will return a clone of the same prototype object
49       * each time the factory is used. The prototype will be cloned using one of these
50       * techniques (in order):
51       * </p>
52       *
53       * <ul>
54       * <li>public clone method</li>
55       * <li>public copy constructor</li>
56       * <li>serialization clone</li>
57       * </ul>
58       *
59       * @param <T>  the type the factory creates
60       * @param prototype  the object to clone each time in the factory
61       * @return the <code>prototype</code> factory, or a {@link ConstantFactory#NULL_INSTANCE} if
62       * the {@code prototype} is {@code null}
63       * @throws IllegalArgumentException if the prototype cannot be cloned
64       */
65      @SuppressWarnings("unchecked")
66      public static <T> Factory<T> prototypeFactory(final T prototype) {
67          if (prototype == null) {
68              return ConstantFactory.<T>constantFactory(null);
69          }
70          try {
71              final Method method = prototype.getClass().getMethod("clone", (Class[]) null);
72              return new PrototypeCloneFactory<>(prototype, method);
73  
74          } catch (final NoSuchMethodException ex) {
75              try {
76                  prototype.getClass().getConstructor(new Class<?>[] { prototype.getClass() });
77                  return new InstantiateFactory<>(
78                      (Class<T>) prototype.getClass(),
79                      new Class<?>[] { prototype.getClass() },
80                      new Object[] { prototype });
81              } catch (final NoSuchMethodException ex2) {
82                  if (prototype instanceof Serializable) {
83                      return (Factory<T>) new PrototypeSerializationFactory<>((Serializable) prototype);
84                  }
85              }
86          }
87          throw new IllegalArgumentException("The prototype must be cloneable via a public clone method");
88      }
89  
90      /**
91       * Restricted constructor.
92       */
93      private PrototypeFactory() {
94          super();
95      }
96  
97      // PrototypeCloneFactory
98      //-----------------------------------------------------------------------
99      /**
100      * PrototypeCloneFactory creates objects by copying a prototype using the clone method.
101      */
102     static class PrototypeCloneFactory<T> implements Factory<T> {
103 
104         /** The object to clone each time */
105         private final T iPrototype;
106         /** The method used to clone */
107         private transient Method iCloneMethod;
108 
109         /**
110          * Constructor to store prototype.
111          */
112         private PrototypeCloneFactory(final T prototype, final Method method) {
113             super();
114             iPrototype = prototype;
115             iCloneMethod = method;
116         }
117 
118         /**
119          * Find the Clone method for the class specified.
120          */
121         private void findCloneMethod() {
122             try {
123                 iCloneMethod = iPrototype.getClass().getMethod("clone", (Class[]) null);
124             } catch (final NoSuchMethodException ex) {
125                 throw new IllegalArgumentException("PrototypeCloneFactory: The clone method must exist and be public ");
126             }
127         }
128 
129         /**
130          * Creates an object by calling the clone method.
131          *
132          * @return the new object
133          */
134         @Override
135         @SuppressWarnings("unchecked")
136         public T create() {
137             // needed for post-serialization
138             if (iCloneMethod == null) {
139                 findCloneMethod();
140             }
141 
142             try {
143                 return (T) iCloneMethod.invoke(iPrototype, (Object[]) null);
144             } catch (final IllegalAccessException ex) {
145                 throw new FunctorException("PrototypeCloneFactory: Clone method must be public", ex);
146             } catch (final InvocationTargetException ex) {
147                 throw new FunctorException("PrototypeCloneFactory: Clone method threw an exception", ex);
148             }
149         }
150     }
151 
152     // PrototypeSerializationFactory
153     //-----------------------------------------------------------------------
154     /**
155      * PrototypeSerializationFactory creates objects by cloning a prototype using serialization.
156      */
157     static class PrototypeSerializationFactory<T extends Serializable> implements Factory<T> {
158 
159         /** The object to clone via serialization each time */
160         private final T iPrototype;
161 
162         /**
163          * Constructor to store prototype
164          */
165         private PrototypeSerializationFactory(final T prototype) {
166             super();
167             iPrototype = prototype;
168         }
169 
170         /**
171          * Creates an object using serialization.
172          *
173          * @return the new object
174          */
175         @Override
176         @SuppressWarnings("unchecked")
177         public T create() {
178             final ByteArrayOutputStream baos = new ByteArrayOutputStream(512);
179             ByteArrayInputStream bais = null;
180             try {
181                 final ObjectOutputStream out = new ObjectOutputStream(baos);
182                 out.writeObject(iPrototype);
183 
184                 bais = new ByteArrayInputStream(baos.toByteArray());
185                 final ObjectInputStream in = new ObjectInputStream(bais);
186                 return (T) in.readObject();
187 
188             } catch (final ClassNotFoundException ex) {
189                 throw new FunctorException(ex);
190             } catch (final IOException ex) {
191                 throw new FunctorException(ex);
192             } finally {
193                 try {
194                     if (bais != null) {
195                         bais.close();
196                     }
197                 } catch (final IOException ex) { //NOPMD
198                     // ignore
199                 }
200                 try {
201                     baos.close();
202                 } catch (final IOException ex) { //NOPMD
203                     // ignore
204                 }
205             }
206         }
207     }
208 
209 }