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 * https://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 <I> The type of results supplied by this builder.
40 * @param <T> The type of the object managed by 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 // 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 }