/*
 * Copyright 2010 JBoss Inc
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.drools.repository;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.Iterator;
import java.util.List;

import javax.jcr.InvalidItemStateException;
import javax.jcr.Node;
import javax.jcr.PathNotFoundException;
import javax.jcr.Property;
import javax.jcr.RepositoryException;
import javax.jcr.UnsupportedRepositoryOperationException;
import javax.jcr.Value;
import javax.jcr.lock.LockException;
import javax.jcr.version.VersionManager;

import org.drools.repository.events.StorageEventManager;

/**
 * This is the parent class for versionable assets.
 * Contains standard fields based on Dublin Core, and
 * stuff required for versioning.
 * For dublin core, refer to <a href="http://dublincore.org/documents/dces/">Here</a>
 *
 * @see CategorisableItem for more attributes to do with BRMS resources.
 */
public abstract class VersionableItem extends Item {

    /**
     * Property names for this node type.
     */
    public static final String TITLE_PROPERTY_NAME            = "drools:title";
    public static final String DESCRIPTION_PROPERTY_NAME      = "drools:description";
    public static final String LAST_MODIFIED_PROPERTY_NAME    = "drools:lastModified";
    public static final String FORMAT_PROPERTY_NAME           = "drools:format";
    public static final String CHECKIN_COMMENT                = "drools:checkinComment";
    public static final String VERSION_NUMBER_PROPERTY_NAME   = "drools:versionNumber";
    public static final String CONTENT_PROPERTY_ARCHIVE_FLAG  = "drools:archive";

    /** Dublin core based fields. */
    public static final String LAST_CONTRIBUTOR_PROPERTY_NAME = "drools:lastContributor";
    public static final String CREATOR_PROPERTY_NAME          = "drools:creator";
    public static final String TYPE_PROPERTY_NAME             = "drools:type";
    public static final String SOURCE_PROPERTY_NAME           = "drools:source";
    public static final String SUBJECT_PROPERTY_NAME          = "drools:subject";
    public static final String RELATION_PROPERTY_NAME         = "drools:relation";
    public static final String RIGHTS_PROPERTY_NAME           = "drools:rights";
    public static final String COVERAGE_PROPERTY_NAME         = "drools:coverage";
    public static final String PUBLISHER_PROPERTY_NAME        = "drools:publisher";

    /**
     * The name of the state property on the rule node type
     */
    public static final String STATE_PROPERTY_NAME            = "drools:stateReference";

    /**
     * The name of the tag property on the rule node type
     */
    public static final String CATEGORY_PROPERTY_NAME         = "drools:categoryReference";

    /**
     * The possible formats for the format property of the node
     */
    public static final String DEFAULT_CONTENT_FORMAT         = "txt";

    /** this is what is referred to when reading content from a versioned node */
    private Node               contentNode                    = null;

    /**
     * Sets this object's node attribute to the specified node
     *
     * @param rulesRepository the RulesRepository object that this object is being created from
     * @param node the node in the repository that this item corresponds to
     */
    public VersionableItem(RulesRepository rulesRepository,
                           Node node) {
        super( rulesRepository,
               node );
    }

    /**
     * @return A unique identifier for this items content node.
     * This UUID is constant even with new versions, it represents the asset, and
     * ALL its historical versions.
     */
    public String getUUID() {
        try {
            return this.getVersionContentNode().getIdentifier();
        } catch (  RepositoryException e ) {
            throw new RulesRepositoryException(e);
        }
    }

    /**
     * This will return true if the current entity is actually a
     * historical version (which means is effectively read only).
     */
    public boolean isHistoricalVersion() {
    	try {
            return this.node.getPrimaryNodeType().getName().equals( "nt:version" ) || node.getPrimaryNodeType().getName().equals( "nt:frozenNode" );
    	} catch (RepositoryException e) {
    		//ignore
    	}
    	return false;
    }

