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