Package org.jboss.dna.connector.federation.merge

Source Code of org.jboss.dna.connector.federation.merge.MergePlan

/*
* JBoss, Home of Professional Open Source.
* Copyright 2008, Red Hat Middleware LLC, and individual contributors
* as indicated by the @author tags. See the copyright.txt file in the
* distribution for a full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.jboss.dna.connector.federation.merge;

import java.io.InvalidClassException;
import java.io.Serializable;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import net.jcip.annotations.GuardedBy;
import net.jcip.annotations.ThreadSafe;
import org.jboss.dna.common.CommonI18n;
import org.jboss.dna.common.util.CheckArg;
import org.jboss.dna.common.util.StringUtil;
import org.jboss.dna.connector.federation.contribution.Contribution;
import org.jboss.dna.connector.federation.contribution.EmptyContribution;
import org.jboss.dna.graph.properties.DateTime;
import org.jboss.dna.graph.properties.Name;
import org.jboss.dna.graph.properties.Property;

/**
* This class represents the details about how information from different sources are merged into a single federated node.
* <p>
* A merge plan basically consists of the individual contribution from each source and the information about how these
* contributions were merged into the single federated node.
* </p>
* <p>
* Merge plans are designed to be {@link Serializable serializable}, as they are persisted on the federated node and deserialized
* to assist in the management of the federated node.
* </p>
*
* @author Randall Hauch
*/
@ThreadSafe
public abstract class MergePlan implements Serializable, Iterable<Contribution> {

    public static MergePlan create( Contribution... contributions ) {
        CheckArg.isNotNull(contributions, "contributions");
        switch (contributions.length) {
            case 0:
                throw new IllegalArgumentException(CommonI18n.argumentMayNotBeEmpty.text("contributions"));
            case 1:
                return new OneContributionMergePlan(contributions[0]);
            case 2:
                return new TwoContributionMergePlan(contributions[0], contributions[1]);
            case 3:
                return new ThreeContributionMergePlan(contributions[0], contributions[1], contributions[2]);
            case 4:
                return new FourContributionMergePlan(contributions[0], contributions[1], contributions[2], contributions[3]);
            case 5:
                return new FiveContributionMergePlan(contributions[0], contributions[1], contributions[2], contributions[3],
                                                     contributions[4]);
            default:
                return new MultipleContributionMergePlan(contributions);
        }
    }

    public static MergePlan create( Collection<Contribution> contributions ) {
        CheckArg.isNotNull(contributions, "contributions");
        Iterator<Contribution> iter = contributions.iterator();
        switch (contributions.size()) {
            case 0:
                throw new IllegalArgumentException(CommonI18n.argumentMayNotBeEmpty.text("contributions"));
            case 1:
                return new OneContributionMergePlan(iter.next());
            case 2:
                return new TwoContributionMergePlan(iter.next(), iter.next());
            case 3:
                return new ThreeContributionMergePlan(iter.next(), iter.next(), iter.next());
            case 4:
                return new FourContributionMergePlan(iter.next(), iter.next(), iter.next(), iter.next());
            case 5:
                return new FiveContributionMergePlan(iter.next(), iter.next(), iter.next(), iter.next(), iter.next());
            default:
                return new MultipleContributionMergePlan(contributions);
        }
    }

    public static MergePlan addContribution( MergePlan plan,
                                             Contribution contribution ) {
        CheckArg.isNotNull(plan, "plan");
        CheckArg.isNotNull(contribution, "contribution");
        if (plan instanceof MultipleContributionMergePlan) {
            ((MultipleContributionMergePlan)plan).addContribution(contribution);
            return plan;
        }
        MergePlan newPlan = null;
        if (plan instanceof OneContributionMergePlan) {
            newPlan = new TwoContributionMergePlan(plan.iterator().next(), contribution);
        } else if (plan instanceof TwoContributionMergePlan) {
            Iterator<Contribution> iter = plan.iterator();
            newPlan = new ThreeContributionMergePlan(iter.next(), iter.next(), contribution);
        } else if (plan instanceof ThreeContributionMergePlan) {
            Iterator<Contribution> iter = plan.iterator();
            newPlan = new FourContributionMergePlan(iter.next(), iter.next(), iter.next(), contribution);
        } else if (plan instanceof FourContributionMergePlan) {
            Iterator<Contribution> iter = plan.iterator();
            newPlan = new FiveContributionMergePlan(iter.next(), iter.next(), iter.next(), iter.next(), contribution);
        } else {
            MultipleContributionMergePlan multiPlan = new MultipleContributionMergePlan();
            for (Contribution existingContribution : plan) {
                multiPlan.addContribution(existingContribution);
            }
            multiPlan.addContribution(contribution);
            newPlan = multiPlan;
        }
        newPlan.setAnnotations(plan.getAnnotations());
        return newPlan;
    }

