Skip to Content
Publication extensions let you customize the publication process by writing custom code that can be injected either post processing or post delivery of the publication. An introduction in publication extension can be found in this blog post.
This is a post processing publication extension that delivers a shortcut of the publication artifact to a BI platform folder based on personalization. For example if you personalize on Country field and have a folder USA and Canada, then a shortcut for the publication artifact filtered to USA will be created in a USA folder, and a shortcut for the Canada document will be created in a Canada folder. Note: this extension is rather an example of how to use publication extensions. You can download a zip file containing the publication extension jar file from here. It was complied based on BI 4.0 SP5, if you want to use it on a 3.1 system you need to recompile the java code using a 32bit java version.
The extension has a few pre-requirements:
  • A folder with the personalization has to exist on the BI platform
  • You need to have at least one other destination other than default destination selected in order to have the publication extension executed.
  • On the BOE server you need to have a C:\PubDemo\logs\ folder. If this folder is not there then the publication will fail as this is a hardcoded value.
  • The publication extension jar file need to be added in the BOE server in the following directory <Enterprise Dir> \java\lib\publishingPlugins. SIA has to be restarted after dropping the jar file.

Below is the source code as example. I have been out of development for a few years, so please excuse if it is not the best piece of code. At the bottom of this blog you can also find an example scenario for a publication that uses this extension.

package com.businessobjects.publishing.processing.plugin.example;
import java.io.FileWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import com.businessobjects.publisher.common.IFilterClause;
import com.businessobjects.publisher.common.IPersonalizationEntry;
import com.businessobjects.publisher.common.IScopeFilter;
import com.businessobjects.publisher.common.IScopeUserInfo;
import com.businessobjects.publisher.postprocessing.IPublicationPostProcessingContext;
import com.businessobjects.publisher.postprocessing.IPublicationPostProcessingPlugin;
import com.businessobjects.publisher.postprocessing.PluginTargetDestination;
import com.crystaldecisions.sdk.occa.infostore.IDestinationPluginArtifactFormat;
import com.crystaldecisions.sdk.occa.infostore.IInfoObject;
import com.crystaldecisions.sdk.occa.infostore.IInfoObjects;
import com.crystaldecisions.sdk.occa.infostore.IInfoStore;
import com.crystaldecisions.sdk.plugin.CeProgID;
import com.crystaldecisions.sdk.plugin.desktop.common.IPersonalizationVariableMapping;
import com.crystaldecisions.sdk.plugin.desktop.common.IPersonalizationVariableValue;
import com.crystaldecisions.sdk.plugin.desktop.shortcut.IShortcut;
import com.crystaldecisions.sdk.occa.infostore.CePropertyID;
import com.crystaldecisions.sdk.occa.pluginmgr.IPluginMgr;
import com.crystaldecisions.sdk.occa.pluginmgr.IPluginInfo;

public class CopyShortCutToBoeFolderPlugin  implements IPublicationPostProcessingPlugin {

