    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}