Package org.drools.repository

Source Code of org.drools.repository.PackageItem

/*
* 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.io.InputStream;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;

import javax.jcr.Binary;
import javax.jcr.ItemExistsException;
import javax.jcr.Node;
import javax.jcr.NodeIterator;
import javax.jcr.PathNotFoundException;
import javax.jcr.Property;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import javax.jcr.Workspace;
import javax.jcr.nodetype.NodeType;
import javax.jcr.query.Query;
import javax.jcr.query.QueryResult;

import org.drools.repository.utils.NodeUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* A PackageItem object aggregates a set of assets (for example, rules). This is
* advantageous for systems using the JBoss Rules engine where the application
* might make use of many related rules.
* <p/>
* A PackageItem refers to rule nodes within the RulesRepository. It contains
* the "master copy" of assets (which may be linked into other packages or other
* types of containers). This is a container "node".
*/
public class PackageItem extends VersionableItem {
    private static final Logger log                                = LoggerFactory.getLogger( PackageItem.class );

    /**
     * This is the name of the rules "subfolder" where rules are kept for this
     * package.
     */
    public static final String  ASSET_FOLDER_NAME                  = "assets";

    /**
     * The dublin core format attribute.
     */
    public static final String  PACKAGE_FORMAT                     = "package";

    /**
     * The name of the rule package node type
     */
    public static final String  RULE_PACKAGE_TYPE_NAME             = "drools:packageNodeType";

    public static final String  HEADER_PROPERTY_NAME               = "drools:header";
    public static final String  EXTERNAL_URI_PROPERTY_NAME         = "drools:externalURI";
    public static final String  CATEGORY_RULE_KEYS_PROPERTY_NAME   = "categoryRuleKeys";
    public static final String  CATEGORY_RULE_VALUES_PROPERTY_NAME = "categoryRuleValues";
    public static final String  WORKSPACE_PROPERTY_NAME            = "drools:workspace";
    public static final String  DEPENDENCIES_PROPERTY_NAME         = "drools:dependencies";

    private static final String COMPILED_PACKAGE_PROPERTY_NAME     = "drools:compiledPackage";
    private final String        BINARY_UP_TO_DATE                  = "drools:binaryUpToDate";

    /**
     * Constructs an object of type RulePackageItem corresponding the specified
     * node
     *
     * @param rulesRepository
     *            the rulesRepository that instantiated this object
     * @param node
     *            the node to which this object corresponds
     * @throws RulesRepositoryException
     */
    public PackageItem(RulesRepository rulesRepository,
                       Node node) throws RulesRepositoryException {
        super( rulesRepository,
                node );

        try {
            //make sure this node is a rule package node
            if ( !(this.node.getPrimaryNodeType().getName().equals( RULE_PACKAGE_TYPE_NAME ) || isHistoricalVersion()) ) {
                String message = this.node.getName() + " is not a node of type " + RULE_PACKAGE_TYPE_NAME + ". It is a node of type: " + this.node.getPrimaryNodeType().getName();
                log.error( message );
                throw new RulesRepositoryException( message );
            }
        } catch ( Exception e ) {
            log.error( "Caught exception: " + e );
            throw new RulesRepositoryException( e );
        }
    }

    PackageItem() {
        super( null,
               null );
    }

    /**
     * Return the name of the package.
     */
    public String getName() {
        return super.getName();
    }

    /**
     * @return true if this package is actually a snapshot.
     */
    public boolean isSnapshot() {
        try {
            return (!this.rulesRepository.isNotSnapshot( this.node.getParent() ));
        } catch ( RepositoryException e ) {
            throw new IllegalStateException( e );
        }
    }

    /**
     * Set this to indicate if the binary is up to date, or not.
     */
    public void updateBinaryUpToDate(boolean status) {
        try {
            checkIsUpdateable();
            this.checkout();
            node.setProperty( BINARY_UP_TO_DATE,
                              status );
        } catch ( RepositoryException e ) {
            log.error( "fail to update drools:binaryUpToDate of " + getName(),
                       e );
        }
    }

    /**
     * Return true if the binary is "up to date".
     *
     * @return
     */
    public boolean isBinaryUpToDate() {
        try {
            if ( this.node.hasProperty( BINARY_UP_TO_DATE ) ) {
                return node.getProperty( BINARY_UP_TO_DATE ).getBoolean();
            } else {
                return false;
            }
        } catch ( RepositoryException e ) {
            log.error( "fail to get drools:binaryUpToDate of " + getName(),
                       e );
            throw new RulesRepositoryException( e );
        }
    }

    /**
     * returns the name of the snapshot, if this package is really a snapshot.
     * If it is not, it will just return the name of the package, so use wisely
     * !
     */
    public String getSnapshotName() {
        try {
            return this.node.getName();
        } catch ( RepositoryException e ) {
            throw new RulesRepositoryException( e );
        }
    }

