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 {@link 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           * Constructs a new instance.
59           */
60          public AbstractBuilder() {
61              // empty
62          }
63  
64          /**
65           * Gets the closer consumer called by {@link #close()}.
66           *
67           * @return the closer consumer called by {@link #close()}.
68           */
69          public FailableConsumer<T, ? extends Exception> getCloser() {
70              return closer;
71          }
72  
73          /**
74           * Gets the initializer supplier called by {@link #initialize()}.
75           *
76           * @return the initializer supplier called by {@link #initialize()}.
77           */
78          public FailableSupplier<T, ? extends Exception> getInitializer() {
79              return initializer;
80          }
81  
82          /**
83           * Sets the closer consumer called by {@link #close()}.
84           *
85           * @param closer the consumer called by {@link #close()}.
86           * @return {@code this} instance.
87           */
88          public B setCloser(final FailableConsumer<T, ? extends Exception> closer) {
89              this.closer = closer != null ? closer : FailableConsumer.nop();
90              return asThis();
91          }
92  
93          /**
94           * Sets the initializer supplier called by {@link #initialize()}.
95           *
96           * @param initializer the supplier called by {@link #initialize()}.
97           * @return {@code this} instance.
98           */
99          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 
184             // Depending on the subclass of AbstractConcurrentInitializer E can be Exception or ConcurrentException
185             // if E is Exception the if statement below will always be true, and the new Exception object created
186             // in getTypedException will never be thrown. If E is ConcurrentException and the if statement is false
187             // we throw the ConcurrentException returned from getTypedException, which wraps the original exception.
188             final E typedException = getTypedException(e);
189             if (typedException.getClass().isAssignableFrom(e.getClass())) {
190                 throw (E) e;
191             }
192             throw typedException;
193         }
194     }
195 
196     /**
197      * Returns true if initialization has been completed. If initialization threw an exception this will return false, but it will return true if a subsequent
198      * call to initialize completes successfully. If the implementation of ConcurrentInitializer can initialize multiple objects, this will only return true if
199      * all objects have been initialized.
200      *
201      * @return true if all initialization is complete, otherwise false
202      */
203     protected abstract boolean isInitialized();
204 
205 }