  IInfoStore m_infoStore = null;
  /**
      * <p>
      * Returns the collection of destinations to which artifacts from this plugin are sent (if those destinations
      * are enabled for the publication). All supported destinations must be added to the collection.
      * If this method returns null or the collection is empty, then the target destinations must
      * be configured using either
      * @return Collection containing the {@link PluginTargetDestination} objects.
      * @throws Exception This is thrown if the operation does not complete successfully.
      */
     public Collection getTargetDestinations() throws Exception {
        
         PluginTargetDestination inboxDestination =
             new PluginTargetDestination(CeProgID.MANAGED_DEST,
             IDestinationPluginArtifactFormat.CeDistributionMode.FILTER_EXCLUDE_SOURCE_DOCUMENTS);
    
         PluginTargetDestination smtpDestination =
             new PluginTargetDestination(CeProgID.SMTP,
             IDestinationPluginArtifactFormat.CeDistributionMode.FILTER_EXCLUDE_SOURCE_DOCUMENTS);
        
         PluginTargetDestination diskDestination =
             new PluginTargetDestination(CeProgID.DISKUNMANAGED,
             IDestinationPluginArtifactFormat.CeDistributionMode.FILTER_EXCLUDE_SOURCE_DOCUMENTS);
        
         PluginTargetDestination ftpDestination =
             new PluginTargetDestination(CeProgID.FTP,
             IDestinationPluginArtifactFormat.CeDistributionMode.FILTER_EXCLUDE_SOURCE_DOCUMENTS);
    
         PluginTargetDestination[] destinations = { inboxDestination, smtpDestination, diskDestination, ftpDestination };
        
         return Arrays.asList(destinations);
 
     }
     /**
      * <p>
      * This method is invoked after the publishing engine has finished personalizing
      * the publication for a particular destination and scope. It must return an
      * <code>IInfoObjects</code> collection containing all the artifacts created by the
      * plugin that will be delivered to the recipients associated with the current
      * destination and scope.
      * </p>
      * @param context An object containing contextual information, including the
      * current session, the scope, and the destination.
      * @return <code>IInfoObjects</code> collection containing the artifacts generated by this plugin.
      * @throws Exception This is thrown if the operation does not complete successfully.
      */
     public IInfoObjects handle(IPublicationPostProcessingContext context) throws Exception {
      FileWriter outFile = new FileWriter(“C:\\PubDemo\\logs\\Publication Post Processing ” + context.getPublication().getTitle() + ” ” + System.currentTimeMillis()+”.log”);
             
         // get the static documents and schedulable document artifacts
         // for the current scope and destination.
         ArrayList docs = context.getDocuments();
         Iterator docIt = docs.iterator();
         m_infoStore = context.getInfoStore();
         IInfoObjects objs = m_infoStore.newInfoObjectCollection();
         while (docIt.hasNext()) {
        
         
          IInfoObject obj = (IInfoObject)docIt.next();
            
          String newFileName = obj.getTitle();
         
          //get the IScopeFilter for the current document, the IScopeFilter interface can be used to obtain personalization information
             IScopeFilter scopeFilter = context.getScopeFilter(obj);
            
             outFile.write(“Scope ID = ” + scopeFilter.getScopeID() + ” for ” + obj.getTitle() + “\n”);
             outFile.write(“\tUsers listed in this scope:\n”);
            
             // retrieve the user information from for this scope
             List userInfos = scopeFilter.getScopeUserInfos();
             Iterator userInfoIt = userInfos.iterator();
             while (userInfoIt.hasNext()) {
                 IScopeUserInfo userInfo = (IScopeUserInfo)userInfoIt.next();
                 outFile.write(“\t\t Enterprise User ID: ” + userInfo.getEnterpriseUserID() +
                      ” \n \t\t User Name: ” + userInfo.getUserName() +
                      ” \n \t\t User Full Name: ” +userInfo.getUserFullName() +
                      ”  \n \t\t User Email Address: ” + userInfo.getUserEmailAddress() + “\n \n”);
             }
            
             // retrieve the root of the tree of logical clauses
             // the FilterClause can either be a leaf clause (with no sub-clauses) or a non-leaf clause (with sub-clauses)
             IFilterClause rootClause = scopeFilter.getRootClause();
            
             //get the string of all personalization for this scope
             String personalizationString = getPersonlizationString(rootClause);
            
             outFile.write(“\tFilterClause: ” + personalizationString + “\n”);
            
             //rename the artifacts to include the personalization values
             if (personalizationString.length() == 0)
              newFileName = newFileName + ” No personalization ” ;
             else
              newFileName = newFileName + ” ” + personalizationString ;
            
             //retrieving the folder ID, for now it is assumed that the name of the persoanlization variable was passed
             //into the extension and is stored as string in the options
             String variable = context.getOptions().toString();
             int folderID = getFolderID(rootClause, variable);
            
             if (folderID != -1){
              outFile.write(“Copying ” + newFileName + ” to folder with ID” + folderID + “\n”);
              // create the actual short cut
              createShortCut(obj, folderID, newFileName);
             }else {
              outFile.write(“FolderID = -1”);
    }
             objs.add(obj);
         }
         outFile.close();
         return objs;
     }
    
