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.merge.strategy;
023    
024    import java.util.Iterator;
025    import java.util.List;
026    import java.util.UUID;
027    import net.jcip.annotations.ThreadSafe;
028    import org.jboss.dna.connector.federation.contribution.Contribution;
029    import org.jboss.dna.connector.federation.merge.FederatedNode;
030    import org.jboss.dna.connector.federation.merge.MergePlan;
031    import org.jboss.dna.graph.DnaLexicon;
032    import org.jboss.dna.graph.ExecutionContext;
033    import org.jboss.dna.graph.Location;
034    import org.jboss.dna.graph.properties.Path;
035    import org.jboss.dna.graph.properties.PathFactory;
036    import org.jboss.dna.graph.properties.Property;
037    import org.jboss.dna.graph.properties.ValueFormatException;
038    
039    /**
040     * A merge strategy that is optimized for merging when there is a single contribution.
041     * 
042     * @author Randall Hauch
043     */
044    @ThreadSafe
045    public class OneContributionMergeStrategy implements MergeStrategy {
046    
047        /**
048         * {@inheritDoc}
049         * <p>
050         * This method only uses the one and only one non-null {@link Contribution} in the <code>contributions</code>.
051         * </p>
052         * 
053         * @see org.jboss.dna.connector.federation.merge.strategy.MergeStrategy#merge(org.jboss.dna.connector.federation.merge.FederatedNode,
054         *      java.util.List, org.jboss.dna.graph.ExecutionContext)
055         */
056        public void merge( FederatedNode federatedNode,
057                           List<Contribution> contributions,
058                           ExecutionContext context ) {
059            assert federatedNode != null;
060            assert context != null;
061            assert contributions != null;
062            assert contributions.size() > 0;
063            Contribution contribution = contributions.get(0);
064            assert contribution != null;
065            final PathFactory pathFactory = context.getValueFactories().getPathFactory();
066            final Location location = federatedNode.getActualLocationOfNode();
067    
068            // Copy the children ...
069            Iterator<Location> childIterator = contribution.getChildren();
070            while (childIterator.hasNext()) {
071                Location child = translateChildFromSourceToRepository(pathFactory, location, childIterator.next());
072                federatedNode.addChild(child);
073            }
074    
075            // Copy the properties ...
076            Property uuidProperty = null;
077            Property dnaUuidProperty = null;
078            Iterator<Property> propertyIterator = contribution.getProperties();
079            while (propertyIterator.hasNext()) {
080                Property property = propertyIterator.next();
081                federatedNode.addProperty(property);
082                if (property.isSingle()) {
083                    if (property.getName().equals(DnaLexicon.UUID) && hasUuidValue(context, property)) {
084                        dnaUuidProperty = property;
085                    } else if (property.getName().getLocalName().equals("uuid") && hasUuidValue(context, property)) {
086                        uuidProperty = property;
087                    }
088                }
089            }
090            if (dnaUuidProperty != null) uuidProperty = dnaUuidProperty; // use "dna:uuid" if there is one
091    
092            // Look for the UUID property on the properties, and update the federated node ...
093            if (uuidProperty != null && !uuidProperty.isEmpty()) {
094                UUID uuid = context.getValueFactories().getUuidFactory().create(uuidProperty.getValues().next());
095                federatedNode.setUuid(uuid);
096                if (dnaUuidProperty == null) {
097                    uuidProperty = context.getPropertyFactory().create(DnaLexicon.UUID, uuid); // Use the "dna:uuid" name
098                }
099                federatedNode.setActualLocationOfNode(federatedNode.getActualLocationOfNode().with(uuidProperty));
100            } else {
101                // See if there is a UUID property on the location and update the federated node with it...
102                uuidProperty = federatedNode.getActualLocationOfNode().getIdProperty(DnaLexicon.UUID);
103                if (uuidProperty == null || uuidProperty.isEmpty()) {
104                    // Generate a new UUID property and add to the node ...
105                    UUID uuid = federatedNode.getUuid();
106                    if (uuid == null) {
107                        uuid = context.getValueFactories().getUuidFactory().create();
108                        federatedNode.setUuid(uuid);
109                    }
110                    uuidProperty = context.getPropertyFactory().create(DnaLexicon.UUID, uuid);
111                }
112                // Set the UUID as a property ...
113                federatedNode.addProperty(uuidProperty);
114            }
115    
116            // Assign the merge plan ...
117            MergePlan mergePlan = MergePlan.create(contributions);
118            federatedNode.setMergePlan(mergePlan);
119        }
120    
121        private boolean hasUuidValue( ExecutionContext context,
122                                      Property property ) {
123            assert property.isSingle();
124            try {
125                context.getValueFactories().getUuidFactory().create(property.getValues().next());
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    }