    /**
     * @return the workspace this package belongs to.
     * @throws RulesRepositoryException
     */
    public String[] getWorkspaces() throws RulesRepositoryException {
        return getStringPropertyArray( WORKSPACE_PROPERTY_NAME );
    }

    /**
     * This sets the Workspace
     *
     * @param workspace
     */
    public void updateWorkspace(String[] workspace) {
        this.updateStringArrayProperty( workspace,
                                        WORKSPACE_PROPERTY_NAME,
                                        false );
    }

    /**
     * This adds a workspace
     *
     * @param workspace
     */
    public void addWorkspace(String workspace) {
        String[] existingWorkspaces = getStringPropertyArray( WORKSPACE_PROPERTY_NAME );
        boolean found = false;
        for ( String existingWorkspace : existingWorkspaces ) {
            if ( existingWorkspace.equals( workspace ) ) {
                found = true;
                break;
            }
        }
        if ( !found ) {
            String[] newWorkspaces = new String[existingWorkspaces.length + 1];
            for ( int i = 0; i < existingWorkspaces.length; i++ ) {
                newWorkspaces[i] = existingWorkspaces[i];
            }
            newWorkspaces[existingWorkspaces.length] = workspace;
            this.updateStringArrayProperty( newWorkspaces,
                                            WORKSPACE_PROPERTY_NAME,
                                            false );
        }
    }

    /**
     * This removes a workspace
     *
     * @param workspace
     */
    public void removeWorkspace(String workspace) {
        String[] existingWorkspaces = getStringPropertyArray( WORKSPACE_PROPERTY_NAME );
        if ( existingWorkspaces.length == 0 ) {
            return;
        }

        List<String> existingWorkspaceList = new ArrayList<String>( existingWorkspaces.length );
        for ( String existingWorkspace : existingWorkspaces ) {
            existingWorkspaceList.add( existingWorkspace );
        }
        existingWorkspaceList.remove( workspace );
        if ( existingWorkspaceList.size() != existingWorkspaces.length ) {
            this.updateStringArrayProperty( existingWorkspaceList.toArray( new String[existingWorkspaceList.size()] ),
                                            WORKSPACE_PROPERTY_NAME,
                                            false );
        }
    }

    /**
     * Adds a rule to the current package with no category (not recommended !).
     * Without categories, its going to be hard to find rules later on (unless
     * packages are enough for you).
     */
    public AssetItem addAsset(String assetName,
                              String description) {
        return addAsset( assetName,
                         description,
                         null,
                         null );
    }

    /**
     * This adds a rule to the current physical package (you can move it later).
     * With the given category.
     * <p/>
     * This will NOT check the asset in, just create the basic record.
     *
     * @param assetName
     *            The name of the asset (the file name minus the extension)
     * @param description
     *            A description of the asset.
     * @param initialCategory
     *            The initial category the asset is placed in (can belong to
     *            multiple ones later).
     * @param format
     *            The dublin core format (which also determines what editor is
     *            used) - this is effectively the file extension.
     */
    public AssetItem addAsset(String assetName,
                              String description,
                              String initialCategory,
                              String format) {
        Node ruleNode;
        try {
            assetName = assetName.trim();
            Node rulesFolder = this.node.getNode( ASSET_FOLDER_NAME );
            String assetPath = NodeUtils.makeJSR170ComplaintName( assetName );
            ruleNode = rulesFolder.addNode( assetPath,
                                            AssetItem.RULE_NODE_TYPE_NAME );
            ruleNode.setProperty( AssetItem.TITLE_PROPERTY_NAME,
                                  assetName );

            ruleNode.setProperty( AssetItem.DESCRIPTION_PROPERTY_NAME,
                                  description );
            if ( format != null ) {
                ruleNode.setProperty( AssetItem.FORMAT_PROPERTY_NAME,
                                      format );
            } else {
                ruleNode.setProperty( AssetItem.FORMAT_PROPERTY_NAME,
                                      AssetItem.DEFAULT_CONTENT_FORMAT );
            }

            ruleNode.setProperty( VersionableItem.CHECKIN_COMMENT,
                                  "Initial" );

            Calendar lastModified = Calendar.getInstance();

            ruleNode.setProperty( AssetItem.LAST_MODIFIED_PROPERTY_NAME,
                                  lastModified );
            ruleNode.setProperty( AssetItem.PACKAGE_NAME_PROPERTY,
                                  this.getName() );
            ruleNode.setProperty( CREATOR_PROPERTY_NAME,
                                  this.node.getSession().getUserID() );

            rulesRepository.getSession().save();

            AssetItem rule = new AssetItem( this.rulesRepository,
                                            ruleNode );

            rule.updateState( StateItem.DRAFT_STATE_NAME );

            if ( initialCategory != null ) {
                rule.addCategory( initialCategory );
            }

            return rule;

        } catch ( RepositoryException e ) {
            if ( e instanceof ItemExistsException ) {
                throw new RulesRepositoryException( "A rule of that name already exists in that package.",
                                                    e );
            } else {
                throw new RulesRepositoryException( e );
            }
        }

    }