     /**
      * This method will create the shortcut
      * @param artifact publication infoboject for which a shortcut shall be created
      * @param parentID ID of the folder in which to place the short cut
      * @param fileName name of the short cut object
      * @throws Exception
      */
     private void createShortCut(IInfoObject artifact, int parentID, String fileName) throws Exception{
     
       //Retrieve the PluginMgr object.
       IPluginMgr oPluginMgr = m_infoStore.getPluginMgr();
  
       //Use the PluginManager to retrieve the Shortcut plugin, which is needed to
       //create a new shortcut.
       IPluginInfo oPluginInfoNewShortcut = oPluginMgr.getPluginInfo(“CrystalEnterprise.Shortcut”);
  
       //Create a new InfoObjects collection to add the new shortcut to
       IInfoObjects oInfoObjects = m_infoStore.newInfoObjectCollection();
  
       //Add the newly created shortcut plugin to the oInfoObjects collection. This will create
       //a new InfoObject based on the type of plugin passed in.
       oInfoObjects.add(oPluginInfoNewShortcut);
  
       //Retrieve the newly created shortcut InfoObject and set its properties
       IShortcut oShortcut = (IShortcut)oInfoObjects.get(0);
 
       //Specify the report to create the shortcut for
       oShortcut.setTargetID(artifact.getID());
 
       //Set the location of the shortcut
       oShortcut.properties().setProperty(CePropertyID.SI_PARENTID, parentID);
       oShortcut.setTitle(“ShortCut: ” + fileName);
  
       //Commit the changes to the CMS using the commit method. This adds the report shortcut.
       m_infoStore.commit(oInfoObjects);
     }
    
     /**
      * This method retrieves the the folder id for the short cut
      * @param clause filter clause
      * @param variable persoanlization variable that the publication was filtered on
      * @return Folder ID (-1 if folder does not exists)
      * @throws Exception
      */
     private int getFolderID(IFilterClause clause, String variable) throws Exception{
      int folderID = -1;
      //retrieve the personalization value for the given variable
            String folderName = getPersonlizationValue(clause,  variable);
           
            String queryString = “Select SI_ID from ci_infoobjects where SI_NAME ='” + folderName +”‘ and SI_KIND=’Folder'”;
            // Query the CMS for the folder.
            IInfoObjects folders = m_infoStore.query(queryString);
            if (folders.size() == 0)
            {
             // The query returned a blank collection (no object found).          
             return folderID;           
            }
           
            IInfoObject folderObj = (IInfoObject) folders.get(0);
            folderID = folderObj.getID();
     
      return folderID;
     }
    
     /**
      * This method retieves the personalization value for the given variable (e.g. Canada for variable country)
      * @param clause filter clause
      * @param variable persoanlization variable that the publication was filtered on
      * @return personalization value
      * @throws Exception
      */
     private String getPersonlizationValue(IFilterClause clause, String variable) throws Exception{
      String vValue = “”;
     
      //navigate through the filter tree to find the personalization value
      if (clause.getClauseType() == IFilterClause.ClauseType.LEAF) {
       IPersonalizationEntry entry = clause.getEntry();
             IPersonalizationVariableMapping mapping = entry.getVariableMapping();
             String vName = mapping.getVariableName();
             //check whether variable matched what we’re looking for
             if(vName.equals(variable)){
             

              IPersonalizationVariableValue value=entry.getVariableValue();
             
              if (value.getValueType() == IPersonalizationVariableValue.CeVariableValueType.FilterExpression)
                  vValue=value.getExpression();
              else if (value.getValueType()==IPersonalizationVariableValue.CeVariableValueType.ObjectValue)
              {
                  Object object=value.getObjectValue();
                  vValue=object.toString();
              }
              else
              {
                  vValue = “UNKNOWN value type!”;
              }
              return  vValue;
             }            
         } else {
             //if not a leaf go down further
             Iterator subClausesIt = clause.getSubClauses().iterator();
             while (subClausesIt.hasNext()) {
                 IFilterClause subClause = (IFilterClause)subClausesIt.next();
                 vValue = getPersonlizationValue(subClause, variable);
             }
            
         }
      return vValue;
     }
    
