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    }