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