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