    /**
     * @return the predecessor node of this node in the version history, or null if no predecessor version exists
     * @throws RulesRepositoryException
     */
    protected Node getPrecedingVersionNode() throws RulesRepositoryException {
        try {
            Node versionNode;
            if (this.node.getPrimaryNodeType().getName().equals("nt:version")) {
                versionNode = this.node;
            } else {
                versionNode = getVersionManager(this.node).getBaseVersion(this.node.getPath());
            }

            Property predecessorsProperty = versionNode.getProperty("jcr:predecessors");
            Value[] predecessorValues = predecessorsProperty.getValues();

            if (predecessorValues.length > 0) {
                Node predecessorNode = this.node.getSession().getNodeByIdentifier(predecessorValues[0].getString());

                // we don't want to return the root node - it isn't a true
                // predecessor
                if (predecessorNode.getName().equals("jcr:rootVersion")) {
                    return null;
                }

                return predecessorNode;
            }
        } catch (PathNotFoundException e) {
            // do nothing - this will happen if no predecessors exits
        } catch (Exception e) {
            log.error("Caught exception", e);
            throw new RulesRepositoryException(e);
        }
        return null;
    }

    /**
     * @return the successor node of this node in the version history
     * @throws RulesRepositoryException
     */
    protected Node getSucceedingVersionNode() throws RulesRepositoryException {
        try {
            Property successorsProperty = this.node.getProperty( "jcr:successors" );
            Value[] successorValues = successorsProperty.getValues();

            if ( successorValues.length > 0 ) {
                Node successorNode = this.node.getSession().getNodeByIdentifier( successorValues[0].getString() );
                return successorNode;
            }
        } catch ( PathNotFoundException e ) {
            //do nothing - this will happen if no successors exist
        } catch ( Exception e ) {
            log.error( "Caught exception",
                       e );
            throw new RulesRepositoryException( e );
        }
        return null;
    }

    /**
     * @return an Iterator over VersionableItem objects encapsulating each successor node of this
     *         Item's node
     * @throws RulesRepositoryException
     * @Deprecated Until I can work out why it isn't quite kosher.
     */
    ItemVersionIterator getSuccessorVersionsIterator() throws RulesRepositoryException {
        return new ItemVersionIterator( this,
                                        ItemVersionIterator.ITERATION_TYPE_SUCCESSOR );
    }

    /**
     * @return an Iterator over VersionableItem objects encapsulating each predecessor node of this
     *         Item's node
     * @throws RulesRepositoryException
     * @Deprecated Until I can work out why it isn't quite kosher.
     */
    ItemVersionIterator getPredecessorVersionsIterator() throws RulesRepositoryException {
        return new ItemVersionIterator( this,
                                        ItemVersionIterator.ITERATION_TYPE_PREDECESSOR );
    }

    /**
     * Clients of this method can cast the resulting object to the type of object they are
     * calling the method on (e.g.
     *         <pre>
     *           RuleItem item;
     *           ...
     *           RuleItem predcessor = (RuleItem) item.getPrecedingVersion();
     *         </pre>
     * @return a VersionableItem object encapsulating the predessor node of this node in the
     *         version history, or null if no predecessor version exists
     * @throws RulesRepositoryException
     */
    public abstract VersionableItem getPrecedingVersion() throws RulesRepositoryException;

    /**
     * Clients of this method can cast the resulting object to the type of object they are
     * calling the method on (e.g.
     *         <pre>
     *           RuleItem item;
     *           ...
     *           RuleItem successor = (RuleItem) item.getSucceedingVersion();
     *         </pre>
     *
     * @return a VersionableItem object encapsulating the successor node of this node in the
     *         version history.
     * @throws RulesRepositoryException
     */
    public abstract VersionableItem getSucceedingVersion() throws RulesRepositoryException;
    
    public abstract Iterator getHistory();
    
