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 * http://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 /** The method used to clone */ 055 private transient Method iCloneMethod; 056 057 /** 058 * Constructor to store prototype. 059 */ 060 private PrototypeCloneFactory(final T prototype, final Method method) { 061 iPrototype = prototype; 062 iCloneMethod = method; 063 } 064 065 /** 066 * Creates an object by calling the clone method. 067 * 068 * @return the new object 069 */ 070 @Override 071 @SuppressWarnings("unchecked") 072 public T create() { 073 // needed for post-serialization 074 if (iCloneMethod == null) { 075 findCloneMethod(); 076 } 077 078 try { 079 return (T) iCloneMethod.invoke(iPrototype, (Object[]) null); 080 } catch (final IllegalAccessException ex) { 081 throw new FunctorException("PrototypeCloneFactory: Clone method must be public", ex); 082 } catch (final InvocationTargetException ex) { 083 throw new FunctorException("PrototypeCloneFactory: Clone method threw an exception", ex); 084 } 085 } 086 087 /** 088 * Find the Clone method for the class specified. 089 */ 090 private void findCloneMethod() { 091 try { 092 iCloneMethod = iPrototype.getClass().getMethod("clone", (Class[]) null); 093 } catch (final NoSuchMethodException ex) { 094 throw new IllegalArgumentException("PrototypeCloneFactory: The clone method must exist and be public "); 095 } 096 } 097 } 098 099 /** 100 * PrototypeSerializationFactory creates objects by cloning a prototype using serialization. 101 * 102 * @param <T> the type of results supplied by this supplier. 103 */ 104 static class PrototypeSerializationFactory<T extends Serializable> implements Factory<T> { 105 106 /** The object to clone via serialization each time */ 107 private final T iPrototype; 108 109 /** 110 * Constructor to store prototype 111 */ 112 private PrototypeSerializationFactory(final T prototype) { 113 iPrototype = prototype; 114 } 115 116 /** 117 * Creates an object using serialization. 118 * 119 * @return the new object 120 */ 121 @Override 122 @SuppressWarnings("unchecked") 123 public T create() { 124 final ByteArrayOutputStream baos = new ByteArrayOutputStream(512); 125 ByteArrayInputStream bais = null; 126 try { 127 final ObjectOutputStream out = new ObjectOutputStream(baos); 128 out.writeObject(iPrototype); 129 130 bais = new ByteArrayInputStream(baos.toByteArray()); 131 final ObjectInputStream in = new ObjectInputStream(bais); 132 return (T) in.readObject(); 133 134 } catch (final ClassNotFoundException | IOException ex) { 135 throw new FunctorException(ex); 136 } finally { 137 try { 138 if (bais != null) { 139 bais.close(); 140 } 141 } catch (final IOException ex) { //NOPMD 142 // ignore 143 } 144 try { 145 baos.close(); 146 } catch (final IOException ex) { //NOPMD 147 // ignore 148 } 149 } 150 } 151 } 152 153 /** 154 * Factory method that performs validation. 155 * <p> 156 * Creates a Factory that will return a clone of the same prototype object 157 * each time the factory is used. The prototype will be cloned using one of these 158 * techniques (in order): 159 * </p> 160 * 161 * <ul> 162 * <li>public clone method</li> 163 * <li>public copy constructor</li> 164 * <li>serialization clone</li> 165 * </ul> 166 * 167 * @param <T> the type the factory creates 168 * @param prototype the object to clone each time in the factory 169 * @return the {@code prototype} factory, or a {@link ConstantFactory#NULL_INSTANCE} if 170 * the {@code prototype} is {@code null} 171 * @throws IllegalArgumentException if the prototype cannot be cloned 172 */ 173 @SuppressWarnings("unchecked") 174 public static <T> Factory<T> prototypeFactory(final T prototype) { 175 if (prototype == null) { 176 return ConstantFactory.<T>constantFactory(null); 177 } 178 try { 179 final Method method = prototype.getClass().getMethod("clone", (Class[]) null); 180 return new PrototypeCloneFactory<>(prototype, method); 181 182 } catch (final NoSuchMethodException ex) { 183 try { 184 prototype.getClass().getConstructor(prototype.getClass()); 185 return new InstantiateFactory<>( 186 (Class<T>) prototype.getClass(), 187 new Class<?>[] { prototype.getClass() }, 188 new Object[] { prototype }); 189 } catch (final NoSuchMethodException ex2) { 190 if (prototype instanceof Serializable) { 191 return (Factory<T>) new PrototypeSerializationFactory<>((Serializable) prototype); 192 } 193 } 194 } 195 throw new IllegalArgumentException("The prototype must be cloneable via a public clone method"); 196 } 197 198 /** 199 * Restricted constructor. 200 */ 201 private PrototypeFactory() { 202 } 203 204}