    /**
     * Define the earliest version of this class that is supported. The Java runtime, upon deserialization, compares the
     * serialized object's version to this, and if less than this version will throw a {@link InvalidClassException}. If, however,
     * the serialized object's version is compatible with this class, it will be deserialized successfully.
     * <p>
     * <a href="http://java.sun.com/j2se/1.5.0/docs/guide/serialization/spec/version.html#6678">Sun's documentation</a> describes
     * the following changes can be made without negatively affecting the deserialization of older versions:
     * <ul>
     * <li>Adding fields - When the class being reconstituted has a field that does not occur in the stream, that field in the
     * object will be initialized to the default value for its type. If class-specific initialization is needed, the class may
     * provide a readObject method that can initialize the field to nondefault values.</i>
     * <li>Adding classes - The stream will contain the type hierarchy of each object in the stream. Comparing this hierarchy in
     * the stream with the current class can detect additional classes. Since there is no information in the stream from which to
     * initialize the object, the class's fields will be initialized to the default values.</i>
     * <li>Removing classes - Comparing the class hierarchy in the stream with that of the current class can detect that a class
     * has been deleted. In this case, the fields and objects corresponding to that class are read from the stream. Primitive
     * fields are discarded, but the objects referenced by the deleted class are created, since they may be referred to later in
     * the stream. They will be garbage-collected when the stream is garbage-collected or reset.</i>
     * <li>Adding writeObject/readObject methods - If the version reading the stream has these methods then readObject is
     * expected, as usual, to read the required data written to the stream by the default serialization. It should call
     * defaultReadObject first before reading any optional data. The writeObject method is expected as usual to call
     * defaultWriteObject to write the required data and then may write optional data.</i>
     * <li>Removing writeObject/readObject methods - If the class reading the stream does not have these methods, the required
     * data will be read by default serialization, and the optional data will be discarded.</i>
     * <li>Adding java.io.Serializable - This is equivalent to adding types. There will be no values in the stream for this class
     * so its fields will be initialized to default values. The support for subclassing nonserializable classes requires that the
     * class's supertype have a no-arg constructor and the class itself will be initialized to default values. If the no-arg
     * constructor is not available, the InvalidClassException is thrown.</i>
     * <li>Changing the access to a field - The access modifiers public, package, protected, and private have no effect on the
     * ability of serialization to assign values to the fields.</i>
     * <li>Changing a field from static to nonstatic or transient to nontransient - When relying on default serialization to
     * compute the serializable fields, this change is equivalent to adding a field to the class. The new field will be written to
     * the stream but earlier classes will ignore the value since serialization will not assign values to static or transient
     * fields.</i>
     * </ul>
     * All other kinds of modifications should be avoided.
     * </p>
     */
    private static final long serialVersionUID = 1L;

    private final ReadWriteLock annotationLock = new ReentrantReadWriteLock();
    private DateTime expirationTimeInUtc;
    @GuardedBy( "annotationLock" )
    private Map<Name, Property> annotations = null;

    /**
     * Create an empty merge plan
     */
    protected MergePlan() {
    }

    /**
     * Determine whether this merge plan has expired given the supplied current time. The {@link #getExpirationTimeInUtc()
     * expiration time} is the earliest time that any of the {@link #getContributionFrom(String) contributions}
     * {@link Contribution#getExpirationTimeInUtc()}.
     *
     * @param utcTime the current time expressed in UTC; may not be null
     * @return true if at least one contribution has expired, or false otherwise
     */
    public boolean isExpired( DateTime utcTime ) {
        assert utcTime != null;
        assert utcTime.toUtcTimeZone().equals(utcTime); // check that it is passed UTC time
        return utcTime.isAfter(getExpirationTimeInUtc());
    }

    /**
     * Get the expiration time (in UTC) that is the earliest time that any of the {@link #getContributionFrom(String)
     * contributions} {@link Contribution#getExpirationTimeInUtc()}.
     *
     * @return the expiration time in UTC, or null if there is no known expiration time
     */
    public DateTime getExpirationTimeInUtc() {
        if (expirationTimeInUtc == null) {
            // This is computed regardless of a lock, since it's not expensive and idempotent
            DateTime earliest = null;
            for (Contribution contribution : this) {
                DateTime contributionTime = contribution.getExpirationTimeInUtc();
                if (earliest == null || (contributionTime != null && contributionTime.isBefore(earliest))) {
                    earliest = contributionTime;
                }
            }
            expirationTimeInUtc = earliest;
        }
        return expirationTimeInUtc;
    }

    /**
     * Get the contribution from the source with the supplied name. Note that contributions always include sources that contribute
     * information and sources that contribute no information. If a source is not included in this list, its contributions are
     * <i>unknown</i>; that is, it is unknown whether that source does or does not contribute to the node.
     *
     * @param sourceName the name of the source
     * @return the contribution, or null if the contribution of the source is unknown
     */
    public abstract Contribution getContributionFrom( String sourceName );