    /**
     * This adds a rule which is imported from global area.
     * <p/>
     * This will NOT check the asset in, just create the basic record.
     *
     * @param sharedAssetName
     *            The name of the imported asset
     */
    public AssetItem addAssetImportedFromGlobalArea(String sharedAssetName) {
        try {
            //assetName = assetName.trim();
            Node rulesFolder = this.node.getNode( ASSET_FOLDER_NAME );

            Session session = rulesRepository.getSession();
            Workspace workspace = session.getWorkspace();
            PackageItem globalArea = rulesRepository.loadGlobalArea();
            AssetItem globalAssetItem = globalArea.loadAsset( sharedAssetName );
            if ( !hasMixin( globalAssetItem.getNode() ) ) {
                globalAssetItem.checkout();
                globalAssetItem.getNode().addMixin( "mix:shareable" );
                globalAssetItem.checkin( "add mix:shareable" );
            }

            String path = rulesFolder.getPath() + "/" + globalAssetItem.getName();
            workspace.clone( workspace.getName(),
                             globalAssetItem.getNode().getPath(),
                             path,
                             false );

            Node ruleNode = rulesFolder.getNode( globalAssetItem.getName() );
            AssetItem rule = new AssetItem( this.rulesRepository,
                                            ruleNode );

            return rule;
        } catch ( RepositoryException e ) {
            if ( e instanceof ItemExistsException ) {
                throw new RulesRepositoryException( "A rule of that name already exists in that package.",
                                                    e );
            } else {
                throw new RulesRepositoryException( e );
            }
        }

    }

    private boolean hasMixin(Node node) {
        try {
            NodeType[] nodeTypes = node.getMixinNodeTypes();
            for ( NodeType nodeType : nodeTypes ) {
                if ( nodeType.isNodeType( "mix:shareable" ) ) {
                    return true;
                }
            }
        } catch ( RepositoryException e ) {

        }

        return false;
    }

    /**
     * This will permanently delete this package.
     */
    public void remove() {
        checkIsUpdateable();
        try {
            log.info( "USER:" + getCurrentUserName() + " REMOVEING package [" + getName() + "]" );
            this.node.remove();
        } catch ( RepositoryException e ) {
            throw new RulesRepositoryException( "Was not able to delete package.",
                                                e );
        }
    }

    /**
     * To avoid updating dependency attribute for every asset operation like
     * adding/renaming/deleting etc, we calculate dependency path on the fly.
     *
     * @return String[] The dependency path.
     */
    public String[] getDependencies() {
        Map<String, String> result = new HashMap<String, String>();
        try {
            Node content = getVersionContentNode();
            Iterator<AssetItem> assets = new AssetItemIterator( content.getNode(
                                                                                 ASSET_FOLDER_NAME ).getNodes(),
                                                                this.rulesRepository );
            while ( assets.hasNext() ) {
                AssetItem asset = assets.next();

                result.put( asset.getName(),
                            encodeDependencyPath(
                                                  asset.getName(),
                                                  isHistoricalVersion() ? Long.toString( asset.getVersionNumber() ) : "LATEST" ) );
            }
        } catch ( RepositoryException e ) {
            throw new RulesRepositoryException( e );
        }

        String[] existingDependencies = getStringPropertyArray( DEPENDENCIES_PROPERTY_NAME );
        for ( String existingDependency : existingDependencies ) {
            String path = decodeDependencyPath( existingDependency )[0];
            if ( result.containsKey( path ) ) {
                result.put( path,
                            existingDependency );
            }
        }

        return result.values().toArray( new String[result.size()] );
    }

    public void updateDependency(String dependencyPath) {
        String[] existingDependencies = getStringPropertyArray( DEPENDENCIES_PROPERTY_NAME );
        boolean found = false;
        for ( int i = 0; i < existingDependencies.length; i++ ) {
            if ( decodeDependencyPath( existingDependencies[i] )[0]
                    .equals( decodeDependencyPath( dependencyPath )[0] ) ) {
                found = true;
                existingDependencies[i] = dependencyPath;
                this.updateStringArrayProperty( existingDependencies,
                                                DEPENDENCIES_PROPERTY_NAME,
                                                false );
                break;
            }
        }
        if ( !found ) {
            String[] newDependencies = new String[existingDependencies.length + 1];
            for ( int i = 0; i < existingDependencies.length; i++ ) {
                newDependencies[i] = existingDependencies[i];
            }
            newDependencies[existingDependencies.length] = dependencyPath;
            this.updateStringArrayProperty( newDependencies,
                                            DEPENDENCIES_PROPERTY_NAME,
                                            false );
        }
    }

