    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}