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