1 /*
2 * ModeShape (http://www.modeshape.org)
3 * See the COPYRIGHT.txt file distributed with this work for information
4 * regarding copyright ownership. Some portions may be licensed
5 * to Red Hat, Inc. under one or more contributor license agreements.
6 * See the AUTHORS.txt file in the distribution for a full listing of
7 * individual contributors.
8 *
9 * ModeShape is free software. Unless otherwise indicated, all code in ModeShape
10 * is licensed to you under the terms of the GNU Lesser General Public License as
11 * published by the Free Software Foundation; either version 2.1 of
12 * the License, or (at your option) any later version.
13 *
14 * ModeShape is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 * Lesser General Public License for more details.
18 *
19 * You should have received a copy of the GNU Lesser General Public
20 * License along with this software; if not, write to the Free
21 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
22 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
23 */
24 package org.modeshape.graph.request;
25
26 import java.util.ArrayList;
27 import java.util.Collections;
28 import java.util.Iterator;
29 import java.util.LinkedList;
30 import java.util.List;
31 import java.util.concurrent.atomic.AtomicBoolean;
32 import net.jcip.annotations.Immutable;
33 import org.modeshape.common.util.CheckArg;
34
35 /**
36 * A request that wraps multiple other requests, allowing multiple requests to be treated as a single request.
37 * <p>
38 * Note that {@link #isCancelled()} and {@link #cancel()} apply to all requests contained by the composite request. In other
39 * words, cancelling this request immediately marks all contained requests as cancelled. However, cancelling any request in the
40 * request has the effect of cancelling all other requests in the composite, including the composite. (This is implemented by
41 * having all {@link Request} objects in the composite share the same cancelled flag object.)
42 * </p>
43 */
44 @Immutable
45 public class CompositeRequest extends Request implements Iterable<Request> {
46
47 private static final long serialVersionUID = 1L;
48
49 public static final int UNKNOWN_NUMBER_OF_REQUESTS = Integer.MAX_VALUE;
50
51 /**
52 * Return a request that either wraps multiple requests, or the single request if only one is supplied.
53 *
54 * @param requests the requests to wrap
55 * @return the requests wrapped in a CompositeRequest, or if only one request is supplied that single request
56 * @throws IllegalArgumentException if there requests are null, empty, or contains only nulls
57 */
58 public static Request with( Request... requests ) {
59 CheckArg.isNotEmpty(requests, "requests");
60 if (requests.length == 1) {
61 CheckArg.isNotNull(requests[0], "requests[0]");
62 return requests[0];
63 }
64 boolean readOnly = true;
65 List<Request> list = new ArrayList<Request>(requests.length);
66 for (Request request : requests) {
67 if (request == null) continue;
68 if (RequestType.COMPOSITE == request.getType()) {
69 CompositeRequest composite = (CompositeRequest)request;
70 list.addAll(composite.getRequests());
71 if (!composite.isReadOnly()) readOnly = false;
72 } else {
73 list.add(request);
74 if (!request.isReadOnly()) readOnly = false;
75 }
76 }
77 CheckArg.isNotEmpty(list, "requests");
78 return new CompositeRequest(list, readOnly);
79 }
80
81 /**
82 * Return a request that either wraps multiple requests, or the single request if only one is supplied.
83 *
84 * @param requests the requests to wrap
85 * @return the requests wrapped in a CompositeRequest, or if only one request is supplied that single request
86 * @throws IllegalArgumentException if there requests are null, empty, or contains only nulls
87 */
88 public static Request with( Iterator<? extends Request> requests ) {
89 CheckArg.isNotNull(requests, "requests");
90 boolean readOnly = true;
91 List<Request> list = new LinkedList<Request>();
92 while (requests.hasNext()) {
93 Request request = requests.next();
94 if (request == null) continue;
95 if (RequestType.COMPOSITE == request.getType()) {
96 CompositeRequest composite = (CompositeRequest)request;
97 list.addAll(composite.getRequests());
98 if (!composite.isReadOnly()) readOnly = false;
99 } else {
100 list.add(request);
101 if (!request.isReadOnly()) readOnly = false;
102 }
103 }
104 if (list.size() == 1) {
105 return list.get(0);
106 }
107 CheckArg.isNotEmpty(list, "requests");
108 return new CompositeRequest(list, readOnly);
109 }
110
111 /**
112 * Return a request that either wraps multiple requests, or the single request if only one is supplied.
113 *
114 * @param requests the requests to wrap
115 * @return the requests wrapped in a CompositeRequest, or if only one request is supplied that single request
116 * @throws IllegalArgumentException if there requests are null or empty
117 */
118 public static Request with( List<? extends Request> requests ) {
119 CheckArg.isNotEmpty(requests, "requests");
120 if (requests.size() == 1) {
121 return requests.get(0);
122 }
123 boolean readOnly = true;
124 for (Request request : requests) {
125 readOnly = request.isReadOnly();
126 if (!readOnly) break;
127 }
128 return new CompositeRequest(requests, readOnly);
129 }
130
131 /**
132 * Add requests to the supplied composite request.
133 *
134 * @param composite the composite request to which the requests are to be added
135 * @param requests the requests to wrap
136 * @return the requests wrapped in a CompositeRequest, or if only one request is supplied that single request, or null if
137 * there are no request
138 * @throws IllegalArgumentException if the composite request is null
139 */
140 public static CompositeRequest add( CompositeRequest composite,
141 Request... requests ) {
142 CheckArg.isNotNull(composite, "composite");
143 if (requests == null || requests.length == 0) return composite;
144 List<Request> list = new ArrayList<Request>(requests.length + composite.size());
145 boolean readOnly = composite.isReadOnly();
146 if (composite.size() != 0) list.addAll(composite.getRequests());
147 for (Request request : requests) {
148 if (request == null) continue;
149 if (RequestType.COMPOSITE == request.getType()) {
150 CompositeRequest compositeRequest = (CompositeRequest)request;
151 list.addAll(compositeRequest.getRequests());
152 if (!compositeRequest.isReadOnly()) readOnly = false;
153 } else {
154 list.add(request);
155 if (!request.isReadOnly()) readOnly = false;
156 }
157 }
158 return new CompositeRequest(list, readOnly);
159 }
160
161 /**
162 * Add requests to the supplied composite request.
163 *
164 * @param composite the composite request to which the requests are to be added
165 * @param requests the requests to wrap
166 * @return the requests wrapped in a CompositeRequest, or if only one request is supplied that single request, or null if
167 * there are no request
168 * @throws IllegalArgumentException if the composite request is null
169 */
170 public static CompositeRequest add( CompositeRequest composite,
171 Iterator<? extends Request> requests ) {
172 CheckArg.isNotNull(composite, "composite");
173 List<Request> list = new LinkedList<Request>();
174 boolean readOnly = composite.isReadOnly();
175 if (composite.size() != 0) list.addAll(composite.getRequests());
176 while (requests.hasNext()) {
177 Request request = requests.next();
178 if (request == null) continue;
179 if (RequestType.COMPOSITE == request.getType()) {
180 CompositeRequest compositeRequest = (CompositeRequest)request;
181 list.addAll(compositeRequest.getRequests());
182 if (!compositeRequest.isReadOnly()) readOnly = false;
183 } else {
184 list.add(request);
185 if (!request.isReadOnly()) readOnly = false;
186 }
187 }
188 return new CompositeRequest(list, readOnly);
189 }
190
191 private final List<Request> requests;
192 private final boolean readOnly;
193
194 /**
195 * Create a composite request from the supplied list of requests.
196 *
197 * @param requests the modifiable list of requests; may not be null
198 * @param readOnly true if all of the requests are {@link Request#isReadOnly() read-only}
199 */
200 protected CompositeRequest( List<? extends Request> requests,
201 boolean readOnly ) {
202 // Iterate through the requests and set the cancelled flag of each request to this object's flag ...
203 final AtomicBoolean flag = super.getCancelledFlag();
204 for (Request request : requests) {
205 request.setCancelledFlag(flag);
206 }
207 this.requests = Collections.unmodifiableList(requests);
208 this.readOnly = readOnly;
209 }
210
211 /**
212 * Create a composite request from the supplied list of requests. This is useful only for subclasses.
213 *
214 * @param readOnly true if all of the requests are {@link Request#isReadOnly() read-only}
215 */
216 protected CompositeRequest( boolean readOnly ) {
217 this.requests = Collections.emptyList();
218 this.readOnly = readOnly;
219 }
220
221 /**
222 * Return the unmodifiable requests contained in this composite request.
223 *
224 * @return requests
225 */
226 public List<Request> getRequests() {
227 return requests;
228 }
229
230 /**
231 * Get the number of requests.
232 *
233 * @return the number of requests
234 */
235 public int size() {
236 return requests.size();
237 }
238
239 /**
240 * {@inheritDoc}
241 *
242 * @see java.lang.Iterable#iterator()
243 */
244 public Iterator<Request> iterator() {
245 return requests.iterator();
246 }
247
248 /**
249 * {@inheritDoc}
250 *
251 * @see org.modeshape.graph.request.Request#isReadOnly()
252 */
253 @Override
254 public boolean isReadOnly() {
255 return readOnly;
256 }
257
258 /**
259 * Check the set of requests for errors. Calling this method will always result in the {@link #getError() existing error}
260 * being reset to a new error or null if there are no errors.
261 */
262 public void checkForErrors() {
263 Request firstRequestWithError = null;
264 LinkedList<Request> requestsWithErrors = null;
265 for (Request request : requests) {
266 if (request.hasError()) {
267 if (firstRequestWithError == null) {
268 firstRequestWithError = request;
269 } else {
270 // This is not the first ...
271 if (requestsWithErrors == null) {
272 requestsWithErrors = new LinkedList<Request>();
273 requestsWithErrors.add(firstRequestWithError);
274 }
275 requestsWithErrors.add(request);
276 }
277 }
278 }
279 if (firstRequestWithError != null) {
280 // There is at least one error ...
281 if (requestsWithErrors == null) {
282 // but only one ...
283 setError(firstRequestWithError.getError());
284 } else {
285 // More than one error ...
286 setError(new MultipleRequestFailuresException(requestsWithErrors, size()));
287 }
288 }
289 }
290
291 /**
292 * {@inheritDoc}
293 *
294 * @see java.lang.Object#equals(java.lang.Object)
295 */
296 @Override
297 public boolean equals( Object obj ) {
298 if (obj == this) return true;
299 if (obj instanceof CompositeRequest) {
300 CompositeRequest that = (CompositeRequest)obj;
301 if (this.size() != that.size()) return false;
302 Iterator<Request> thisIter = this.iterator();
303 Iterator<Request> thatIter = that.iterator();
304 while (thisIter.hasNext()) {
305 Request thisRequest = thisIter.next();
306 Request thatRequest = thatIter.next();
307 if (thisRequest == null) {
308 if (thatRequest != null) return false;
309 } else {
310 if (!thisRequest.equals(thatRequest)) return false;
311 }
312 }
313 return true;
314 }
315 return false;
316 }
317
318 @Override
319 public String toString() {
320 StringBuffer buff = new StringBuffer();
321 buff.append("[CompositeRequest (" + size() + ")");
322
323 for (Request r : this.getRequests()) {
324 buff.append("\n\t").append(r);
325 }
326
327 buff.append("]");
328 return buff.toString();
329 }
330
331 @Override
332 public RequestType getType() {
333 return RequestType.COMPOSITE;
334 }
335 }