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 * <b>WARNING:</b> from v4.1 onwards {@link Factory} instances returned by 035 * {@link #prototypeFactory(Object)} will <b>not</b> 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 * Factory method that performs validation. 047 * <p> 048 * Creates a Factory that will return a clone of the same prototype object 049 * each time the factory is used. The prototype will be cloned using one of these 050 * techniques (in order): 051 * </p> 052 * 053 * <ul> 054 * <li>public clone method</li> 055 * <li>public copy constructor</li> 056 * <li>serialization clone</li> 057 * </ul> 058 * 059 * @param <T> the type the factory creates 060 * @param prototype the object to clone each time in the factory 061 * @return the <code>prototype</code> factory, or a {@link ConstantFactory#NULL_INSTANCE} if 062 * the {@code prototype} is {@code null} 063 * @throws IllegalArgumentException if the prototype cannot be cloned 064 */ 065 @SuppressWarnings("unchecked") 066 public static <T> Factory<T> prototypeFactory(final T prototype) { 067 if (prototype == null) { 068 return ConstantFactory.<T>constantFactory(null); 069 } 070 try { 071 final Method method = prototype.getClass().getMethod("clone", (Class[]) null); 072 return new PrototypeCloneFactory<>(prototype, method); 073 074 } catch (final NoSuchMethodException ex) { 075 try { 076 prototype.getClass().getConstructor(new Class<?>[] { prototype.getClass() }); 077 return new InstantiateFactory<>( 078 (Class<T>) prototype.getClass(), 079 new Class<?>[] { prototype.getClass() }, 080 new Object[] { prototype }); 081 } catch (final NoSuchMethodException ex2) { 082 if (prototype instanceof Serializable) { 083 return (Factory<T>) new PrototypeSerializationFactory<>((Serializable) prototype); 084 } 085 } 086 } 087 throw new IllegalArgumentException("The prototype must be cloneable via a public clone method"); 088 } 089 090 /** 091 * Restricted constructor. 092 */ 093 private PrototypeFactory() { 094 super(); 095 } 096 097 // PrototypeCloneFactory 098 //----------------------------------------------------------------------- 099 /** 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}