    /**
     * Gets the Title of the versionable node.  See the Dublin Core documentation for more
     * explanation: http://dublincore.org/documents/dces/
     *
     * @return the title of the node this object encapsulates
     * @throws RulesRepositoryException
     */
    public String getTitle() throws RulesRepositoryException {
        try {
            Node theNode = getVersionContentNode();

            Property data = theNode.getProperty( TITLE_PROPERTY_NAME );
            return data.getValue().getString();
        } catch ( Exception e ) {
            log.error( "Caught Exception",
                       e );
            throw new RulesRepositoryException( e );
        }
    }

    /**
     * See the Dublin Core documentation for more
     * explanation: http://dublincore.org/documents/dces/
     *
     * @param title the new title for the node
     * @throws RulesRepositoryException
     */
    public void updateTitle(String title) throws RulesRepositoryException {
        updateStringProperty( title,
                              TITLE_PROPERTY_NAME );
    }


    public void updateType(String type) {
        updateStringProperty( type,
                              TYPE_PROPERTY_NAME );
    }

    public void updateExternalSource(String source) {
        updateStringProperty( source,
                              SOURCE_PROPERTY_NAME );
    }

    public void updateSubject(String sub) {
        updateStringProperty( sub,
                              SUBJECT_PROPERTY_NAME );
    }

    public void updateExternalRelation(String rel) {
        updateStringProperty( rel,
                              RELATION_PROPERTY_NAME );
    }

    public void updateRights(String rights) {
        updateStringProperty( rights,
                              RIGHTS_PROPERTY_NAME );
    }

    public void updateCoverage(String cov) {
        updateStringProperty( cov,
                              COVERAGE_PROPERTY_NAME );
    }

    public void updatePublisher(String pub) {
        updateStringProperty( pub,
                              PUBLISHER_PROPERTY_NAME );
    }



    /**
     * update a text field. This is a convenience method that just
     * uses the JCR node to set a property.
     * This will also update the timestamp.
     */
    public void updateStringProperty(String value,
                                      String prop) {
        updateStringProperty(value, prop, true);
    }

    /**
     * optionally update last updated... 
     */
    public void updateStringProperty(String value,
                                      String prop, boolean setLastUpdated) {
        try {
            checkIsUpdateable();

            if (value == null) {
                return;
            }

            this.checkout();
            node.setProperty( prop,
                              value );
            if (setLastUpdated) {
                Calendar lastModified = Calendar.getInstance();
                this.node.setProperty( LAST_MODIFIED_PROPERTY_NAME,
                                       lastModified );
            }

        } catch ( Exception e ) {
            if ( e instanceof RuntimeException ) {
                throw (RuntimeException) e;
            }
            throw new RulesRepositoryException( e );
        }
    }

    /**
     * optionally update last updated...
     */
    public void updateStringArrayProperty(String[] value, String prop, boolean setLastUpdated) {
        try {
            checkIsUpdateable();

            if (value == null) {
                return;
            }

            this.checkout();
            node.setProperty(prop, value);
            if (setLastUpdated) {
                Calendar lastModified = Calendar.getInstance();
                this.node.setProperty(LAST_MODIFIED_PROPERTY_NAME, lastModified);
                this.node.setProperty(LAST_CONTRIBUTOR_PROPERTY_NAME, node.getSession().getUserID());
            }

        } catch (RulesRepositoryException e) {
            throw new RulesRepositoryException(e);
        } catch (UnsupportedRepositoryOperationException e) {
            throw new RulesRepositoryException(e);
        } catch (LockException e) {
            throw new RulesRepositoryException(e);
        } catch (RepositoryException e) {
            throw new RulesRepositoryException(e);
        }
    }

    /**
     * See the Dublin Core documentation for more
     * explanation: http://dublincore.org/documents/dces/
     *
     * @return the description of this object's node.
     * @throws RulesRepositoryException
     */
    public String getDescription() throws RulesRepositoryException {
            return getStringProperty( DESCRIPTION_PROPERTY_NAME );
    }

