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 // PrototypeCloneFactory 046 /** 047 * PrototypeCloneFactory creates objects by copying a prototype using the clone method. 048 */ 049 static class PrototypeCloneFactory<T> implements Factory<T> { 050 051 /** The object to clone each time */ 052 private final T iPrototype; 053 /** The method used to clone */ 054 private transient Method iCloneMethod; 055 056 /** 057 * Constructor to store prototype. 058 */ 059 private PrototypeCloneFactory(final T prototype, final Method method) { 060 iPrototype = prototype; 061 iCloneMethod = method; 062 } 063 064 /** 065 * Creates an object by calling the clone method. 066 * 067 * @return the new object 068 */ 069 @Override 070 @SuppressWarnings("unchecked") 071 public T create() { 072 // needed for post-serialization 073 if (iCloneMethod == null) { 074 findCloneMethod(); 075 } 076 077 try { 078 return (T) iCloneMethod.invoke(iPrototype, (Object[]) null); 079 } catch (final IllegalAccessException ex) { 080 throw new FunctorException("PrototypeCloneFactory: Clone method must be public", ex); 081 } catch (final InvocationTargetException ex) { 082 throw new FunctorException("PrototypeCloneFactory: Clone method threw an exception", ex); 083 } 084 } 085 086 /** 087 * Find the Clone method for the class specified. 088 */ 089 private void findCloneMethod() { 090 try { 091 iCloneMethod = iPrototype.getClass().getMethod("clone", (Class[]) null); 092 } catch (final NoSuchMethodException ex) { 093 throw new IllegalArgumentException("PrototypeCloneFactory: The clone method must exist and be public "); 094 } 095 } 096 } 097 098 // PrototypeSerializationFactory 099 /** 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}