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;
025    
026    import java.util.ArrayList;
027    import java.util.Collections;
028    import java.util.HashMap;
029    import java.util.Iterator;
030    import java.util.LinkedList;
031    import java.util.List;
032    import java.util.Map;
033    import net.jcip.annotations.Immutable;
034    import org.jboss.dna.common.collection.Problems;
035    import org.jboss.dna.common.collection.ThreadSafeProblems;
036    import org.jboss.dna.common.util.CheckArg;
037    import org.jboss.dna.connector.federation.merge.strategy.MergeStrategy;
038    import org.jboss.dna.connector.federation.merge.strategy.OneContributionMergeStrategy;
039    import org.jboss.dna.connector.federation.merge.strategy.SimpleMergeStrategy;
040    import org.jboss.dna.graph.cache.CachePolicy;
041    import org.jboss.dna.graph.connector.RepositorySource;
042    
043    /**
044     * The configuration of a federated repository. workspace The configuration defines, among other things, the set of
045     * {@link #getSourceProjections() source projections} in the federated workspace that each specify how and where content from a
046     * {@link RepositorySource source} is federated into the unified workspace.
047     * 
048     * @author Randall Hauch
049     */
050    @Immutable
051    public class FederatedWorkspace implements Comparable<FederatedWorkspace> {
052    
053        private final Projection cacheProjection;
054        private final CachePolicy cachePolicy;
055        private final List<Projection> sourceProjections;
056        private final Map<String, List<Projection>> projectionsBySourceName;
057        private final Problems problems;
058        private final String name;
059        private final MergeStrategy mergingStrategy;
060    
061        /**
062         * Create a configuration for a federated workspace.
063         * 
064         * @param workspaceName the name of the federated workspace; may not be null
065         * @param cacheProjection the projection used for the cache; may not be null
066         * @param sourceProjections the source projections; may not be null
067         * @param cachePolicy the cache policy for this workspace; may be null if there is no policy
068         * @throws IllegalArgumentException if the name is null or is blank
069         */
070        public FederatedWorkspace( String workspaceName,
071                                   Projection cacheProjection,
072                                   Iterable<Projection> sourceProjections,
073                                   CachePolicy cachePolicy ) {
074            this(workspaceName, cacheProjection, sourceProjections, cachePolicy, null);
075        }
076    
077        /**
078         * Create a configuration for a federated workspace.
079         * 
080         * @param workspaceName the name of the federated workspace; may not be null
081         * @param cacheProjection the projection used for the cache; may not be null
082         * @param sourceProjections the source projections; may not be null
083         * @param cachePolicy the cache policy for this workspace; may be null if there is no policy
084         * @param mergeStrategy the strategy that should be used to merge nodes, or null if the strategy should be chosen based
085         *        automatically based upon the number of sources used by the projections
086         * @throws IllegalArgumentException if the name is null or is blank
087         */
088        public FederatedWorkspace( String workspaceName,
089                                   Projection cacheProjection,
090                                   Iterable<Projection> sourceProjections,
091                                   CachePolicy cachePolicy,
092                                   MergeStrategy mergeStrategy ) {
093            CheckArg.isNotNull(workspaceName, "workspaceName");
094            CheckArg.isNotNull(cacheProjection, "cacheProjection");
095            this.name = workspaceName;
096            this.cachePolicy = cachePolicy;
097            this.problems = new ThreadSafeProblems();
098            this.cacheProjection = cacheProjection;
099            List<Projection> projectionList = new ArrayList<Projection>();
100            for (Projection projection : sourceProjections) {
101                if (projection == null) continue;
102                if (!projectionList.contains(projection)) {
103                    projectionList.add(projection);
104                }
105            }
106            this.sourceProjections = Collections.unmodifiableList(projectionList);
107            CheckArg.isNotEmpty(this.sourceProjections, "sourceProjections");
108            this.projectionsBySourceName = new HashMap<String, List<Projection>>();
109            for (Projection projection : this.sourceProjections) {
110                String sourceName = projection.getSourceName();
111                List<Projection> projectionsForSource = projectionsBySourceName.get(sourceName);
112                if (projectionsForSource == null) {
113                    projectionsForSource = new LinkedList<Projection>();
114                    projectionsBySourceName.put(sourceName, projectionsForSource);
115                }
116                projectionsForSource.add(projection);
117            }
118            if (mergeStrategy != null) {
119                this.mergingStrategy = mergeStrategy;
120            } else {
121                if (this.sourceProjections.size() == 1 && this.sourceProjections.get(0).isSimple()) {
122                    this.mergingStrategy = new OneContributionMergeStrategy();
123                } else {
124                    this.mergingStrategy = new SimpleMergeStrategy();
125                }
126            }
127            assert this.mergingStrategy != null;
128        }
129    
130        /**
131         * Get the name of this repository
132         * 
133         * @return name
134         */
135        public String getName() {
136            return this.name;
137        }
138    
139        /**
140         * Get the cache policy for this workspace
141         * 
142         * @return the workspace's cache policy; may be null
143         */
144        public CachePolicy getCachePolicy() {
145            return cachePolicy;
146        }
147    
148        /**
149         * Get the merging strategy used for this workspace
150         * 
151         * @return the workspace's merging strategy; never null
152         */
153        public MergeStrategy getMergingStrategy() {
154            return mergingStrategy;
155        }
156    
157        /**
158         * Return the problem associated with this configuration. These problems may change at any time, although the returned
159         * {@link Problems} object is thread-safe.
160         * 
161         * @return the thread-safe problems for this configuration
162         */
163        public Problems getProblems() {
164            return problems;
165        }
166    
167        /**
168         * Get the projection that defines the cache for this repository. This projection does not exist in the
169         * {@link #getSourceProjections() list of source projections}.
170         * 
171         * @return the region used for caching; never null
172         */
173        public Projection getCacheProjection() {
174            return cacheProjection;
175        }
176    
177        /**
178         * Return the unmodifiable list of source projections.
179         * 
180         * @return the source projections; never null and never empty
181         */
182        public List<Projection> getSourceProjections() {
183            return sourceProjections;
184        }
185    
186        /**
187         * Return the unmodifiable list of projections for the source name.
188         * 
189         * @param sourceName the name of the source
190         * @return the list of projections for this source, or null if there are none
191         */
192        public List<Projection> getProjectionsFor( String sourceName ) {
193            return this.projectionsBySourceName.get(sourceName);
194        }
195    
196        /**
197         * Determine whether this workspace has a projection supplied contribution
198         * 
199         * @param sourceName the name of the source
200         * @param workspaceName the name of the workspace
201         * @return true if this workspace contains a projection that uses the supplied source and workspace
202         */
203        public boolean contains( String sourceName,
204                                 String workspaceName ) {
205            List<Projection> projections = this.projectionsBySourceName.get(sourceName);
206            if (projections != null) {
207                for (Projection projection : sourceProjections) {
208                    if (projection.getWorkspaceName().equals(workspaceName)) return true;
209                }
210            }
211            return false;
212        }
213    
214        /**
215         * {@inheritDoc}
216         * 
217         * @see java.lang.Object#hashCode()
218         */
219        @Override
220        public int hashCode() {
221            return this.name.hashCode();
222        }
223    
224        /**
225         * {@inheritDoc}
226         * 
227         * @see java.lang.Object#equals(java.lang.Object)
228         */
229        @Override
230        public boolean equals( Object obj ) {
231            if (obj == this) return true;
232            if (obj instanceof FederatedWorkspace) {
233                FederatedWorkspace that = (FederatedWorkspace)obj;
234                if (!this.getName().equals(that.getName())) return false;
235                if (!this.getCacheProjection().equals(that.getCacheProjection())) return false;
236                if (!this.getSourceProjections().equals(that.getSourceProjections())) return false;
237                return true;
238            }
239            return false;
240        }
241    
242        /**
243         * {@inheritDoc}
244         * 
245         * @see java.lang.Comparable#compareTo(java.lang.Object)
246         */
247        public int compareTo( FederatedWorkspace that ) {
248            if (that == this) return 0;
249            int diff = this.getName().compareTo(that.getName());
250            if (diff != 0) return diff;
251            diff = this.getCacheProjection().compareTo(that.getCacheProjection());
252            if (diff != 0) return diff;
253            Iterator<Projection> thisIter = this.getSourceProjections().iterator();
254            Iterator<Projection> thatIter = that.getSourceProjections().iterator();
255            while (thisIter.hasNext() && thatIter.hasNext()) {
256                diff = thisIter.next().compareTo(thatIter.next());
257                if (diff != 0) return diff;
258            }
259            if (thisIter.hasNext()) return 1;
260            if (thatIter.hasNext()) return -1;
261            return 0;
262        }
263    }