    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 */
    017
    018package org.apache.commons.lang3.concurrent;
    019
    020import java.util.Objects;
    021
    022import org.apache.commons.lang3.builder.AbstractSupplier;
    023import org.apache.commons.lang3.exception.ExceptionUtils;
    024import org.apache.commons.lang3.function.FailableConsumer;
    025import org.apache.commons.lang3.function.FailableSupplier;
    026
    027/**
    028 * Abstracts and defines operations for {@link ConcurrentInitializer} implementations.
    029 *
    030 * @param <T> the type of the object managed by this initializer class.
    031 * @param <E> The exception type thrown by {@link #initialize()}.
    032 * @since 3.14.0
    033 */
    034public abstract class AbstractConcurrentInitializer<T, E extends Exception> implements ConcurrentInitializer<T> {
    035
    036    /**
    037     * Builds a new instance for subclasses.
    038     *
    039     * @param <I> The type of results supplied by this builder.
    040     * @param <T> The type of the object managed by the initializer class.
    041     * @param <B> The type of builder.
    042     * @param <E> The exception type thrown by {@link #initialize()}.
    043     */
    044    public abstract static class AbstractBuilder<I extends AbstractConcurrentInitializer<T, E>, T, B extends AbstractBuilder<I, T, B, E>, E extends Exception>
    045            extends AbstractSupplier<I, B, E> {
    046
    047        /**
    048         * Closer consumer called by {@link #close()}.
    049         */
    050        private FailableConsumer<T, ? extends Exception> closer = FailableConsumer.nop();
    051
    052        /**
    053         * Initializer supplier called by {@link #initialize()}.
    054         */
    055        private FailableSupplier<T, ? extends Exception> initializer = FailableSupplier.nul();
    056
    057        /**
    058         * Constructs a new instance.
    059         */
    060        public AbstractBuilder() {
    061            // empty
    062        }
    063
    064        /**
    065         * Gets the closer consumer called by {@link #close()}.
    066         *
    067         * @return the closer consumer called by {@link #close()}.
    068         */
    069        public FailableConsumer<T, ? extends Exception> getCloser() {
    070            return closer;
    071        }
    072
    073        /**
    074         * Gets the initializer supplier called by {@link #initialize()}.
    075         *
    076         * @return the initializer supplier called by {@link #initialize()}.
    077         */
    078        public FailableSupplier<T, ? extends Exception> getInitializer() {
    079            return initializer;
    080        }
    081
    082        /**
    083         * Sets the closer consumer called by {@link #close()}.
    084         *
    085         * @param closer the consumer called by {@link #close()}.
    086         * @return {@code this} instance.
    087         */
    088        public B setCloser(final FailableConsumer<T, ? extends Exception> closer) {
    089            this.closer = closer != null ? closer : FailableConsumer.nop();
    090            return asThis();
    091        }
    092
    093        /**
    094         * Sets the initializer supplier called by {@link #initialize()}.
    095         *
    096         * @param initializer the supplier called by {@link #initialize()}.
    097         * @return {@code this} instance.
    098         */
    099        public B setInitializer(final FailableSupplier<T, ? extends Exception> initializer) {
    100            this.initializer = initializer != null ? initializer : FailableSupplier.nul();
    101            return asThis();
    102        }
    103
    104    }
    105
    106    /**
    107     * Closer consumer called by {@link #close()}.
    108     */
    109    private final FailableConsumer<? super T, ? extends Exception> closer;
    110
    111    /**
    112     * Initializer supplier called by {@link #initialize()}.
    113     */
    114    private final FailableSupplier<? extends T, ? extends Exception> initializer;
    115
    116    /**
    117     * Constructs a new instance.
    118     */
    119    public AbstractConcurrentInitializer() {
    120        this(FailableSupplier.nul(), FailableConsumer.nop());
    121    }
    122
    123    /**
    124     * Constructs a new instance.
    125     *
    126     * @param initializer the initializer supplier called by {@link #initialize()}.
    127     * @param closer the closer consumer called by {@link #close()}.
    128     */
    129    AbstractConcurrentInitializer(final FailableSupplier<? extends T, ? extends Exception> initializer, final FailableConsumer<? super T, ? extends Exception> closer) {
    130        this.closer = Objects.requireNonNull(closer, "closer");
    131        this.initializer = Objects.requireNonNull(initializer, "initializer");
    132    }
    133
    134    /**
    135     * Calls the closer with the manager object.
    136     *
    137     * @throws ConcurrentException Thrown by the closer.
    138     * @since 3.14.0
    139     */
    140    public void close() throws ConcurrentException {
    141        if (isInitialized()) {
    142            try {
    143                closer.accept(get());
    144            } catch (final Exception e) {
    145                // This intentionally does not duplicate the logic in initialize
    146                // or care about the generic type E.
    147                //
    148                // initialize may run inside a Future and it does not make sense
    149                // to wrap an exception stored inside a Future. However close()
    150                // always runs on the current thread so it always wraps in a
    151                // ConcurrentException
    152                throw new ConcurrentException(ExceptionUtils.throwUnchecked(e));
    153            }
    154        }
    155    }
    156
    157    /**
    158     * Gets an Exception with a type of E as defined by a concrete subclass of this class.
    159     *
    160     * @param e The actual exception that was thrown.
    161     * @return a new exception with the actual type of E, that wraps e.
    162     */
    163    protected abstract E getTypedException(Exception e);
    164
    165    /**
    166     * Creates and initializes the object managed by this {@code
    167     * ConcurrentInitializer}. This method is called by {@link #get()} when the object is accessed for the first time. An implementation can focus on the
    168     * creation of the object. No synchronization is needed, as this is already handled by {@code get()}.
    169     * <p>
    170     * Subclasses and clients that do not provide an initializer are expected to implement this method.
    171     * </p>
    172     *
    173     * @return the managed data object.
    174     * @throws E if an error occurs during object creation.
    175     */
    176    @SuppressWarnings("unchecked")
    177    protected T initialize() throws E {
    178        try {
    179            return initializer.get();
    180        } catch (final Exception e) {
    181            // Do this first so we don't pass a RuntimeException or Error into an exception constructor
    182            ExceptionUtils.throwUnchecked(e);
    183            // Depending on the subclass of AbstractConcurrentInitializer E can be Exception or ConcurrentException
    184            // if E is Exception the if statement below will always be true, and the new Exception object created
    185            // in getTypedException will never be thrown. If E is ConcurrentException and the if statement is false
    186            // we throw the ConcurrentException returned from getTypedException, which wraps the original exception.
    187            final E typedException = getTypedException(e);
    188            if (typedException.getClass().isAssignableFrom(e.getClass())) {
    189                throw (E) e;
    190            }
    191            throw typedException;
    192        }
    193    }
    194
    195    /**
    196     * Tests whether initialization has been completed. If initialization threw an exception this will return false, but it will return true if a subsequent
    197     * call to initialize completes successfully. If the implementation of ConcurrentInitializer can initialize multiple objects, this will only return true if
    198     * all objects have been initialized.
    199     *
    200     * @return true if all initialization is complete, otherwise false.
    201     */
    202    protected abstract boolean isInitialized();
    203
    204}