    /**
     * Return whether the named source was consulted for a contribution.
     *
     * @param sourceName the name of the source
     * @return true if the source has some {@link Contribution contribution} (even if it is an {@link EmptyContribution})
     */
    public abstract boolean isSource( String sourceName );

    public abstract int getContributionCount();

    /**
     * Get the plan annotation property with the given name. Plan annotations are custom properties that may be set by
     * MergeProcessor implementations to store custom properties on the plan. This method does nothing if the supplied name is
     * null
     *
     * @param name the name of the annotation
     * @return the existing annotation, or null if there is no annotation with the supplied name
     * @see #setAnnotation(Property)
     */
    public Property getAnnotation( Name name ) {
        if (name == null) return null;
        try {
            annotationLock.readLock().lock();
            if (this.annotations == null) return null;
            return this.annotations.get(name);
        } finally {
            annotationLock.readLock().unlock();
        }
    }

    /**
     * Set the plan annotation property. This method replaces and returns any existing annotation property with the same name.
     * This method also returns immediately if the supplied annotation is null.
     *
     * @param annotation the new annotation
     * @return the previous annotation property with the same name, or null if there was no previous annotation property for the
     *         name
     * @see #getAnnotation(Name)
     */
    public Property setAnnotation( Property annotation ) {
        if (annotation == null) return null;
        try {
            annotationLock.writeLock().lock();
            if (this.annotations == null) {
                this.annotations = new HashMap<Name, Property>();
            }
            return this.annotations.put(annotation.getName(), annotation);
        } finally {
            annotationLock.writeLock().unlock();
        }
    }

    /**
     * Get the number of annotations.
     *
     * @return the number of annotations
     */
    public int getAnnotationCount() {
        try {
            annotationLock.readLock().lock();
            if (this.annotations == null) return 0;
            return this.annotations.size();
        } finally {
            annotationLock.readLock().unlock();
        }
    }

    /**
     * Get the set of annotation {@link Name names}.
     *
     * @return the unmodifiable set of names, or an empty set if there are no annotations
     */
    public Set<Name> getAnnotationNames() {
        try {
            annotationLock.readLock().lock();
            if (this.annotations == null) return Collections.emptySet();
            return Collections.unmodifiableSet(this.annotations.keySet());
        } finally {
            annotationLock.readLock().unlock();
        }
    }

    /**
     * Set the annotations. This
     *
     * @param annotations
     */
    protected void setAnnotations( Map<Name, Property> annotations ) {
        try {
            annotationLock.writeLock().lock();
            this.annotations = annotations == null || annotations.isEmpty() ? null : annotations;
        } finally {
            annotationLock.writeLock().unlock();
        }
    }

    /**
     * Get a copy of the annotations.
     *
     * @return a copy of annotations; never null
     */
    public Map<Name, Property> getAnnotations() {
        Map<Name, Property> result = null;
        try {
            annotationLock.writeLock().lock();
            if (this.annotations != null && !this.annotations.isEmpty()) {
                result = new HashMap<Name, Property>(this.annotations);
            } else {
                result = Collections.emptyMap();
            }
        } finally {
            annotationLock.writeLock().unlock();
        }
        return result;
    }

    /**
     * {@inheritDoc}
     *
     * @see java.lang.Object#toString()
     */
    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        boolean first = true;
        for (Contribution contribution : this) {
            if (!first) {
                first = false;
                sb.append(", ");
            }
            sb.append(contribution);
        }
        sb.append(StringUtil.readableString(getAnnotations()));
        return sb.toString();
    }

    /**
     * {@inheritDoc}
     *
     * @see java.lang.Object#equals(java.lang.Object)
     */
    @Override
    public boolean equals( Object obj ) {
        if (obj == this) return true;
        if (obj instanceof MergePlan) {
            MergePlan that = (MergePlan)obj;
            if (this.getContributionCount() != that.getContributionCount()) return false;
            Iterator<Contribution> thisContribution = this.iterator();
            Iterator<Contribution> thatContribution = that.iterator();
            while (thisContribution.hasNext() && thatContribution.hasNext()) {
                if (!thisContribution.next().equals(thatContribution.next())) return false;
            }
            if (this.getAnnotationCount() != that.getAnnotationCount()) return false;
            if (!this.getAnnotations().equals(that.getAnnotations())) return false;
            return true;
        }
        return false;
    }

    protected boolean checkEachContributionIsFromDistinctSource() {
        Set<String> sourceNames = new HashSet<String>();
        for (Contribution contribution : this) {
            boolean added = sourceNames.add(contribution.getSourceName());
            if (!added) return false;
        }
        return true;
    }

}
TOP

Related Classes of org.jboss.dna.connector.federation.merge.MergePlan

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.