View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  package org.apache.commons.lang3.concurrent;
19  
20  import java.util.Objects;
21  
22  import org.apache.commons.lang3.builder.AbstractSupplier;
23  import org.apache.commons.lang3.exception.ExceptionUtils;
24  import org.apache.commons.lang3.function.FailableConsumer;
25  import org.apache.commons.lang3.function.FailableSupplier;
26  
27  /**
28   * Abstracts and defines operations for ConcurrentInitializer implementations.
29   *
30   * @param <T> the type of the object managed by this initializer class.
31   * @param <E> The exception type thrown by {@link #initialize()}.
32   * @since 3.14.0
33   */
34  public abstract class AbstractConcurrentInitializer<T, E extends Exception> implements ConcurrentInitializer<T> {
35  
36      /**
37       * Builds a new instance for subclasses.
38       *
39       * @param <T> the type of the object managed by the initializer class.
40       * @param <I> the type of the initializer class.
41       * @param <B> the type of builder.
42       * @param <E> The exception type thrown by {@link #initialize()}.
43       */
44      public abstract static class AbstractBuilder<I extends AbstractConcurrentInitializer<T, E>, T, B extends AbstractBuilder<I, T, B, E>, E extends Exception>
45              extends AbstractSupplier<I, B, E> {
46  
47          /**
48           * Closer consumer called by {@link #close()}.
49           */
50          private FailableConsumer<T, ? extends Exception> closer = FailableConsumer.nop();
51  
52          /**
53           * Initializer supplier called by {@link #initialize()}.
54           */
55          private FailableSupplier<T, ? extends Exception> initializer = FailableSupplier.nul();
56  
57          /**
58           * Gets the closer consumer called by {@link #close()}.
59           *
60           * @return the closer consumer called by {@link #close()}.
61           */
62          public FailableConsumer<T, ? extends Exception> getCloser() {
63              return closer;
64          }
65  
66          /**
67           * Gets the initializer supplier called by {@link #initialize()}.
68           *
69           * @return the initializer supplier called by {@link #initialize()}.
70           */
71          public FailableSupplier<T, ? extends Exception> getInitializer() {
72              return initializer;
73          }
74  
75          /**
76           * Sets the closer consumer called by {@link #close()}.
77           *
78           * @param closer the consumer called by {@link #close()}.
79           * @return this
80           */
81          public B setCloser(final FailableConsumer<T, ? extends Exception> closer) {
82              this.closer = closer != null ? closer : FailableConsumer.nop();
83              return asThis();
84          }
85  
86          /**
87           * Sets the initializer supplier called by {@link #initialize()}.
88           *
89           * @param initializer the supplier called by {@link #initialize()}.
90           * @return this
91           */
92          public B setInitializer(final FailableSupplier<T, ? extends Exception> initializer) {
93              this.initializer = initializer != null ? initializer : FailableSupplier.nul();
94              return asThis();
95          }
96  
97      }
98  
99      /**
100      * Closer consumer called by {@link #close()}.
101      */
102     private final FailableConsumer<? super T, ? extends Exception> closer;
103 
104     /**
105      * Initializer supplier called by {@link #initialize()}.
106      */
107     private final FailableSupplier<? extends T, ? extends Exception> initializer;
108 
109     /**
110      * Constructs a new instance.
111      */
112     public AbstractConcurrentInitializer() {
113         this(FailableSupplier.nul(), FailableConsumer.nop());
114     }
115 
116     /**
117      * Constructs a new instance.
118      *
119      * @param initializer the initializer supplier called by {@link #initialize()}.
120      * @param closer the closer consumer called by {@link #close()}.
121      */
122     AbstractConcurrentInitializer(final FailableSupplier<? extends T, ? extends Exception> initializer, final FailableConsumer<? super T, ? extends Exception> closer) {
123         this.closer = Objects.requireNonNull(closer, "closer");
124         this.initializer = Objects.requireNonNull(initializer, "initializer");
125     }
126 
127     /**
128      * Calls the closer with the manager object.
129      *
130      * @throws ConcurrentException Thrown by the closer.
131      * @since 3.14.0
132      */
133     public void close() throws ConcurrentException {
134         if (isInitialized()) {
135             try {
136                 closer.accept(get());
137             } catch (final Exception e) {
138                 // This intentionally does not duplicate the logic in initialize
139                 // or care about the generic type E.
140                 //
141                 // initialize may run inside a Future and it does not make sense
142                 // to wrap an exception stored inside a Future. However close()
143                 // always runs on the current thread so it always wraps in a
144                 // ConcurrentException
145                 throw new ConcurrentException(ExceptionUtils.throwUnchecked(e));
146             }
147         }
148     }
149 
150     /**
151      * Gets an Exception with a type of E as defined by a concrete subclass of this class.
152      *
153      * @param e The actual exception that was thrown
154      * @return a new exception with the actual type of E, that wraps e.
155      */
156     protected abstract E getTypedException(Exception e);
157 
158     /**
159      * Creates and initializes the object managed by this {@code
160      * ConcurrentInitializer}. This method is called by {@link #get()} when the object is accessed for the first time. An implementation can focus on the
161      * creation of the object. No synchronization is needed, as this is already handled by {@code get()}.
162      * <p>
163      * Subclasses and clients that do not provide an initializer are expected to implement this method.
164      * </p>
165      *
166      * @return the managed data object
167      * @throws E if an error occurs during object creation
168      */
169     @SuppressWarnings("unchecked")
170     protected T initialize() throws E {
171         try {
172             return initializer.get();
173         } catch (final Exception e) {
174             // Do this first so we don't pass a RuntimeException or Error into an exception constructor
175             ExceptionUtils.throwUnchecked(e);
176 
177             // Depending on the subclass of AbstractConcurrentInitializer E can be Exception or ConcurrentException
178             // if E is Exception the if statement below will always be true, and the new Exception object created
179             // in getTypedException will never be thrown. If E is ConcurrentException and the if statement is false
180             // we throw the ConcurrentException returned from getTypedException, which wraps the original exception.
181             final E typedException = getTypedException(e);
182             if (typedException.getClass().isAssignableFrom(e.getClass())) {
183                 throw (E) e;
184             }
185             throw typedException;
186         }
187     }
188 
189     /**
190      * Returns true if initialization has been completed. If initialization threw an exception this will return false, but it will return true if a subsequent
191      * call to initialize completes successfully. If the implementation of ConcurrentInitializer can initialize multiple objects, this will only return true if
192      * all objects have been initialized.
193      *
194      * @return true if all initialization is complete, otherwise false
195      */
196     protected abstract boolean isInitialized();
197 
198 }