    public static String encodeDependencyPath(String dependencyPath,
                                              String dependencyVersion) {
        return dependencyPath + "?version=" + dependencyVersion;
    }

    public static String[] decodeDependencyPath(String dependencyPath) {
        if ( dependencyPath.indexOf( "?version=" ) >= 0 ) {
            return dependencyPath.split( "\\?version=" );
        } else {
            return new String[]{dependencyPath, "LATEST"};
        }
    }

    // The following should be kept for reference on how to add a reference that
    //is either locked to a version or follows head - FOR SHARING ASSETS
    //    /**
    //     * Adds a rule to the rule package node this object represents.  The reference to the rule
    //     * will optionally follow the head version of the specified rule's node or the specific
    //     * current version.
    //     *
    //     * @param ruleItem the ruleItem corresponding to the node to add to the rule package this
    //     *                 object represents
    //     * @param followRuleHead if true, the reference to the rule node will follow the head version
    //     *                       of the node, even if new versions are added. If false, will refer
    //     *                       specifically to the current version.
    //     * @throws RulesRepositoryException
    //     */
    //    public void addRuleReference(RuleItem ruleItem, boolean followRuleHead) throws RulesRepositoryException {
    //        try {
    //            ValueFactory factory = this.node.getSession().getValueFactory();
    //            int i = 0;
    //            Value[] newValueArray = null;
    //
    //            try {
    //                Value[] oldValueArray = this.node.getProperty(RULE_REFERENCE_PROPERTY_NAME).getValues();
    //                newValueArray = new Value[oldValueArray.length + 1];
    //
    //                for(i=0; i<oldValueArray.length; i++) {
    //                    newValueArray[i] = oldValueArray[i];
    //                }
    //            }
    //            catch(PathNotFoundException e) {
    //                //the property has not been created yet. do so now
    //                newValueArray = new Value[1];
    //            }
    //            finally {
    //                if(newValueArray != null) { //just here to make the compiler happy
    //                    if(followRuleHead) {
    //                        newValueArray[i] = factory.createValue(ruleItem.getNode());
    //                    }
    //                    else {
    //                        //this is the magic that ties it to a specific version
    //                        newValueArray[i] = factory.createValue(ruleItem.getNode().getBaseVersion());
    //                    }
    //                    this.node.checkout();
    //                    this.node.setProperty(RULE_REFERENCE_PROPERTY_NAME, newValueArray);
    //                    this.node.getSession().save();
    //                    this.node.checkin();
    //                }
    //                else {
    //                    throw new RulesRepositoryException("Unexpected null pointer for newValueArray");
    //                }
    //            }
    //        }
    //        catch(UnsupportedRepositoryOperationException e) {
    //            String message = "";
    //            try {
    //                message = "Error: Caught UnsupportedRepositoryOperationException when attempting to get base version for rule: " + ruleItem.getNode().getName() + ". Are you sure your JCR repository supports versioning? ";
    //                log.error(message + e);
    //            }
    //            catch (RepositoryException e1) {
    //                log.error("Caught exception: " + e1);
    //                throw new RulesRepositoryException(message, e1);
    //            }
    //            log.error("Caught exception: " + e);
    //            throw new RulesRepositoryException(e);
    //        }
    //        catch(Exception e) {
    //            log.error("Caught exception: " + e);
    //            throw new RulesRepositoryException(e);
    //        }
    //    }

    //MN: The following should be kept as a reference on how to remove a version tracking reference
    //as a compliment to the above method (which is also commented out !).
    //    /**
    //     * Removes the specified rule from the rule package node this object represents.
    //     *
    //     * @param ruleItem the ruleItem corresponding to the node to remove from the rule package
    //     *                 this object represents
    //     * @throws RulesRepositoryException
    //     */
    //    public void removeRuleReference(AssetItem ruleItem) throws RulesRepositoryException {
    //        try {
    //            Value[] oldValueArray = this.node.getProperty( RULE_REFERENCE_PROPERTY_NAME ).getValues();
    //            Value[] newValueArray = new Value[oldValueArray.length - 1];
    //
    //            boolean wasThere = false;
    //
    //            int j = 0;
    //            for ( int i = 0; i < oldValueArray.length; i++ ) {
    //                Node ruleNode = this.node.getSession().getNodeByUUID( oldValueArray[i].getString() );
    //                AssetItem currentRuleItem = new AssetItem( this.rulesRepository,
    //                                                         ruleNode );
    //                if ( currentRuleItem.equals( ruleItem ) ) {
    //                    wasThere = true;
    //                } else {
    //                    newValueArray[j] = oldValueArray[i];
    //                    j++;
    //                }
    //            }
    //
    //            if ( !wasThere ) {
    //                return;
    //            } else {
    //                this.node.checkout();
    //                this.node.setProperty( RULE_REFERENCE_PROPERTY_NAME,
    //                                       newValueArray );
    //                this.node.getSession().save();
    //                this.node.checkin();
    //            }
    //        } catch ( PathNotFoundException e ) {
    //            //the property has not been created yet.
    //            return;
    //        } catch ( Exception e ) {
    //            log.error( "Caught exception",
    //                       e );
    //            throw new RulesRepositoryException( e );
    //        }
    //    }