    /**
     * get this version number (default is incrementing integer, but you
     * can provide an implementation of VersionNumberGenerator if needed).
     */
    public long getVersionNumber() {
//        try {
//            if ( getVersionContentNode().hasProperty( VERSION_NUMBER_PROPERTY_NAME ) ) {
//                return getVersionContentNode().getProperty( VERSION_NUMBER_PROPERTY_NAME ).getString();
//            } else {
//                return null;
//            }
//        } catch ( RepositoryException e ) {
//            throw new RulesRepositoryException( e );
//        }

        return getLongProperty( VERSION_NUMBER_PROPERTY_NAME );
    }

    /**
     * This will return the checkin comment for the latest revision.
     */
    public String getCheckinComment() throws RulesRepositoryException {
        return getStringProperty( CHECKIN_COMMENT );
    }

    /**
     * @return the date the function node (this version) was last modified
     * @throws RulesRepositoryException
     */
    public Calendar getLastModified() throws RulesRepositoryException {
        try {
            Node n = getVersionContentNode();
            if (n.hasProperty(LAST_MODIFIED_PROPERTY_NAME)) {
                Property lastModifiedProperty = getVersionContentNode().getProperty( LAST_MODIFIED_PROPERTY_NAME );
                return lastModifiedProperty.getDate();
            } else {
                return null;
            }
        } catch ( Exception e ) {
            log.error( "Caught Exception",
                       e );
            throw new RulesRepositoryException( e );
        }
    }

    /**
     * Creates a new version of this object's node, updating the description content
     * for the node.
     * <br>
     * See the Dublin Core documentation for more
     * explanation: http://dublincore.org/documents/dces/
     *
     * @param newDescriptionContent the new description content for the rule
     * @throws RulesRepositoryException
     */
    public void updateDescription(String newDescriptionContent) throws RulesRepositoryException {
        try {
            this.checkout();
            //this.node.setProperty(arg0, arg1);

            this.node.setProperty( DESCRIPTION_PROPERTY_NAME,
                                   newDescriptionContent );

            Calendar lastModified = Calendar.getInstance();
            this.node.setProperty( LAST_MODIFIED_PROPERTY_NAME,
                                   lastModified );

        } catch ( Exception e ) {
            log.error( "Caught Exception",
                       e );
            throw new RulesRepositoryException( e );
        }
    }
    
    /**
     * This returns the format of an item.
     * This is analogous to a file extension
     * if the resource was a file (it may contain more information
     * then a pure file extension could, however).
     *
     * See the Dublin Core documentation for more
     * explanation: http://dublincore.org/documents/dces/
     *
     * @return the format of this object's node
     * @throws RulesRepositoryException
     */
    public String getFormat() throws RulesRepositoryException {
        try {
            Node theNode = getVersionContentNode();

            Property data = theNode.getProperty(FORMAT_PROPERTY_NAME);
            return data.getValue().getString();
        } catch (Exception e) {
            log.error("Caught Exception", e);
            throw new RulesRepositoryException(e);
        }
    }

    /**
     * This sets the format (or "file extension" of the resource).
     * In some cases this is critical, and generally should not be changed
     * after the initial version is checked in.
     *
     * @param newFormat
     */
    public void updateFormat(String newFormat) {
        this.updateStringProperty( newFormat,
                                   FORMAT_PROPERTY_NAME );
    }

    /**
     * When retrieving content, if we are dealing with a version in the history,
     * we need to get the actual content node to retrieve values.
     */
    public Node getVersionContentNode() throws RepositoryException,
                                       PathNotFoundException {
        if ( this.contentNode == null ) {
            this.contentNode = getRealContentFromVersion(this.node);
        }
        return contentNode;
    }

    /**
     * This deals with a node which *may* be a version, if it is, it grabs the frozen copy.
     */
    protected Node getRealContentFromVersion(Node node) throws RepositoryException, PathNotFoundException {
        if (node.getPrimaryNodeType().getName().equals("nt:version")) {
            return node.getNode("jcr:frozenNode");
        } else {
            return node;
        }
    }

    /**
     * Need to get the name from the content node, not the version node
     * if it is in fact a version !
     */
    public String getName() {
        return getTitle();
    }

    /**
     * This will check out the node prior to editing.
     */
    public void checkout() {
        checkout(this.node);
    }

