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      // PrototypeCloneFactory
46      /**
47       * PrototypeCloneFactory creates objects by copying a prototype using the clone method.
48       */
49      static class PrototypeCloneFactory<T> implements Factory<T> {
50  
51          /** The object to clone each time */
52          private final T iPrototype;
53          /** The method used to clone */
54          private transient Method iCloneMethod;
55  
56          /**
57           * Constructor to store prototype.
58           */
59          private PrototypeCloneFactory(final T prototype, final Method method) {
60              iPrototype = prototype;
61              iCloneMethod = method;
62          }
63  
64          /**
65           * Creates an object by calling the clone method.
66           *
67           * @return the new object
68           */
69          @Override
70          @SuppressWarnings("unchecked")
71          public T create() {
72              // needed for post-serialization
73              if (iCloneMethod == null) {
74                  findCloneMethod();
75              }
76  
77              try {
78                  return (T) iCloneMethod.invoke(iPrototype, (Object[]) null);
79              } catch (final IllegalAccessException ex) {
80                  throw new FunctorException("PrototypeCloneFactory: Clone method must be public", ex);
81              } catch (final InvocationTargetException ex) {
82                  throw new FunctorException("PrototypeCloneFactory: Clone method threw an exception", ex);
83              }
84          }
85  
86          /**
87           * Find the Clone method for the class specified.
88           */
89          private void findCloneMethod() {
90              try {
91                  iCloneMethod = iPrototype.getClass().getMethod("clone", (Class[]) null);
92              } catch (final NoSuchMethodException ex) {
93                  throw new IllegalArgumentException("PrototypeCloneFactory: The clone method must exist and be public ");
94              }
95          }
96      }
97  
98      // PrototypeSerializationFactory
99      /**
100      * PrototypeSerializationFactory creates objects by cloning a prototype using serialization.
101      */
102     static class PrototypeSerializationFactory<T extends Serializable> implements Factory<T> {
103 
104         /** The object to clone via serialization each time */
105         private final T iPrototype;
106 
107         /**
108          * Constructor to store prototype
109          */
110         private PrototypeSerializationFactory(final T prototype) {
111             iPrototype = prototype;
112         }
113 
114         /**
115          * Creates an object using serialization.
116          *
117          * @return the new object
118          */
119         @Override
120         @SuppressWarnings("unchecked")
121         public T create() {
122             final ByteArrayOutputStream baos = new ByteArrayOutputStream(512);
123             ByteArrayInputStream bais = null;
124             try {
125                 final ObjectOutputStream out = new ObjectOutputStream(baos);
126                 out.writeObject(iPrototype);
127 
128                 bais = new ByteArrayInputStream(baos.toByteArray());
129                 final ObjectInputStream in = new ObjectInputStream(bais);
130                 return (T) in.readObject();
131 
132             } catch (final ClassNotFoundException | IOException ex) {
133                 throw new FunctorException(ex);
134             } finally {
135                 try {
136                     if (bais != null) {
137                         bais.close();
138                     }
139                 } catch (final IOException ex) { //NOPMD
140                     // ignore
141                 }
142                 try {
143                     baos.close();
144                 } catch (final IOException ex) { //NOPMD
145                     // ignore
146                 }
147             }
148         }
149     }
150 
151     /**
152      * Factory method that performs validation.
153      * <p>
154      * Creates a Factory that will return a clone of the same prototype object
155      * each time the factory is used. The prototype will be cloned using one of these
156      * techniques (in order):
157      * </p>
158      *
159      * <ul>
160      * <li>public clone method</li>
161      * <li>public copy constructor</li>
162      * <li>serialization clone</li>
163      * </ul>
164      *
165      * @param <T>  the type the factory creates
166      * @param prototype  the object to clone each time in the factory
167      * @return the {@code prototype} factory, or a {@link ConstantFactory#NULL_INSTANCE} if
168      * the {@code prototype} is {@code null}
169      * @throws IllegalArgumentException if the prototype cannot be cloned
170      */
171     @SuppressWarnings("unchecked")
172     public static <T> Factory<T> prototypeFactory(final T prototype) {
173         if (prototype == null) {
174             return ConstantFactory.<T>constantFactory(null);
175         }
176         try {
177             final Method method = prototype.getClass().getMethod("clone", (Class[]) null);
178             return new PrototypeCloneFactory<>(prototype, method);
179 
180         } catch (final NoSuchMethodException ex) {
181             try {
182                 prototype.getClass().getConstructor(prototype.getClass());
183                 return new InstantiateFactory<>(
184                     (Class<T>) prototype.getClass(),
185                     new Class<?>[] { prototype.getClass() },
186                     new Object[] { prototype });
187             } catch (final NoSuchMethodException ex2) {
188                 if (prototype instanceof Serializable) {
189                     return (Factory<T>) new PrototypeSerializationFactory<>((Serializable) prototype);
190                 }
191             }
192         }
193         throw new IllegalArgumentException("The prototype must be cloneable via a public clone method");
194     }
195 
196     /**
197      * Restricted constructor.
198      */
199     private PrototypeFactory() {
200     }
201 
202 }