    //MN: This should be kept as a reference for
    //    /**
    //     * Gets a list of RuleItem objects for each rule node in this rule package
    //     *
    //     * @return the List object holding the RuleItem objects in this rule package
    //     * @throws RulesRepositoryException
    //     */
    //    public List getRules() throws RulesRepositoryException {
    //        try {
    //            Value[] valueArray = this.node.getProperty(RULE_REFERENCE_PROPERTY_NAME).getValues();
    //            List returnList = new ArrayList();
    //
    //            for(int i=0; i<valueArray.length; i++) {
    //                Node ruleNode = this.node.getSession().getNodeByUUID(valueArray[i].getString());
    //                returnList.add(new RuleItem(this.rulesRepository, ruleNode));
    //            }
    //            return returnList;
    //        }
    //        catch(PathNotFoundException e) {
    //            //the property has not been created yet.
    //            return new ArrayList();
    //        }
    //        catch(Exception e) {
    //            log.error("Caught exception: " + e);
    //            throw new RulesRepositoryException(e);
    //        }
    //    }

    /**
     * Return an iterator for the rules in this package
     */
    public Iterator<AssetItem> getAssets() {
        try {
            Node content = getVersionContentNode();
            return new VersionedAssetItemIterator( content.getNode( ASSET_FOLDER_NAME ).getNodes(),
                                                   this.rulesRepository,
                                                   this.getDependencies() );
        } catch ( RepositoryException e ) {
            throw new RulesRepositoryException( e );
        }

    }

    /**
     * This will query any assets stored under this package. For example, you
     * can pass in <code>"drools:format = 'drl'"</code> to get a list of only a
     * certain type of asset.
     *
     * @param fieldPredicates
     *            A predicate string (SQL style).
     * @return A list of matches.
     */
    public AssetItemIterator queryAssets(String fieldPredicates,
                                         boolean seekArchived) {
        try {
            String sql;
            if ( isHistoricalVersion() ) {
                sql = "SELECT * FROM nt:frozenNode";
            } else {
                sql = "SELECT * FROM " + AssetItem.RULE_NODE_TYPE_NAME;
            }

            sql += " WHERE jcr:path LIKE '" + getVersionContentNode().getPath() + "/" + ASSET_FOLDER_NAME + "[%]/%'";
            if ( fieldPredicates.length() > 0 ) {
                sql += " and " + fieldPredicates;

            }

            if ( !seekArchived ) {
                sql += " AND " + AssetItem.CONTENT_PROPERTY_ARCHIVE_FLAG + " = 'false'";
            }

            sql += " ORDER BY " + AssetItem.TITLE_PROPERTY_NAME;

            Query q = node.getSession().getWorkspace().getQueryManager().createQuery( sql,
                                                                                      Query.SQL );

            long time = System.currentTimeMillis();
            QueryResult res = q.execute();

            NodeIterator it = res.getNodes();
            long taken = System.currentTimeMillis() - time;
            if ( taken > 2000 ) {
                log.debug( "QueryExec time is: " + (System.currentTimeMillis() - time) );
                log.debug( "SQL is " + sql );
                log.debug( it.getClass().getName() );
            }

            //return new AssetItemIterator(it, this.rulesRepository);
            return new VersionedAssetItemIterator( it,
                                                   this.rulesRepository,
                                                   this.getDependencies() );
        } catch ( RepositoryException e ) {
            throw new RulesRepositoryException( e );
        }
    }

    public AssetItemIterator queryAssets(String fieldPredicates) {
        return queryAssets( fieldPredicates,
                            false );
    }

    public AssetItemIterator listArchivedAssets() {
        return queryAssets( AssetItem.CONTENT_PROPERTY_ARCHIVE_FLAG + " = 'true'",
                            true );
    }

    public AssetItemIterator listAssetsByFormat(List<String> formatInList) {
        return listAssetsByFormat( formatInList.toArray( new String[formatInList.size()] ) );
    }

    /**
     * @return The header contents as pertains to a package of rule assets.
     */
    //    public String getHeader() {
    //        return this.getStringProperty( HEADER_PROPERTY_NAME );
    //    }

    //    public void updateHeader(String header) {
    //        updateStringProperty( header, HEADER_PROPERTY_NAME );
    public AssetItemIterator listAssetsWithVersionsSpecifiedByDependenciesByFormat(String... assetFormats) {
        AssetItemIterator assetItemIterator = listAssetsByFormat( assetFormats );
        ((VersionedAssetItemIterator) assetItemIterator).setReturnAssetsWithVersionsSpecifiedByDependencies( true );
        return assetItemIterator;
    }

