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.merge.strategy;
025    
026    import java.util.Iterator;
027    import java.util.List;
028    import java.util.UUID;
029    import net.jcip.annotations.ThreadSafe;
030    import org.jboss.dna.connector.federation.contribution.Contribution;
031    import org.jboss.dna.connector.federation.merge.FederatedNode;
032    import org.jboss.dna.connector.federation.merge.MergePlan;
033    import org.jboss.dna.graph.DnaLexicon;
034    import org.jboss.dna.graph.ExecutionContext;
035    import org.jboss.dna.graph.Location;
036    import org.jboss.dna.graph.property.Path;
037    import org.jboss.dna.graph.property.PathFactory;
038    import org.jboss.dna.graph.property.Property;
039    import org.jboss.dna.graph.property.ValueFormatException;
040    
041    /**
042     * A merge strategy that is optimized for merging when there is a single contribution.
043     * 
044     * @author Randall Hauch
045     */
046    @ThreadSafe
047    public class OneContributionMergeStrategy implements MergeStrategy {
048    
049        /**
050         * {@inheritDoc}
051         * <p>
052         * This method only uses the one and only one non-null {@link Contribution} in the <code>contributions</code>.
053         * </p>
054         * 
055         * @see org.jboss.dna.connector.federation.merge.strategy.MergeStrategy#merge(org.jboss.dna.connector.federation.merge.FederatedNode,
056         *      java.util.List, org.jboss.dna.graph.ExecutionContext)
057         */
058        public void merge( FederatedNode federatedNode,
059                           List<Contribution> contributions,
060                           ExecutionContext context ) {
061            assert federatedNode != null;
062            assert context != null;
063            assert contributions != null;
064            assert contributions.size() > 0;
065            Contribution contribution = contributions.get(0);
066            assert contribution != null;
067            final PathFactory pathFactory = context.getValueFactories().getPathFactory();
068            Location location = federatedNode.getActualLocationOfNode();
069    
070            // Copy the children ...
071            Iterator<Location> childIterator = contribution.getChildren();
072            while (childIterator.hasNext()) {
073                Location child = translateChildFromSourceToRepository(pathFactory, location, childIterator.next());
074                federatedNode.addChild(child);
075            }
076    
077            // Copy the properties ...
078            Property uuidProperty = null;
079            Property dnaUuidProperty = null;
080            Iterator<Property> propertyIterator = contribution.getProperties();
081            while (propertyIterator.hasNext()) {
082                Property property = propertyIterator.next();
083                federatedNode.addProperty(property);
084                if (property.isSingle()) {
085                    if (property.getName().equals(DnaLexicon.UUID) && hasUuidValue(context, property)) {
086                        dnaUuidProperty = property;
087                    } else if (property.getName().getLocalName().equals("uuid") && hasUuidValue(context, property)) {
088                        uuidProperty = property;
089                    }
090                }
091            }
092            if (dnaUuidProperty != null) uuidProperty = dnaUuidProperty; // use "dna:uuid" if there is one
093    
094            if (federatedNode.at().getUuid() != null) {
095                federatedNode.setActualLocationOfNode(federatedNode.at());
096            } else {
097                // Look for the UUID property on the properties, and update the federated node ...
098                if (uuidProperty != null && !uuidProperty.isEmpty()) {
099                    UUID uuid = context.getValueFactories().getUuidFactory().create(uuidProperty.getFirstValue());
100                    uuidProperty = context.getPropertyFactory().create(DnaLexicon.UUID, uuid); // Use the "dna:uuid" name
101                    federatedNode.setActualLocationOfNode(federatedNode.at().with(uuidProperty));
102                } else {
103                    // Make sure there's a UUID for an identification property ...
104                    if (location.getUuid() == null) {
105                        location = location.with(UUID.randomUUID());
106                    }
107                    // Set the UUID as a property (it wasn't set already) ...
108                    uuidProperty = location.getIdProperty(DnaLexicon.UUID);
109                    assert uuidProperty != null; // there should be one!
110                    federatedNode.addProperty(uuidProperty);
111                    federatedNode.setActualLocationOfNode(location);
112                }
113            }
114    
115            // Assign the merge plan ...
116            MergePlan mergePlan = MergePlan.create(contributions);
117            federatedNode.setMergePlan(mergePlan);
118            Property mergePlanProperty = context.getPropertyFactory().create(DnaLexicon.MERGE_PLAN, (Object)mergePlan);
119            federatedNode.addProperty(mergePlanProperty);
120        }
121    
122        private boolean hasUuidValue( ExecutionContext context,
123                                      Property property ) {
124            try {
125                context.getValueFactories().getUuidFactory().create(property.getFirstValue());
126                return true;
127            } catch (ValueFormatException e) {
128                return false;
129            }
130        }
131    
132        /**
133         * Utility method to translate the list of locations of the children so that the locations all are correctly relative to
134         * parent location of the federated node.
135         * 
136         * @param factory the path factory
137         * @param parent the parent of the child
138         * @param childInSource the child to be translated, with a source-specific location
139         * @return the list of locations of each child
140         */
141        protected Location translateChildFromSourceToRepository( PathFactory factory,
142                                                                 Location parent,
143                                                                 Location childInSource ) {
144            // Convert the locations of the children (relative to the source) to be relative to this node
145            Path parentPath = parent.getPath();
146            if (parentPath == null) return childInSource;
147            Path newPath = factory.create(parentPath, childInSource.getPath().getLastSegment());
148            return childInSource.with(newPath);
149        }
150    }