001 /*
002 * JBoss, Home of Professional Open Source.
003 * Copyright 2008, Red Hat Middleware LLC, and individual contributors
004 * as indicated by the @author tags. See the copyright.txt file in the
005 * distribution for a full listing of individual contributors.
006 *
007 * This is free software; you can redistribute it and/or modify it
008 * under the terms of the GNU Lesser General Public License as
009 * published by the Free Software Foundation; either version 2.1 of
010 * the License, or (at your option) any later version.
011 *
012 * This software is distributed in the hope that it will be useful,
013 * but WITHOUT ANY WARRANTY; without even the implied warranty of
014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015 * Lesser General Public License for more details.
016 *
017 * You should have received a copy of the GNU Lesser General Public
018 * License along with this software; if not, write to the Free
019 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
020 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
021 */
022 package org.jboss.dna.connector.federation.contribution;
023
024 import java.io.Serializable;
025 import java.util.Collection;
026 import java.util.Collections;
027 import java.util.Iterator;
028 import java.util.List;
029 import java.util.NoSuchElementException;
030 import net.jcip.annotations.Immutable;
031 import org.jboss.dna.graph.Location;
032 import org.jboss.dna.graph.properties.DateTime;
033 import org.jboss.dna.graph.properties.Name;
034 import org.jboss.dna.graph.properties.Property;
035 import org.jboss.dna.graph.properties.basic.JodaDateTime;
036
037 /**
038 * The contribution of a source to the information for a single federated node. Users of this interface should treat contributions
039 * as generally being immutable, since some implementation will be immutable and will return immutable {@link #getProperties()
040 * properties} and {@link #getChildren() children} containers. Thus, rather than make changes to an existing contribution, a new
041 * contribution is created to replace the previous contribution.
042 *
043 * @author Randall Hauch
044 */
045 @Immutable
046 public abstract class Contribution implements Serializable {
047
048 /**
049 * Create an empty contribution from the named source.
050 *
051 * @param sourceName the name of the source, which may not be null or blank
052 * @param expirationTime the time (in UTC) after which this contribution should be considered expired, or null if there is no
053 * expiration time
054 * @return the contribution
055 */
056 public static Contribution create( String sourceName,
057 DateTime expirationTime ) {
058 return new EmptyContribution(sourceName, expirationTime);
059 }
060
061 /**
062 * Create a contribution of a single property from the named source.
063 *
064 * @param sourceName the name of the source, which may not be null or blank
065 * @param locationInSource the location in the source for this contributed information; may not be null
066 * @param expirationTime the time (in UTC) after which this contribution should be considered expired, or null if there is no
067 * expiration time
068 * @param property the property from the source; may not be null
069 * @return the contribution
070 */
071 public static Contribution create( String sourceName,
072 Location locationInSource,
073 DateTime expirationTime,
074 Property property ) {
075 if (property == null) {
076 return new EmptyContribution(sourceName, expirationTime);
077 }
078 return new OnePropertyContribution(sourceName, locationInSource, expirationTime, property);
079 }
080
081 /**
082 * Create a contribution of a single child from the named source.
083 *
084 * @param sourceName the name of the source, which may not be null or blank
085 * @param locationInSource the path in the source for this contributed information; may not be null
086 * @param expirationTime the time (in UTC) after which this contribution should be considered expired, or null if there is no
087 * expiration time
088 * @param child the child from the source; may not be null or empty
089 * @return the contribution
090 */
091 public static Contribution create( String sourceName,
092 Location locationInSource,
093 DateTime expirationTime,
094 Location child ) {
095 if (child == null) {
096 return new EmptyContribution(sourceName, expirationTime);
097 }
098 return new OneChildContribution(sourceName, locationInSource, expirationTime, child);
099 }
100
101 /**
102 * Create a contribution of a single child from the named source.
103 *
104 * @param sourceName the name of the source, which may not be null or blank
105 * @param locationInSource the path in the source for this contributed information; may not be null
106 * @param expirationTime the time (in UTC) after which this contribution should be considered expired, or null if there is no
107 * expiration time
108 * @param child1 the first child from the source; may not be null or empty
109 * @param child2 the second child from the source; may not be null or empty
110 * @return the contribution
111 */
112 public static Contribution create( String sourceName,
113 Location locationInSource,
114 DateTime expirationTime,
115 Location child1,
116 Location child2 ) {
117 if (child1 != null) {
118 if (child2 != null) {
119 return new TwoChildContribution(sourceName, locationInSource, expirationTime, child1, child2);
120 }
121 return new OneChildContribution(sourceName, locationInSource, expirationTime, child1);
122 }
123 if (child2 != null) {
124 return new OneChildContribution(sourceName, locationInSource, expirationTime, child2);
125 }
126 return new EmptyContribution(sourceName, expirationTime);
127 }
128
129 /**
130 * Create a contribution of the supplied properties and children from the named source.
131 *
132 * @param sourceName the name of the source, which may not be null or blank
133 * @param locationInSource the path in the source for this contributed information; may not be null
134 * @param expirationTime the time (in UTC) after which this contribution should be considered expired, or null if there is no
135 * expiration time
136 * @param properties the properties from the source; may not be null
137 * @param children the children from the source; may not be null or empty
138 * @return the contribution
139 */
140 public static Contribution create( String sourceName,
141 Location locationInSource,
142 DateTime expirationTime,
143 Collection<Property> properties,
144 List<Location> children ) {
145 if (properties == null || properties.isEmpty()) {
146 // There are no properties ...
147 if (children == null || children.isEmpty()) {
148 return new EmptyContribution(sourceName, expirationTime);
149 }
150 if (children.size() == 1) {
151 return new OneChildContribution(sourceName, locationInSource, expirationTime, children.iterator().next());
152 }
153 if (children.size() == 2) {
154 Iterator<Location> iter = children.iterator();
155 return new TwoChildContribution(sourceName, locationInSource, expirationTime, iter.next(), iter.next());
156 }
157 return new MultiChildContribution(sourceName, locationInSource, expirationTime, children);
158 }
159 // There are some properties ...
160 if (children == null || children.isEmpty()) {
161 // There are no children ...
162 if (properties.size() == 1) {
163 return new OnePropertyContribution(sourceName, locationInSource, expirationTime, properties.iterator().next());
164 }
165 if (properties.size() == 2) {
166 Iterator<Property> iter = properties.iterator();
167 return new TwoPropertyContribution(sourceName, locationInSource, expirationTime, iter.next(), iter.next());
168 }
169 if (properties.size() == 3) {
170 Iterator<Property> iter = properties.iterator();
171 return new ThreePropertyContribution(sourceName, locationInSource, expirationTime, iter.next(), iter.next(),
172 iter.next());
173 }
174 return new MultiPropertyContribution(sourceName, locationInSource, expirationTime, properties);
175 }
176 // There are some properties AND some children ...
177 return new NodeContribution(sourceName, locationInSource, expirationTime, properties, children);
178 }
179
180 /**
181 * Create a placeholder contribution of a single child from the named source.
182 *
183 * @param sourceName the name of the source, which may not be null or blank
184 * @param locationInSource the path in the source for this contributed information; may not be null
185 * @param expirationTime the time (in UTC) after which this contribution should be considered expired, or null if there is no
186 * expiration time
187 * @param child the child from the source; may not be null or empty
188 * @return the contribution
189 */
190 public static Contribution createPlaceholder( String sourceName,
191 Location locationInSource,
192 DateTime expirationTime,
193 Location child ) {
194 if (child == null) {
195 return new EmptyContribution(sourceName, expirationTime);
196 }
197 return new PlaceholderContribution(sourceName, locationInSource, expirationTime, Collections.singletonList(child));
198 }
199
200 /**
201 * Create a placeholder contribution of the supplied properties and children from the named source.
202 *
203 * @param sourceName the name of the source, which may not be null or blank
204 * @param locationInSource the path in the source for this contributed information; may not be null
205 * @param expirationTime the time (in UTC) after which this contribution should be considered expired, or null if there is no
206 * expiration time
207 * @param children the children from the source; may not be null or empty
208 * @return the contribution
209 */
210 public static Contribution createPlaceholder( String sourceName,
211 Location locationInSource,
212 DateTime expirationTime,
213 List<Location> children ) {
214 if (children == null || children.isEmpty()) {
215 return new EmptyContribution(sourceName, expirationTime);
216 }
217 return new PlaceholderContribution(sourceName, locationInSource, expirationTime, children);
218 }
219
220 /**
221 * This is the first version of this class. See the documentation of BasicMergePlan.serialVersionUID.
222 */
223 private static final long serialVersionUID = 1L;
224
225 protected static final Iterator<Property> EMPTY_PROPERTY_ITERATOR = new EmptyIterator<Property>();
226 protected static final Iterator<Location> EMPTY_CHILDREN_ITERATOR = new EmptyIterator<Location>();
227
228 private final String sourceName;
229 private DateTime expirationTimeInUtc;
230
231 /**
232 * Create a contribution for the source with the supplied name and path.
233 *
234 * @param sourceName the name of the source, which may not be null or blank
235 * @param expirationTime the time (in UTC) after which this contribution should be considered expired, or null if there is no
236 * expiration time
237 */
238 protected Contribution( String sourceName,
239 DateTime expirationTime ) {
240 assert sourceName != null && sourceName.trim().length() != 0;
241 assert expirationTime == null || expirationTime.equals(expirationTime.toUtcTimeZone());
242 this.sourceName = sourceName;
243 this.expirationTimeInUtc = expirationTime;
244 }
245
246 /**
247 * Get the name of the source that made this contribution.
248 *
249 * @return the name of the contributing source
250 */
251 public String getSourceName() {
252 return this.sourceName;
253 }
254
255 /**
256 * Get the source-specific location of this information.
257 *
258 * @return the location as known to the source, or null for {@link EmptyContribution}
259 */
260 public abstract Location getLocationInSource();
261
262 /**
263 * Determine whether this contribution has expired given the supplied current time.
264 *
265 * @param utcTime the current time expressed in UTC; may not be null
266 * @return true if at least one contribution has expired, or false otherwise
267 */
268 public boolean isExpired( DateTime utcTime ) {
269 assert utcTime != null;
270 assert utcTime.toUtcTimeZone().equals(utcTime); // check that it is passed UTC time
271 return !expirationTimeInUtc.isAfter(utcTime);
272 }
273
274 /**
275 * Get the expiration time, already in UTC.
276 *
277 * @return the expiration time in UTC
278 */
279 public DateTime getExpirationTimeInUtc() {
280 return this.expirationTimeInUtc;
281 }
282
283 /**
284 * Get the properties that are in this contribution. This resulting iterator does not support {@link Iterator#remove()
285 * removal}.
286 *
287 * @return the properties; never null
288 */
289 public Iterator<Property> getProperties() {
290 return EMPTY_PROPERTY_ITERATOR;
291 }
292
293 /**
294 * Get the number of properties that are in this contribution.
295 *
296 * @return the number of properties
297 */
298 public int getPropertyCount() {
299 return 0;
300 }
301
302 /**
303 * Get the contributed property with the supplied name.
304 *
305 * @param name the name of the property
306 * @return the contributed property that matches the name, or null if no such property is in the contribution
307 */
308 public Property getProperty( Name name ) {
309 return null;
310 }
311
312 /**
313 * Get the children that make up this contribution. This resulting iterator does not support {@link Iterator#remove() removal}
314 * .
315 *
316 * @return the children; never null
317 */
318 public Iterator<Location> getChildren() {
319 return EMPTY_CHILDREN_ITERATOR;
320 }
321
322 /**
323 * Get the number of children that make up this contribution.
324 *
325 * @return the number of children
326 */
327 public int getChildrenCount() {
328 return 0;
329 }
330
331 /**
332 * Return whether this contribution is an empty contribution.
333 *
334 * @return true if this contribution is empty, or false otherwise
335 */
336 public boolean isEmpty() {
337 return false;
338 }
339
340 /**
341 * Determine whether this contribution is considered a placeholder necessary solely because the same source has contributions
342 * at or below the children.
343 *
344 * @return true if a placeholder contribution, or false otherwise
345 */
346 public boolean isPlaceholder() {
347 return false;
348 }
349
350 /**
351 * {@inheritDoc}
352 * <p>
353 * This implementation returns the hash code of the {@link #getSourceName() source name}, and is compatible with the
354 * implementation of {@link #equals(Object)}.
355 * </p>
356 */
357 @Override
358 public int hashCode() {
359 return this.sourceName.hashCode();
360 }
361
362 /**
363 * {@inheritDoc}
364 *
365 * @see java.lang.Object#toString()
366 */
367 @Override
368 public String toString() {
369 StringBuffer sb = new StringBuffer();
370 sb.append("Contribution from \"");
371 sb.append(getSourceName());
372 if (isExpired(new JodaDateTime().toUtcTimeZone())) {
373 sb.append("\": expired ");
374 } else {
375 sb.append("\": expires ");
376 }
377 sb.append(getExpirationTimeInUtc().getString());
378 if (getPropertyCount() != 0) {
379 sb.append(" { ");
380 boolean first = true;
381 Iterator<Property> propIter = getProperties();
382 while (propIter.hasNext()) {
383 if (!first) sb.append(", ");
384 else first = false;
385 sb.append(propIter.next());
386 }
387 sb.append(" }");
388 }
389 if (getChildrenCount() != 0) {
390 sb.append("< ");
391 boolean first = true;
392 Iterator<Location> childIter = getChildren();
393 while (childIter.hasNext()) {
394 if (!first) sb.append(", ");
395 else first = false;
396 sb.append(childIter.next());
397 }
398 sb.append(" >");
399 }
400 return sb.toString();
401 }
402
403 /**
404 * {@inheritDoc}
405 * <p>
406 * This implementation only compares the {@link #getSourceName() source name}.
407 * </p>
408 */
409 @Override
410 public boolean equals( Object obj ) {
411 if (obj == this) return true;
412 if (obj instanceof Contribution) {
413 Contribution that = (Contribution)obj;
414 if (!this.getSourceName().equals(that.getSourceName())) return false;
415 return true;
416 }
417 return false;
418 }
419
420 protected static class ImmutableIterator<T> implements Iterator<T> {
421 private final Iterator<T> iter;
422
423 protected ImmutableIterator( Iterator<T> iter ) {
424 this.iter = iter;
425 }
426
427 /**
428 * {@inheritDoc}
429 *
430 * @see java.util.Iterator#hasNext()
431 */
432 public boolean hasNext() {
433 return iter.hasNext();
434 }
435
436 /**
437 * {@inheritDoc}
438 *
439 * @see java.util.Iterator#next()
440 */
441 public T next() {
442 return iter.next();
443 }
444
445 /**
446 * {@inheritDoc}
447 *
448 * @see java.util.Iterator#remove()
449 */
450 public void remove() {
451 throw new UnsupportedOperationException();
452 }
453 }
454
455 protected static class EmptyIterator<T> implements Iterator<T> {
456
457 /**
458 * {@inheritDoc}
459 *
460 * @see java.util.Iterator#hasNext()
461 */
462 public boolean hasNext() {
463 return false;
464 }
465
466 /**
467 * {@inheritDoc}
468 *
469 * @see java.util.Iterator#next()
470 */
471 public T next() {
472 throw new NoSuchElementException();
473 }
474
475 /**
476 * {@inheritDoc}
477 *
478 * @see java.util.Iterator#remove()
479 */
480 public void remove() {
481 throw new UnsupportedOperationException();
482 }
483
484 }
485
486 protected static class OneValueIterator<T> implements Iterator<T> {
487
488 private final T value;
489 private boolean next = true;
490
491 protected OneValueIterator( T value ) {
492 assert value != null;
493 this.value = value;
494 }
495
496 /**
497 * {@inheritDoc}
498 *
499 * @see java.util.Iterator#hasNext()
500 */
501 public boolean hasNext() {
502 return next;
503 }
504
505 /**
506 * {@inheritDoc}
507 *
508 * @see java.util.Iterator#next()
509 */
510 public T next() {
511 if (next) {
512 next = false;
513 return value;
514 }
515 throw new NoSuchElementException();
516 }
517
518 /**
519 * {@inheritDoc}
520 *
521 * @see java.util.Iterator#remove()
522 */
523 public void remove() {
524 throw new UnsupportedOperationException();
525 }
526
527 }
528
529 protected static class TwoValueIterator<T> implements Iterator<T> {
530
531 private final T value1;
532 private final T value2;
533 private int next = 2;
534
535 protected TwoValueIterator( T value1,
536 T value2 ) {
537 this.value1 = value1;
538 this.value2 = value2;
539 }
540
541 /**
542 * {@inheritDoc}
543 *
544 * @see java.util.Iterator#hasNext()
545 */
546 public boolean hasNext() {
547 return next > 0;
548 }
549
550 /**
551 * {@inheritDoc}
552 *
553 * @see java.util.Iterator#next()
554 */
555 public T next() {
556 if (next == 2) {
557 next = 1;
558 return value1;
559 }
560 if (next == 1) {
561 next = 0;
562 return value2;
563 }
564 throw new NoSuchElementException();
565 }
566
567 /**
568 * {@inheritDoc}
569 *
570 * @see java.util.Iterator#remove()
571 */
572 public void remove() {
573 throw new UnsupportedOperationException();
574 }
575 }
576
577 protected static class ThreeValueIterator<T> implements Iterator<T> {
578
579 private final T value1;
580 private final T value2;
581 private final T value3;
582 private int next = 3;
583
584 protected ThreeValueIterator( T value1,
585 T value2,
586 T value3 ) {
587 this.value1 = value1;
588 this.value2 = value2;
589 this.value3 = value3;
590 }
591
592 /**
593 * {@inheritDoc}
594 *
595 * @see java.util.Iterator#hasNext()
596 */
597 public boolean hasNext() {
598 return next > 0;
599 }
600
601 /**
602 * {@inheritDoc}
603 *
604 * @see java.util.Iterator#next()
605 */
606 public T next() {
607 if (next == 3) {
608 next = 2;
609 return value1;
610 }
611 if (next == 2) {
612 next = 1;
613 return value2;
614 }
615 if (next == 1) {
616 next = 0;
617 return value3;
618 }
619 throw new NoSuchElementException();
620 }
621
622 /**
623 * {@inheritDoc}
624 *
625 * @see java.util.Iterator#remove()
626 */
627 public void remove() {
628 throw new UnsupportedOperationException();
629 }
630
631 }
632 }