    /**
     * This will load an iterator for assets of the given format type.
     */
    public AssetItemIterator listAssetsByFormat(String... formats) {

        if ( formats.length == 1 ) {
            return queryAssets( FORMAT_PROPERTY_NAME + "='" + formats[0] + "'" );
        } else {
            StringBuilder predicateBuilder = new StringBuilder( " ( " );
            for ( int i = 0; i < formats.length; i++ ) {
                predicateBuilder.append( FORMAT_PROPERTY_NAME ).append( "='" ).append( formats[i] ).append( "'" );
                if ( i != formats.length - 1 ) {
                    predicateBuilder.append( " OR " );
                }
            }
            predicateBuilder.append( " ) " );
            return queryAssets( predicateBuilder.toString() );
        }
    }

    public AssetItemIterator listAssetsNotOfFormat(String[] formats) {
        if ( formats.length == 1 ) {
            return queryAssets( "not drools:format='" + formats[0] + "'" );
        } else {
            StringBuilder predicateBuilder = new StringBuilder( "not ( " );
            for ( int i = 0; i < formats.length; i++ ) {
                predicateBuilder.append( "drools:format='" ).append( formats[i] ).append( "'" );
                if ( !(i == formats.length - 1) ) {
                    predicateBuilder.append( " OR " );
                }
            }
            predicateBuilder.append( " ) " );
            return queryAssets( predicateBuilder.toString() );
        }
    }

    /**
     * Load a specific rule asset by name.
     */
    public AssetItem loadAsset(String name) {
        try {
            Node content = getVersionContentNode();
            return new AssetItem(
                                  this.rulesRepository,
                                  content.getNode( ASSET_FOLDER_NAME ).getNode( name ) );
        } catch ( RepositoryException e ) {
            throw new RulesRepositoryException( e );
        }
    }

    /**
     * Returns true if this package item contains an asset of the given name.
     */
    public boolean containsAsset(String name) {
        Node content;
        try {
            content = getVersionContentNode();
            return content.getNode( ASSET_FOLDER_NAME ).hasNode( name );
        } catch ( RepositoryException e ) {
            throw new RulesRepositoryException( e );
        }
    }

    /**
     * Nicely formats the information contained by the node that this object
     * encapsulates
     */
    public String toString() {
        try {
            return "Content of the rule package named " + this.node.getName() + ":"
                    + "Description: " + this.getDescription() + "\n"
                    + "Format: " + this.getFormat() + "\n"
                    + "Last modified: " + this.getLastModified() + "\n"
                    + "Title: " + this.getTitle() + "\n"
                    + "----\n";
        } catch ( Exception e ) {
            log.error( "Caught Exception",
                       e );
            return "";
        }
    }

    /**
     * @return An iterator over the nodes history.
     */
    public PackageHistoryIterator getHistory() {
        return new PackageHistoryIterator( this.rulesRepository,
                                           this.node );
    }

    @Override
    public PackageItem getPrecedingVersion() throws RulesRepositoryException {
        try {
            Node precedingVersionNode = this.getPrecedingVersionNode();
            if ( precedingVersionNode != null ) {
                return new PackageItem( this.rulesRepository,
                                        precedingVersionNode );
            } else {
                return null;
            }
        } catch ( Exception e ) {
            log.error( "Caught exception",
                       e );
            throw new RulesRepositoryException( e );
        }
    }

    @Override
    public PackageItem getSucceedingVersion() throws RulesRepositoryException {
        try {
            Node succeedingVersionNode = this.getSucceedingVersionNode();
            if ( succeedingVersionNode != null ) {
                return new PackageItem( this.rulesRepository,
                                        succeedingVersionNode );
            } else {
                return null;
            }
        } catch ( Exception e ) {
            log.error( "Caught exception",
                       e );
            throw new RulesRepositoryException( e );
        }
    }

