/* * Copyright 2005 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.guvnor.server.builder; import java.io.IOException; import java.io.StringReader; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Properties; import org.drools.builder.conf.DefaultPackageNameOption; import org.drools.compiler.DroolsError; import org.drools.compiler.DroolsParserException; import org.drools.guvnor.client.common.AssetFormats; import org.drools.guvnor.client.rpc.RuleAsset; import org.drools.guvnor.server.ServiceImplementation; import org.drools.guvnor.server.contenthandler.ContentHandler; import org.drools.guvnor.server.contenthandler.ContentManager; import org.drools.guvnor.server.contenthandler.ICompilable; import org.drools.guvnor.server.contenthandler.IRuleAsset; import org.drools.guvnor.server.selector.AssetSelector; import org.drools.guvnor.server.selector.BuiltInSelector; import org.drools.guvnor.server.selector.SelectorManager; import org.drools.guvnor.server.util.LoggingHelper; import org.drools.lang.descr.PackageDescr; import org.drools.repository.AssetItem; import org.drools.repository.AssetItemIterator; import org.drools.repository.PackageItem; import org.drools.repository.RulesRepositoryException; import org.drools.repository.VersionableItem; import org.drools.rule.Package; /** * This assembles packages in the BRMS into binary package objects, and deals * with errors etc. Each content type is responsible for contributing to the * package. * * @author Michael Neale */ public class ContentPackageAssembler { private static final LoggingHelper log = LoggingHelper.getLogger( ContentPackageAssembler.class ); private PackageItem packageItem; /** * We accumulate errors here. If they come from the builder, then we reset * the builders errors so as to not double report. It also means we can * track errors to the exact asset that caused it. */ private List<ContentAssemblyError> errors = new ArrayList<ContentAssemblyError>(); BRMSPackageBuilder builder; private String customSelectorName; private String buildMode; private String statusOperator; private String statusDescriptionValue; private boolean enableStatusSelector; private String categoryOperator; private String categoryValue; private boolean enableCategorySelector; /** * Use this if you want to build the whole package. * * @param pkg * The package. */ public ContentPackageAssembler(PackageItem pkg) { this( pkg, true ); } /** * @param pkg * The package. * @param compile * true if we want to build it. False and its just for looking at * source. */ public ContentPackageAssembler(PackageItem pkg, boolean compile) { this( pkg, compile, null, null, null, false, null, null, false, null ); } /** * @param assetPackage * The package. * @param compile * true if we want to build it. False and its just for looking at * source. * @param selectorConfigName */ public ContentPackageAssembler(PackageItem assetPackage, boolean compile, String buildMode, String statusOperator, String statusDescriptionValue, boolean enableStatusSelector, String categoryOperator, String categoryValue, boolean enableCategorySelector, String selectorConfigName) { this.packageItem = assetPackage; this.customSelectorName = selectorConfigName; this.buildMode = buildMode; this.statusOperator = statusOperator; this.statusDescriptionValue = statusDescriptionValue; this.enableStatusSelector = enableStatusSelector; this.categoryOperator = categoryOperator; this.categoryValue = categoryValue; this.enableCategorySelector = enableCategorySelector; createBuilder(); if ( compile && preparePackage() ) { buildPackage(); } } /** * Use this if you want to build and compile just the one asset. */ public ContentPackageAssembler(AssetItem assetToBuild) { this.packageItem = assetToBuild.getPackage(); createBuilder(); if ( preparePackage() ) { buildAsset( assetToBuild ); } } public ContentPackageAssembler(RuleAsset asset, PackageItem packageItem) { this.packageItem = packageItem; createBuilder(); if ( preparePackage() ) { buildAsset( asset ); } } public void createBuilder() { try { Properties ps = loadConfProperties( packageItem ); ps.setProperty( DefaultPackageNameOption.PROPERTY_NAME, this.packageItem.getName() ); builder = BRMSPackageBuilder.getInstance( BRMSPackageBuilder.getJars( packageItem ), ps ); } catch ( IOException e ) { throw new RulesRepositoryException( "Unable to load configuration properties for package.", e ); } } /** * Load all the .properties and .conf files into one big happy Properties instance. */ Properties loadConfProperties(PackageItem pkg) throws IOException { Properties ps = new Properties(); AssetItemIterator iter = pkg.listAssetsByFormat( new String[]{"properties", "conf"} ); while ( iter.hasNext() ) { AssetItem conf = iter.next(); conf.getContent(); Properties p = new Properties(); p.load( conf.getBinaryContentAttachment() ); ps.putAll( p ); } return ps; } /** * This will build the package - preparePackage would have been called first. * This will always prioritise DRL before other assets. */ private void buildPackage() { AssetSelector selector = null; if ( "customSelector".equals( buildMode ) ) { selector = SelectorManager.getInstance().getSelector( customSelectorName ); } else if ( "builtInSelector".equals( buildMode ) ) { selector = (BuiltInSelector) SelectorManager.getInstance().getSelector( "BuiltInSelector" ); ((BuiltInSelector) selector).setStatusOperator( statusOperator ); ((BuiltInSelector) selector).setStatus( statusDescriptionValue ); ((BuiltInSelector) selector).setEnableStatusSelector( enableStatusSelector ); ((BuiltInSelector) selector).setCategory( categoryValue ); ((BuiltInSelector) selector).setCategoryOperator( categoryOperator ); ((BuiltInSelector) selector).setEnableCategorySelector( enableCategorySelector ); } else { //return the NilSelector, i.e., allows everything selector = SelectorManager.getInstance().getSelector( null ); } if ( selector == null ) { this.errors.add( new ContentAssemblyError( this.packageItem, "The selector named " + customSelectorName + " is not available." ) ); return; } StringBuffer includedAssets = new StringBuffer( "Following assets have been included in package build: " ); Iterator<AssetItem> drls = packageItem.listAssetsByFormat( new String[]{AssetFormats.DRL} ); while ( drls.hasNext() ) { AssetItem asset = (AssetItem) drls.next(); if ( !asset.isArchived() && (selector.isAssetAllowed( asset )) ) { buildAsset( asset ); includedAssets.append( asset.getName() + ", " ); } } Iterator<AssetItem> it = packageItem.getAssets(); while ( it.hasNext() ) { AssetItem asset = (AssetItem) it.next(); if ( !asset.getFormat().equals( AssetFormats.DRL ) && !asset.isArchived() && (selector.isAssetAllowed( asset )) ) { buildAsset( asset ); includedAssets.append( asset.getName() + ", " ); } } log.info( includedAssets.toString() ); } /** * Builds assets that are "rule" assets (ie things that are not functions * etc). */ private void buildAsset(AssetItem asset) { ContentHandler h = ContentManager.getHandler( asset.getFormat() ); if ( h instanceof ICompilable && !asset.getDisabled() ) { try { ((ICompilable) h).compile( builder, asset, new ErrorLogger() ); if ( builder.hasErrors() ) { this.recordBuilderErrors( asset ); // clear the errors, so we don't double report. builder.clearErrors(); } } catch ( DroolsParserException e ) { throw new RulesRepositoryException( e ); } catch ( IOException e ) { throw new RulesRepositoryException( e ); } } } private void buildAsset(RuleAsset asset) { ContentHandler h = ContentManager.getHandler( asset.metaData.format ); if ( h instanceof ICompilable && !asset.metaData.disabled ) { try { ((ICompilable) h).compile( builder, asset, new ErrorLogger() ); if ( builder.hasErrors() ) { this.recordBuilderErrors( asset ); // clear the errors, so we don't double report. builder.clearErrors(); } } catch ( DroolsParserException e ) { throw new RulesRepositoryException( e ); } catch ( IOException e ) { throw new RulesRepositoryException( e ); } } } /** * This prepares the package builder, loads the jars/classpath. * * @return true if everything is good to go, false if its all gone horribly * wrong, and we can't even get the package header up. */ private boolean preparePackage() { // firstly we loadup the classpath builder.addPackage( new PackageDescr( packageItem.getName() ) ); loadDeclaredTypes(); // now we deal with the header (imports, templates, globals). addDrl( ServiceImplementation.getDroolsHeader( packageItem ) ); if ( builder.hasErrors() ) { recordBuilderErrors( packageItem ); // if we have any failures, lets drop out now, no point in going // any further return false; } loadDSLFiles(); // finally, any functions we will load at this point. AssetItemIterator it = this.packageItem.listAssetsByFormat( new String[]{AssetFormats.FUNCTION} ); // Adds the function DRLs as one string because they might be calling each others. StringBuilder stringBuilder = new StringBuilder(); while ( it.hasNext() ) { AssetItem func = it.next(); if ( !func.getDisabled() ) { stringBuilder.append( func.getContent() ); } } addDrl( stringBuilder.toString() ); // If the function part had errors we need to add them one by one to find out which one is bad. if ( builder.hasErrors() ) { builder.clearErrors(); it = this.packageItem.listAssetsByFormat( new String[]{AssetFormats.FUNCTION} ); while ( it.hasNext() ) { AssetItem func = it.next(); if ( !func.getDisabled() ) { addDrl( func.getContent() ); if ( builder.hasErrors() ) { recordBuilderErrors( func ); builder.clearErrors(); } } } } return errors.size() == 0; } private void loadDeclaredTypes() { AssetItemIterator it = this.packageItem.listAssetsByFormat( new String[]{AssetFormats.DRL_MODEL} ); while ( it.hasNext() ) { AssetItem as = it.next(); if ( !as.getDisabled() ) { try { String content = as.getContent(); if ( nonEmpty( content ) ) { builder.addPackageFromDrl( new StringReader( as.getContent() ) ); } } catch ( DroolsParserException e ) { this.errors.add( new ContentAssemblyError( as, "Parser exception: " + e.getMessage() ) ); } catch ( IOException e ) { this.errors.add( new ContentAssemblyError( as, "IOException: " + e.getMessage() ) ); } } } } private boolean nonEmpty(String content) { return content != null && content.trim().length() > 0; } private void loadDSLFiles() { // now we load up the DSL files builder.setDSLFiles( BRMSPackageBuilder.getDSLMappingFiles( packageItem, new BRMSPackageBuilder.DSLErrorEvent() { public void recordError(AssetItem asset, String message) { errors.add( new ContentAssemblyError( asset, message ) ); } } ) ); } /** * This will return true if there is an error in the package configuration * or functions. * * @return */ public boolean isPackageConfigurationInError() { if ( this.errors.size() > 0 ) { return this.errors.get( 0 ).isPackageItem(); } else { return false; } } private void addDrl(String drl) { if ( "".equals( drl.trim() ) ) { return; } try { builder.addPackageFromDrl( new StringReader( drl ) ); } catch ( DroolsParserException e ) { throw new RulesRepositoryException( "Unexpected error when parsing package.", e ); } catch ( IOException e ) { throw new RulesRepositoryException( "IO Exception occurred when parsing package.", e ); } } /** * This will accumulate the errors. */ private void recordBuilderErrors(VersionableItem asset) { DroolsError[] errs = builder.getErrors().getErrors(); for ( int i = 0; i < errs.length; i++ ) { this.errors.add( new ContentAssemblyError( asset, errs[i].getMessage() ) ); } } private void recordBuilderErrors(RuleAsset asset) { DroolsError[] errs = builder.getErrors().getErrors(); for ( int i = 0; i < errs.length; i++ ) { this.errors.add( new ContentAssemblyError( asset, errs[i].getMessage() ) ); } } /** * I've got a package people ! */ public Package getBinaryPackage() { if ( this.hasErrors() ) { throw new IllegalStateException( "There is no package available, as there were errors." ); } return builder.getPackage(); } public boolean hasErrors() { return errors.size() > 0; } public List<ContentAssemblyError> getErrors() { return this.errors; } public BRMSPackageBuilder getBuilder() { return builder; } /** * This is passed in to the compilers so extra errors can be added. * * @author Michael Neale */ public class ErrorLogger { public void logError(ContentAssemblyError err) { errors.add( err ); } } public String getDRL() { StringBuffer src = new StringBuffer(); src.append( "package " + this.packageItem.getName() + "\n" ); src.append( ServiceImplementation.getDroolsHeader( this.packageItem ) + "\n\n" ); // now we load up the DSL files builder.setDSLFiles( BRMSPackageBuilder.getDSLMappingFiles( packageItem, new BRMSPackageBuilder.DSLErrorEvent() { public void recordError(AssetItem asset, String message) { errors.add( new ContentAssemblyError( asset, message ) ); } } ) ); // do the functions and declared types. AssetItemIterator it = this.packageItem.listAssetsByFormat( new String[]{AssetFormats.FUNCTION, AssetFormats.DRL_MODEL} ); while ( it.hasNext() ) { AssetItem func = it.next(); if ( !func.isArchived() && !func.getDisabled() ) { src.append( func.getContent() ).append( "\n\n" ); } } // now the rules Iterator<AssetItem> iter = packageItem.getAssets(); while ( iter.hasNext() ) { AssetItem asset = (AssetItem) iter.next(); if ( !asset.isArchived() && !asset.getDisabled() ) { ContentHandler handler = ContentManager.getHandler( asset.getFormat() ); if ( handler.isRuleAsset() ) { IRuleAsset ruleAsset = (IRuleAsset) handler; ruleAsset.assembleDRL( builder, asset, src ); } src.append( "\n\n" ); } } return src.toString(); } }