    /**
     * This will check out the node prior to editing.
     * @param targetNode the node to be checked out.
     */
    public static void checkout(Node targetNode) {
        try {
            getVersionManager(targetNode).checkout(targetNode.getPath());
        } catch ( UnsupportedRepositoryOperationException e ) {
            String message = "";
            try {
                message = "Error: Caught UnsupportedRepositoryOperationException when attempting to checkout rule: " + targetNode.getName() + ". Are you sure your JCR repository supports versioning? ";
                log.error( message,
                           e );
            } catch ( RepositoryException e1 ) {
                log.error( "Caught Exception",
                           e );
                throw new RulesRepositoryException( e1 );
            }
            throw new RulesRepositoryException( message,
                                                e );
        } catch ( InvalidItemStateException e ) {
            String message = "Your operation was failed because it conflicts with a change made through another user. Please try again.";
            log.error( "Caught Exception", e );
            throw new RulesRepositoryException( message, e );
        } catch ( Exception e ) {
            log.error( "Caught Exception",
                       e );
            throw new RulesRepositoryException( e );
        }
    }
    /**
     * This will save the content (if it hasn't been already) and
     * then check it in to create a new version.
     * It will also set the last modified property.
     */
    public void checkin(String comment) {
        checkIsUpdateable();
        checkInAssetIfNecessary(false);
        try {

            this.node.setProperty( LAST_MODIFIED_PROPERTY_NAME, Calendar.getInstance() );
            this.node.setProperty( CHECKIN_COMMENT, comment );
            this.node.setProperty( LAST_CONTRIBUTOR_PROPERTY_NAME, this.node.getSession().getUserID() );
            long nextVersion = getVersionNumber() + 1;
            this.node.setProperty( VERSION_NUMBER_PROPERTY_NAME,  nextVersion );
            this.node.getSession().save();
            
            getVersionManager(this.node).checkin(this.node.getPath());

            if (StorageEventManager.hasSaveEvent()) {
                if (this instanceof AssetItem) {
                    StorageEventManager.getSaveEvent().onAssetCheckin((AssetItem) this);
                }
            }
            StorageEventManager.doCheckinEvents(this);

        } catch ( RepositoryException e ) {
            throw new RulesRepositoryException( "Unable to checkin.",
                                                e );
        }
    }

	/*
	 * When we make a version of package (check in the package), we need to know
	 * the exact version number of child assets that this package contains. If
	 * the child asset does not have any version yet (it may have been saved,
	 * but has never been checked in), we need to check in this asset so that we
	 * have a version number that we can refer to from the versioned package.
	 */
    private void checkInAssetIfNecessary(boolean force) {
    	if(!(this instanceof PackageItem)) {
    		return;
    	}
    	Iterator<AssetItem> assets = ((PackageItem)this).getAssets();
    	while(assets.hasNext()) {
    		AssetItem asset = assets.next();
     		
    		if(force || !assetHasHistory(asset)) {
    			asset.checkout();
    			asset.checkin("Package[" + this.getTitle() + "] checked in");
    		}
    	}
    }
    
    private boolean assetHasHistory(AssetItem asset) {
		AssetHistoryIterator it = asset.getHistory();      
		while ( it.hasNext() ) {
            VersionableItem historical = (VersionableItem) it.next();
            long versionNumber = historical.getVersionNumber();
            if ( versionNumber != 0) {
            	//Version 0 is not a real version. It is the root version
                return true;
            }
        }
		
		return false;  	    	
    }
    /**
     * This will check to see if the node is the "head" and
     * so can be updated (you can't update historical nodes ).
     * @throws RulesRepositoryException if it is not allowed
     * (means a programming error !).
     */
    protected void checkIsUpdateable() {
        try {
            if ( this.node.getPrimaryNodeType().getName().equals( "nt:version" ) ) {
                String message = "Error. Tags can only be added to the head version of a rule node";
                log.error( message );
                throw new RulesRepositoryException( message );
            }
        } catch ( RepositoryException e ) {
            throw new RulesRepositoryException( e );
        }
    }

