001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * https://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.commons.collections4.functors; 018 019import java.io.ByteArrayInputStream; 020import java.io.ByteArrayOutputStream; 021import java.io.IOException; 022import java.io.ObjectInputStream; 023import java.io.ObjectOutputStream; 024import java.io.Serializable; 025import java.lang.reflect.InvocationTargetException; 026import java.lang.reflect.Method; 027 028import org.apache.commons.collections4.Factory; 029import org.apache.commons.collections4.FunctorException; 030 031/** 032 * Factory implementation that creates a new instance each time based on a prototype. 033 * <p> 034 * <strong>WARNING:</strong> from v4.1 onwards {@link Factory} instances returned by 035 * {@link #prototypeFactory(Object)} will <strong>not</strong> be serializable anymore in order 036 * to prevent potential remote code execution exploits. Please refer to 037 * <a href="https://issues.apache.org/jira/browse/COLLECTIONS-580">COLLECTIONS-580</a> 038 * for more details. 039 * </p> 040 * 041 * @since 3.0 042 */ 043public class PrototypeFactory { 044 045 /** 046 * PrototypeCloneFactory creates objects by copying a prototype using the clone method. 047 * 048 * @param <T> The type of results supplied by this supplier. 049 */ 050 static class PrototypeCloneFactory<T> implements Factory<T> { 051 052 /** The object to clone each time */ 053 private final T iPrototype; 054 055 /** The method used to clone */ 056 private transient Method iCloneMethod; 057 058 /** 059 * Constructor to store prototype. 060 */ 061 private PrototypeCloneFactory(final T prototype, final Method method) { 062 iPrototype = prototype; 063 iCloneMethod = method; 064 } 065 066 /** 067 * Creates an object by calling the clone method. 068 * 069 * @return The new object 070 */ 071 @Override 072 @SuppressWarnings("unchecked") 073 public T create() { 074 // needed for post-serialization 075 if (iCloneMethod == null) { 076 findCloneMethod(); 077 } 078 079 try { 080 return (T) iCloneMethod.invoke(iPrototype, (Object[]) null); 081 } catch (final IllegalAccessException ex) { 082 throw new FunctorException("PrototypeCloneFactory: Clone method must be public", ex); 083 } catch (final InvocationTargetException ex) { 084 throw new FunctorException("PrototypeCloneFactory: Clone method threw an exception", ex); 085 } 086 } 087 088 /** 089 * Find the Clone method for the class specified. 090 */ 091 private void findCloneMethod() { 092 try { 093 iCloneMethod = iPrototype.getClass().getMethod("clone", (Class[]) null); 094 } catch (final NoSuchMethodException ex) { 095 throw new IllegalArgumentException("PrototypeCloneFactory: The clone method must exist and be public "); 096 } 097 } 098 } 099 100 /** 101 * PrototypeSerializationFactory creates objects by cloning a prototype using serialization. 102 * 103 * @param <T> The type of results supplied by this supplier. 104 */ 105 static class PrototypeSerializationFactory<T extends Serializable> implements Factory<T> { 106 107 /** The object to clone via serialization each time */ 108 private final T iPrototype; 109 110 /** 111 * Constructor to store prototype 112 */ 113 private PrototypeSerializationFactory(final T prototype) { 114 iPrototype = prototype; 115 } 116 117 /** 118 * Creates an object using serialization. 119 * 120 * @return The new object 121 */ 122 @Override 123 @SuppressWarnings("unchecked") 124 public T create() { 125 final ByteArrayOutputStream baos = new ByteArrayOutputStream(512); 126 ByteArrayInputStream bais = null; 127 try { 128 final ObjectOutputStream out = new ObjectOutputStream(baos); 129 out.writeObject(iPrototype); 130 131 bais = new ByteArrayInputStream(baos.toByteArray()); 132 final ObjectInputStream in = new ObjectInputStream(bais); 133 return (T) in.readObject(); 134 135 } catch (final ClassNotFoundException | IOException ex) { 136 throw new FunctorException(ex); 137 } finally { 138 try { 139 if (bais != null) { 140 bais.close(); 141 } 142 } catch (final IOException ex) { //NOPMD 143 // ignore 144 } 145 try { 146 baos.close(); 147 } catch (final IOException ex) { //NOPMD 148 // ignore 149 } 150 } 151 } 152 } 153 154 /** 155 * Factory method that performs validation. 156 * <p> 157 * Creates a Factory that will return a clone of the same prototype object 158 * each time the factory is used. The prototype will be cloned using one of these 159 * techniques (in order): 160 * </p> 161 * 162 * <ul> 163 * <li>public clone method</li> 164 * <li>public copy constructor</li> 165 * <li>serialization clone</li> 166 * </ul> 167 * 168 * @param <T> the type the factory creates 169 * @param prototype The object to clone each time in the factory 170 * @return The {@code prototype} factory, or a {@link ConstantFactory#NULL_INSTANCE} if 171 * the {@code prototype} is {@code null} 172 * @throws IllegalArgumentException if the prototype cannot be cloned 173 */ 174 @SuppressWarnings("unchecked") 175 public static <T> Factory<T> prototypeFactory(final T prototype) { 176 if (prototype == null) { 177 return ConstantFactory.<T>constantFactory(null); 178 } 179 try { 180 final Method method = prototype.getClass().getMethod("clone", (Class[]) null); 181 return new PrototypeCloneFactory<>(prototype, method); 182 183 } catch (final NoSuchMethodException ex) { 184 try { 185 prototype.getClass().getConstructor(prototype.getClass()); 186 return new InstantiateFactory<>( 187 (Class<T>) prototype.getClass(), 188 new Class<?>[] { prototype.getClass() }, 189 new Object[] { prototype }); 190 } catch (final NoSuchMethodException ex2) { 191 if (prototype instanceof Serializable) { 192 return (Factory<T>) new PrototypeSerializationFactory<>((Serializable) prototype); 193 } 194 } 195 } 196 throw new IllegalArgumentException("The prototype must be cloneable via a public clone method"); 197 } 198 199 /** 200 * Restricted constructor. 201 */ 202 private PrototypeFactory() { 203 } 204 205}