    /**
     * This will return a list of assets for a given state. It works through the
     * assets that belong to this package, and if they are not in the correct
     * state, walks backwards until it finds one in the correct state.
     * <p/>
     * If it walks all the way back up the versions looking for the "latest"
     * version with the appropriate state, and can't find one, that asset is not
     * included in the result.
     * <p/>
     * This will exclude any items that have the "ignoreState" set (so for
     * example, retired items, invalid items etc).
     *
     * @param state
     *            The state of assets to retrieve.
     * @param ignoreState
     *            The statuses to not include in the results (it will look at
     *            the status of the latest one).
     */
    public Iterator<AssetItem> getAssetsWithStatus(final StateItem state,
                                                   final StateItem ignoreState) {
        List<AssetItem> result = new LinkedList<AssetItem>();
        for ( Iterator<AssetItem> rules = getAssets(); rules.hasNext(); ) {
            AssetItem head = (AssetItem) rules.next();
            if ( head.sameState( state ) ) {
                result.add( head );
            } else if ( head.sameState( ignoreState ) ) {
                //ignore this one
            } else {
                List<AssetItem> fullHistory = new LinkedList<AssetItem>();
                for ( Iterator<AssetItem> iter = head.getHistory(); iter.hasNext(); ) {
                    AssetItem element = iter.next();
                    if ( !(element.getVersionNumber() == 0) ) {
                        fullHistory.add( element );
                    }
                }

                sortHistoryByVersionNumber( fullHistory );

                for ( Iterator<AssetItem> prev = fullHistory.iterator(); prev.hasNext(); ) {
                    AssetItem prevRule = prev.next();
                    if ( prevRule.sameState( state ) ) {
                        result.add( prevRule );
                        break;
                    }
                }
            }
        }
        return result.iterator();
    }

    void sortHistoryByVersionNumber(List<AssetItem> fullHistory) {
        Collections.sort( fullHistory,
                          new Comparator<AssetItem>() {
                              public int compare(AssetItem a1,
                                                 AssetItem a2) {
                                  long la1 = a1.getVersionNumber();
                                  long la2 = a2.getVersionNumber();
                                  return la1 == la2 ? 0 : (la1 < la2 ? 1 : -1);
                              }
                          } );
    }

    /**
     * This will return a list of assets for a given state. It works through the
     * assets that belong to this package, and if they are not in the correct
     * state, walks backwards until it finds one in the correct state.
     * <p/>
     * If it walks all the way back up the versions looking for the "latest"
     * version with the appropriate state, and can't find one, that asset is not
     * included in the result.
     */
    public Iterator<AssetItem> getAssetsWithStatus(final StateItem state) {
        return getAssetsWithStatus( state,
                                    null );
    }

    /**
     * @return The external URI which will be used to sync this package to an
     *         external resource. Generally this will resolve to a directory in
     *         (for example) Subversion - with each asset being a file (with the
     *         format property as the file extension).
     */
    public String getExternalURI() {
        return this.getStringProperty( EXTERNAL_URI_PROPERTY_NAME );
    }

    //    }

    public void updateExternalURI(String uri) {
        updateStringProperty( uri,
                              EXTERNAL_URI_PROPERTY_NAME );
    }

    public void setCatRules(String map) {
        updateStringProperty( map,
                              CATEGORY_RULE_KEYS_PROPERTY_NAME );
    }

    public void updateCategoryRules(String keys,
                                    String values) throws RulesRepositoryException {
        //System.out.println("(updateCategoryRules) keys: " + keys + " Values: " + values );
        try {

            this.checkout();
            this.updateStringProperty( keys,
                                       CATEGORY_RULE_KEYS_PROPERTY_NAME );
            this.updateStringProperty( values,
                                       CATEGORY_RULE_VALUES_PROPERTY_NAME );

        } catch ( Exception e ) {
            log.error( "Caught Exception",
                       e );
            throw new RulesRepositoryException( e );
        }
    }

    private static HashMap<String, String> convertFromObjectGraphs(final String[] keys,
                                                                   final String[] values) {
        HashMap<String, String> hash = new HashMap<String, String>();

        for ( int i = 0; i < keys.length; i++ ) {
            hash.put( keys[i],
                      values[i] );
        }
        return hash;
    }

    public String[] convertStringToArray(String tagName) {
        //System.out.println("(convertStringToArray) Tags: " + tagName);
        List<String> list = new ArrayList<String>();

        StringTokenizer tok = new StringTokenizer( tagName,
                                                   "," );
        while ( tok.hasMoreTokens() ) {
            String currentTagName = tok.nextToken();
            list.add( currentTagName );
        }

        return list.toArray( new String[0] );

    }

    public HashMap<String, String> getCategoryRules() {
        return convertFromObjectGraphs( convertStringToArray( getCategoryRules( true ) ),
                                        convertStringToArray( getCategoryRules( false ) ) );
    }

    public String getCategoryRules(boolean keys) {
        if ( keys ) {
            return getStringProperty( CATEGORY_RULE_KEYS_PROPERTY_NAME );
        }
        return getStringProperty( CATEGORY_RULE_VALUES_PROPERTY_NAME );
    }

    /**
     * Update the checkin comment.
     */
    public void updateCheckinComment(String comment) {
        updateStringProperty( comment,
                              VersionableItem.CHECKIN_COMMENT );
    }

    /**
     * This will change the status of this package, and all the contained
     * assets. No new versions are created of anything.
     *
     * @param newState
     *            The status tag to change it to.
     */
    public void changeStatus(String newState) {
        StateItem stateItem = rulesRepository.getState( newState );
        updateState( stateItem );
        for ( Iterator<AssetItem> iter = getAssets(); iter.hasNext(); ) {
            iter.next().updateState( stateItem );
        }
    }