    /**
     * Sets this object's rule node's state property to refer to the specified state node
     *
     * @param stateName the name of the state to set the rule node to
     * @throws RulesRepositoryException
     */
    public void updateState(String stateName) throws RulesRepositoryException {
        try {

            //now set the state property of the rule
            checkout();

            StateItem stateItem = this.rulesRepository.getState( stateName );
            this.updateState( stateItem );
        } catch ( Exception e ) {
            log.error( "Caught exception",
                       e );
            throw new RulesRepositoryException( e );
        }
    }

    /**
     * Sets this object's rule node's state property to refer to the specified StateItem's node
     *
     * @param stateItem the StateItem encapsulating the node to refer to from this object's node's state
     *                  property
     * @throws RulesRepositoryException
     */
    public void updateState(StateItem stateItem) throws RulesRepositoryException {
        checkIsUpdateable();
        try {

            //now set the state property of the rule
            checkout();
            this.node.setProperty( STATE_PROPERTY_NAME,
                                   stateItem.getNode() );
        } catch ( Exception e ) {
            log.error( "Caught exception",
                       e );
            throw new RulesRepositoryException( e );
        }
    }

    /**
     * Gets StateItem object corresponding to the state property of this object's node
     *
     * @return a StateItem object corresponding to the state property of this object's node, or null
     *         if the state property is not set
     * @throws RulesRepositoryException
     */
    public StateItem getState() throws RulesRepositoryException {
        try {
            Node content = getVersionContentNode();
            Property stateProperty = content.getProperty( STATE_PROPERTY_NAME );
            Node stateNode = this.rulesRepository.getSession().getNodeByIdentifier( stateProperty.getString() );
            return new StateItem( this.rulesRepository,
                                  stateNode );
        } catch ( PathNotFoundException e ) {
            //not set
            return null;
        } catch ( Exception e ) {
            log.error( "Caught exception",
                       e );
            throw new RulesRepositoryException( e );
        }
    }

    /**
     * This will return the current state item as a displayable thing.
     * If there is no state, it will be an empty string.
     */
    public String getStateDescription() {
        StateItem state = this.getState();
        if ( state == null ) {
            return "";
        } else {
            return state.getName();
        }
    }

    /** Compare this rules state with some other state */
    public boolean sameState(StateItem other) {
        StateItem thisState = getState();
        if ( thisState == other ) {
            return true;
        } else if ( thisState != null ) {
            return thisState.equals( other );
        } else {
            return false;
        }
    }

    /**
     * Returns the last contributors name.
     */
    public String getLastContributor() {
        return getStringProperty( LAST_CONTRIBUTOR_PROPERTY_NAME );
    }

    /**
     * This is the person who initially created the resource.
     */
    public String getCreator() {
        return getStringProperty( CREATOR_PROPERTY_NAME );
    }

    /**
     * This is the Dublin Core field of type (a broad classification of resource type).
     */
    public String getType() {
        return getStringProperty( TYPE_PROPERTY_NAME );
    }

    /**
     * This is the source of the asset/rule. Ie a human description of where it came from.
     */
    public String getExternalSource() {
        return getStringProperty( SOURCE_PROPERTY_NAME );
    }

    /**
     * Typically,
     * Subject will be expressed as keywords,
     * key phrases or classification codes that describe a topic of the resource.
     */
    public String getSubject() {
        return getStringProperty( SUBJECT_PROPERTY_NAME );
    }

    /**
     * A reference to a EXTERNAL related resource.
     */
    public String getExternalRelation() {
        return getStringProperty( RELATION_PROPERTY_NAME );
    }

    /**
     * Optionally contains any copyright/ownership rights for the asset.
     */
    public String getRights() {
        return getStringProperty( RIGHTS_PROPERTY_NAME );
    }