     /**
      * This method gets the string of all personalization for this scope
      * @param clause filter clause
      * @return string of all personalization for this scope
      */
     private String getPersonlizationString(IFilterClause clause) {
         if (clause.getClauseType() == IFilterClause.ClauseType.LEAF) {
             IPersonalizationEntry entry = clause.getEntry();
             IPersonalizationVariableMapping mapping = entry.getVariableMapping();

             String vName = mapping.getVariableName();
             String vValue = “”;
             IPersonalizationVariableValue value=entry.getVariableValue();
             if (value.getValueType() == IPersonalizationVariableValue.CeVariableValueType.FilterExpression)
                 vValue=value.getExpression();
             else if (value.getValueType()==IPersonalizationVariableValue.CeVariableValueType.ObjectValue)
             {
                 Object object=value.getObjectValue();
                 vValue=object.toString();
             }
             else
             {
                 vValue = “UNKNOWN value type!”;
             }
             return  vName + ” = ” + vValue;
         } else {
             String joinString;
             String result = “”;
             if (clause.getClauseType() == IFilterClause.ClauseType.AND)
                 joinString = “AND”;
             else if (clause.getClauseType() == IFilterClause.ClauseType.OR)
                 joinString = “OR”;
             else
                 joinString = “UNKNOWN”;
             Iterator subClausesIt = clause.getSubClauses().iterator();
             while (subClausesIt.hasNext()) {
                 IFilterClause subClause = (IFilterClause)subClausesIt.next();
                 String subClauseStr = getPersonlizationString(subClause);
                 if (subClause.getClauseType() != IFilterClause.ClauseType.LEAF)
                     subClauseStr = “(” + subClauseStr + “)”;
                 if (result.length() > 0)
                     result += ” ” + joinString + ” “;
                 result += subClauseStr;
             }
             return result;
         }
     }
    
}

Example scenario:

  • Create the following artifacts:
    • User/User Groups:
      • Canadian User Group
        • Joanne
      • American User Group
        • Glenda
      • Jane
        • Does not belong to a group
        • Is not part of the publication
    • Country Profile
      • Canadian User Group     – Country = Canada
      • American User Group    – Country = USA
    • “Publication Demo” folder
      • Publication folder
        • Any CR or WebI report that can be filtered by country
        • Publication
          • Uses above CR or WebI report as source document
          • Recipients
            • Canadian User Group
            • American User Group
        • Personalization on Country field to Country profile
        • BI Inbox destination
        • Uses Post Processing Publication Extension with following settings:
          • Publication Extension Name: CopyToBoeFolderPlugin
          • Class Name: com.businessobjects.publishing.processing.plugin.example.CopyToBoeFolderPlugin
          • Parameter: {Customer.Country}
            • I designed the extension so that you need to specify which personalization field shall be used to determine which folder to deliver the copy to, this is in case you personalize on country as well as region
            • For some reason CR uses curly brackets for report fields so they need to be specified as well, one could make the extension more user friendly 🙂
      • Canada folder
        • This is where the artifacts for Canadian User Group will be delivered to
        • Canadian User Group has full control
        • American User Group has no access
      • USA folder
        • This is where the artifacts for American User Group will be delivered to
        • American User Group has full control
        • Canadian User Group has no access
  • Scenario:
    1. Logon as Administrator and Schedule the publication
    2. Logon as Glenda
      1. navigate to the USA folder
      2. open the USA report
      3. note Glenda is not able to see the Canada Folder or the Publication folder
    3. Repeat step 2 for Joanne and the Canada folder
    4. Logon as Jane
      1. See that Jane has no view rights to anything
    5. Logon as Administrator
    6. Add Jane to the Canadian user group
    7. Logon as Jane
    8. Navigate to the Canada folder and see old artifacts of the publication
To report this post you need to login first.