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    }