org.photovault.replication.ObjectHistory.java Source code

Java tutorial

Introduction

Here is the source code for org.photovault.replication.ObjectHistory.java

Source

/*
  Copyright (c) 2008 Harri Kaimio
      
  This file is part of Photovault.
    
  Photovault is free software; you can redistribute it and/or modify it
  under the terms of the GNU General Public License as published by
  the Free Software Foundation; either version 2 of the License, or
  (at your option) any later version.
    
  Photovault 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
  General Public License for more details.
    
  You should have received a copy of the GNU General Public License
  along with Photovault; if not, write to the Free Software Foundation,
  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*/

package org.photovault.replication;

import java.io.IOException;
import java.io.ObjectOutputStream;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.OneToMany;
import javax.persistence.OneToOne;
import javax.persistence.Table;
import javax.persistence.Transient;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 Manages version history and version changes for a single object. Each versioned 
 object must maintain an embedden instance of this class and provide a method
 for accessing it (indicated by the {@link History} annotation.
     
 @author Harri Kaimio
 @since 0.6
     
 @param <T> Class of the target obejct
 */
@Entity
@Table(name = "pv_version_histories")
public class ObjectHistory<T> {

    static private Log log = LogFactory.getLog(ObjectHistory.class.getName());

    /**
     Name of the class of target
     */
    String targetClassName;

    /**
     Identifier of this change history. THis is the same as the UUID of the 
     target object
     */
    private UUID uuid;

    /**
     Current version of the target object
     */
    Change<T> currentVersion;

    /**
     Head changes (i.e. changes that do not have children)
     */
    Set<Change<T>> heads = new HashSet<Change<T>>();

    /**
     All changes to target obejct that are known 
     */
    Set<Change<T>> allChanges = new HashSet<Change<T>>();

    /**
     Default constructor for persistence & replication layers, do not use otherwise
     */
    public ObjectHistory() {
    }

    /**
     Constructor
     @param target
     */
    public ObjectHistory(T target) {
        targetClassName = target.getClass().getName();
    }

    /**
     Returns the UUID of the target obejct
     */
    @Id
    @Column(name = "uuid")
    @org.hibernate.annotations.Type(type = "org.photovault.persistence.UUIDUserType")
    public UUID getTargetUuid() {
        return uuid;
    }

    public void setTargetUuid(UUID uuid) {
        this.uuid = uuid;
    }

    /**
     Add a new change to change history. Called bu {@link Change} when it is 
     freezed
     @param c The new change
     */
    void addChange(Change<T> c) {
        for (Change<T> parent : c.getParentChanges()) {
            heads.remove(parent);
        }
        heads.add(c);
        allChanges.add(c);
    }

    /**
     Create a new empty change object for target object
     @return A new, unfrozen Change object
     */
    public Change<T> createChange() {
        return new Change<T>(this);
    }

    /**
     Returns name of the target object's class
     */
    @Column(name = "target_class")
    public String getTargetClassName() {
        return targetClassName;
    }

    /**
     Set the name of target object's class
     @param cl
     */
    public void setTargetClassName(String cl) {
        targetClassName = cl;
    }

    /**
     Returns all known changes in target objects's history. 
     */
    @OneToMany(mappedBy = "targetHistory", cascade = CascadeType.ALL, targetEntity = Change.class)
    public Set<Change<T>> getChanges() {
        return this.allChanges;
    }

    void setChanges(Set c) {
        allChanges = c;
    }

    @Transient
    public Change<T> getChange(UUID id) {
        for (Change<T> c : allChanges) {
            if (id.equals(c.getUuid())) {
                return c;
            }
        }
        return null;
    }

    /**
     Returns the set of head changes, i.e. changes that do not have a child.
     */
    @OneToMany(targetEntity = Change.class)
    @JoinTable(name = "pv_change_unmerged_branches", joinColumns = @JoinColumn(name = "target_uuid"), inverseJoinColumns = @JoinColumn(name = "change_uuid"))
    public Set<Change<T>> getHeads() {
        return heads;
    }

    void setHeads(Set heads) {
        this.heads = heads;
    }

    void setVersion(Change<T> version) {
        currentVersion = version;
    }

    @OneToOne(targetEntity = Change.class)
    @JoinColumn(name = "version_uuid")
    public Change<T> getVersion() {
        return currentVersion;
    }

    /**
     Write all changes of the target object to output stream. First, the total 
     number of changes is written as integer. Then the changes are 
     written in topologically sorted order so that all predecessors of a change 
     are written before it.
         
     @param os The output stream
     @throws java.io.IOException If an error occurs during writing.
     */
    public void writeChanges(ObjectOutputStream os) throws IOException {
        Set<UUID> writtenIds = new HashSet<UUID>();
        List<ChangeDTO<T>> changesToWrite = new ArrayList<ChangeDTO<T>>(allChanges.size());
        for (Change<T> ch : heads) {
            prepareDtos(ch, writtenIds, changesToWrite);
        }
        os.writeInt(changesToWrite.size());
        for (ChangeDTO<T> dto : changesToWrite) {
            os.writeObject(dto);
        }
    }

    /**
     Prepares {@link ChangeDTO} objects for serialization for certain change and 
     its predecessors if they have not been prepared already. This fuction is 
     called by writeChanges().
         
     @param ch The change
     @param writtenIds Set of uuids of those changes that have been prepared 
     already. IDs of changes prepared by this call are added to the set.
     @param dtoList List of DTOs. DTOs prepared by this method will be appended 
     to end of the list.
     */
    private void prepareDtos(Change<T> ch, Set<UUID> writtenIds, List<ChangeDTO<T>> dtoList) throws IOException {
        // First, ensure that all predecessors are written
        for (Change<T> parent : ch.getParentChanges()) {
            if (!writtenIds.contains(parent.getUuid())) {
                prepareDtos(parent, writtenIds, dtoList);
            }
        }
        ChangeDTO<T> dto = new ChangeDTO<T>(ch);
        dtoList.add(dto);
        writtenIds.add(ch.getUuid());
    }
}