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