    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}