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