    /**
     * If the asset is a binary asset, then use this to update the content (do
     * NOT use text).
     */
    public PackageItem updateCompiledPackage(InputStream data) {
        checkout();
        try {
            Binary binary = this.node.getSession().getValueFactory().createBinary( data );
            this.node.setProperty( COMPILED_PACKAGE_PROPERTY_NAME,
                                   binary );
            this.node.setProperty( LAST_MODIFIED_PROPERTY_NAME,
                                   Calendar.getInstance() );
            return this;
        } catch ( RepositoryException e ) {
            log.error( "Unable to update the assets binary content",
                       e );
            throw new RulesRepositoryException( e );
        }
    }

    /**
     * This is a convenience method for returning the binary data as a byte
     * array.
     */
    public byte[] getCompiledPackageBytes() {

        try {
            Node ruleNode = getVersionContentNode();
            if ( ruleNode.hasProperty( COMPILED_PACKAGE_PROPERTY_NAME ) ) {
                Property data = ruleNode.getProperty( COMPILED_PACKAGE_PROPERTY_NAME );
                InputStream in = data.getBinary().getStream();

                // Create the byte array to hold the data
                byte[] bytes = new byte[(int) data.getLength()];

                // Read in the bytes
                int offset = 0;
                int numRead = 0;
                while ( offset < bytes.length
                        && (numRead = in.read( bytes,
                                               offset,
                                               bytes.length - offset )) >= 0 ) {
                    offset += numRead;
                }

                // Ensure all the bytes have been read in
                if ( offset < bytes.length ) {
                    throw new RulesRepositoryException( "Could not completely read binary package for " + getName() );
                }

                // Close the input stream and return bytes
                in.close();
                return bytes;
            } else {
                return null;
            }
        } catch ( Exception e ) {
            log.error( e.getMessage(),
                       e );
            if ( e instanceof RuntimeException ) throw (RuntimeException) e;
            throw new RulesRepositoryException( e );
        }
    }

    /**
     * Creates a nested package.
     */
    public PackageItem createSubPackage(String subPackageName) throws RepositoryException {

        this.checkout();
        log.info( "USER: {} CREATEING subpackage [{}] under [{}]",
                  new Object[]{getCurrentUserName(), subPackageName, getName()} );
        Node subPkgsNode;
        try {
            subPkgsNode = node.getNode( RulesRepository.RULE_PACKAGE_AREA );
        } catch ( PathNotFoundException e ) {
            subPkgsNode = node.addNode( RulesRepository.RULE_PACKAGE_AREA,
                                        "nt:folder" );
        }
        //        subPkgsNode.checkout();
        String assetPath = NodeUtils.makeJSR170ComplaintName( subPackageName );
        Node ruleSubPackageNode = subPkgsNode.addNode( assetPath,
                                                       PackageItem.RULE_PACKAGE_TYPE_NAME );

        ruleSubPackageNode.addNode( PackageItem.ASSET_FOLDER_NAME,
                                    "drools:versionableAssetFolder" );

        ruleSubPackageNode.setProperty( PackageItem.TITLE_PROPERTY_NAME,
                                        subPackageName );

        ruleSubPackageNode.setProperty( AssetItem.DESCRIPTION_PROPERTY_NAME,
                                        "" );
        ruleSubPackageNode.setProperty( AssetItem.FORMAT_PROPERTY_NAME,
                                        PackageItem.PACKAGE_FORMAT );
        ruleSubPackageNode.setProperty( PackageItem.CREATOR_PROPERTY_NAME,
                                        this.rulesRepository.getSession().getUserID() );
        Calendar lastModified = Calendar.getInstance();
        ruleSubPackageNode.setProperty( PackageItem.LAST_MODIFIED_PROPERTY_NAME,
                                        lastModified );
        ruleSubPackageNode.setProperty( PackageItem.CONTENT_PROPERTY_ARCHIVE_FLAG,
                                        false );

        return new PackageItem( this.rulesRepository,
                                ruleSubPackageNode );
    }

    /**
     * Returns a {@link PackageIterator} of its children
     *
     * @return a {@link PackageIterator} of its children
     */
    public PackageIterator listSubPackages() {
        try {
            return new PackageIterator( getRulesRepository(),
                                        node.getNode( RulesRepository.RULE_PACKAGE_AREA ).getNodes() );
        } catch ( PathNotFoundException e ) {
            return new PackageIterator();
        } catch ( RepositoryException e ) {
            throw new RulesRepositoryException( e );
        }
    }

    private String getCurrentUserName() {
        return this.rulesRepository.getSession().getUserID();
    }
}
TOP

Related Classes of org.drools.repository.PackageItem

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.