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