    /**
     * Typically, Coverage will include spatial location
     * (a place name or geographic coordinates), temporal period (a period label, date, or date range) or jurisdiction (such as a named administrative entity). Recommended best practice is to select a value from a controlled vocabulary (for example, the Thesaurus of Geographic Names [TGN]) and to use, where appropriate, named places or time periods in preference to numeric identifiers such as sets of coordinates or date ranges.
     */
    public String getCoverage() {
        return getStringProperty( COVERAGE_PROPERTY_NAME );
    }

    /**
     *  Examples of Publisher include a person, an organization, or a service.
     *  Typically, the name of a Publisher should be used to indicate the entity.
     */
    public String getPublisher() {
        return getStringProperty( PUBLISHER_PROPERTY_NAME );
    }

    /**
     * This returns the date/time on which the asset was "ORIGINALLY CREATED".
     * Kinda handy if you want to know how old something is.
     */
    public Calendar getCreatedDate() {
        Property prop;
        try {
            prop = this.node.getProperty( "jcr:created" );
            return prop.getDate();
        } catch ( RepositoryException e ) {
            throw new RulesRepositoryException( e );
        }

    }

    public String getStringProperty(String property) {
        try {
            Node theNode = getVersionContentNode();
            if ( theNode.hasProperty( property ) ) {
                Property data = theNode.getProperty( property );
                return data.getValue().getString();
            } else {
                return "";
            }
        } catch ( RepositoryException e ) {
            throw new RulesRepositoryException( e );
        }
    }

    public String[] getStringPropertyArray(String property) {
        try {
            Node theNode = getVersionContentNode();
            if ( theNode.hasProperty( property ) ) {
                Property data = theNode.getProperty( property );
                Value[] values = data.getValues();
               
                List<String> list = new ArrayList<String>();
                for (Value value : values) {
                    list.add(value.getString());

                }
                return list.toArray(new String[0]);
            } else {
                return new String[0];
            }
        } catch ( RepositoryException e ) {
            throw new RulesRepositoryException( e );
        }
    }
    protected long getLongProperty(String property) {
        try {
            Node theNode = getVersionContentNode();
            if ( theNode.hasProperty( property ) ) {
                Property data = theNode.getProperty( property );
                return data.getValue().getLong();
            } else {
                return 0;
            }
        } catch ( RepositoryException e ) {
            throw new RulesRepositoryException( e );
        }
    }

    /**
     * This returns the id of the exact version node (as opposed to the "main" node).
     * Note that each asset has only one UUID the whole time, but there are also UUIDs
     * for each item in the history.
     * So while the main UUID version remains constant, the version UUIDs change on each
     * checkin, which is what this method provides.
     */
    public String getVersionSnapshotUUID() {
        try {
            if ( isHistoricalVersion() ) {
                return this.node.getIdentifier();
            } else {
                throw new RulesRepositoryException( "This is the current version of the asset." );
            }
        } catch ( RepositoryException e ) {
            throw new RulesRepositoryException(e);
        }

    }


    public VersionableItem archiveItem(boolean data) {
        checkout();

        try {
            this.node.setProperty(CONTENT_PROPERTY_ARCHIVE_FLAG, data);
            return this;
        } catch (RepositoryException e) {
            log.error("Unable to update this VersionableItem binary archive flag");
            throw new RulesRepositoryException(e);
        }
    }

    /**
     * Test if the VersionableItem is archived
     */
    public boolean isArchived() {
        try {
            Node ruleNode = getVersionContentNode();
            return ruleNode.getProperty( CONTENT_PROPERTY_ARCHIVE_FLAG ).getBoolean();

/*            return this.node.getProperty(CONTENT_PROPERTY_ARCHIVE_FLAG)
                    .getBoolean();*/
        } catch ( PathNotFoundException e ) {
            // doesn't have this property
            return false;
        } catch (RepositoryException e) {
            log.error("Unable to check this asset");
            throw new RulesRepositoryException(e);
        }
    }
    
    public static VersionManager getVersionManager(Node targetNode) throws RepositoryException {
        return targetNode.getSession().getWorkspace().getVersionManager();
    }

}
