Monday, April 15, 2013

Tridion Odata retrieves 25 results by default (this setting can be overridden)


If I send a query to the OData service it is restricting a number of results to 25. 

Eg:
The following filter is having more than 25 results actually but the Odata is returning the first 25 results alone. The same behaviour is showing for Components, ComponentPresentations.
List<OEntity> customMetasSg1 = new ArrayList<OEntity>(100);
customMetasSg1 = consumer.getEntities("CustomMetas")
.filter(
"KeyName eq 'StructureGroupID'"
)
.execute().toList();
Output : customMetasSg1.size()25


Is there a way this setting can be overridden to get all the results based on query criteria?

Yes
Option 1: setting &top=XX
Option 2: setting DefaultTop in cd_webservice_conf.xml as described http://sdllivecontent.sdl.com/LiveContent/content/en-US/SDL_Tridion_2011_SPONE/cd_webservice_confN1001C#addHistory=true&filename=cd_webservice_confReference.ConfigNodeN1000D.xml&docid=cd_webservice_confN1000D&inner_id=&tid=&query=&scope=&resource=&eventType=lcContent.loadDoccd_webservice_confN1000D

Tridion Deployer Extensions - Insert DCPs in custom Database - Custom Module

For getting started with tridion deployer extensions please refer
https://sdltridionworld.com/articles/sdltridion2011/tutorials/Deployer_Extensions_With_Eclipse_1.aspx
Below is just sample requirement and sample code of deployer extension.

Requirement

After Successful Publish , Some of  DCP's (Dynamic Component Presentations) should be saved in Custom DB along with Broker DB.
Below Solution gives you example of:

  • How to read tridion dynamic component presentations from transcation ZIP file
  • How to read config values form cd_deployer_conf.xml  in your Java program config.getParameterValue
  • How to read Metadata of Component using data.getMetaData Tridion API
  • How to talk to JDBC and insert content 
  • How to extend Module 
  • How to write Custom Module

Solution

I have certain templates whose DCPs need to be saved in DV

  1. Publish XML
  2. GSA Content XML
  3. GSA Asset XML


All CONTENT(NOT Binary) published with above Component Templates should be saved in custom DB
All BINARY Content published with GSA Asset XML DCP should be saved in custom DB


Sample cd_deployer_conf.xml entry



In your Java Project refer following libraries

Sample Java Code (Deployer Extensions):
package com.tridion.abc.extensions;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.Calendar;
import java.util.Iterator;

import javax.naming.Context;
import javax.naming.NamingException;
import javax.sql.DataSource;

import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.tridion.configuration.Configuration;
import com.tridion.configuration.ConfigurationException;
import com.tridion.deployer.Module;
import com.tridion.deployer.ProcessingException;
import com.tridion.deployer.Processor;
import com.tridion.transport.transportpackage.Component;
import com.tridion.transport.transportpackage.ComponentPresentation;
import com.tridion.transport.transportpackage.ComponentPresentationKey;
import com.tridion.transport.transportpackage.MetaData;
import com.tridion.transport.transportpackage.MetaDataFile;
import com.tridion.transport.transportpackage.ProcessorInstructions;
import com.tridion.transport.transportpackage.Section;
import com.tridion.transport.transportpackage.TransportPackage;
import com.tridion.util.TCMURI;

public class InsertFeedintoDB extends Module {
private static Logger log = LoggerFactory
.getLogger(InsertFeedintoDB.class);
String action = null;
String contentXML = null;
String _GSAContentXML = null;
String _GSAAssetXML = null;
static String _DBSourceURL = null;
private static final String SQL_SELECTBY_CONTENTID = "SELECT CNTN_ID FROM C0064DBA.TIBPUB_EVNT WHERE CNTN_ID=?";
private static final String SQL_INSERT_COMPONENT = "INSERT INTO C0064DBA.TIBPUB_EVNT (PUB_EVNT_ID,CNTN_ID,EVNT_TS, CNTN_XML,EVNT_TYP,SCHM_NM ,GSA_CNT_XML , GSA_AST_XML) VALUES (C0064DBA.PUB_EVNT_SEQ.NEXTVAL,?,?,?,?,?,?,?)";
private static final String SQL_UPDATE_PUBLISHXML = "UPDATE C0064DBA.TIBPUB_EVNT SET CNTN_XML=?, EVNT_TS=?,EVNT_TYP=? WHERE CNTN_ID=?";
private static final String SQL_UPDATE_GSAASSETXML = "UPDATE C0064DBA.TIBPUB_EVNT SET GSA_AST_XML=?, EVNT_TS=?,EVNT_TYP=?  WHERE CNTN_ID=?";
private static final String SQL_UPDATE_GSACONTENTXML = "UPDATE C0064DBA.TIBPUB_EVNT SET GSA_CNT_XML=?, EVNT_TS=?,EVNT_TYP=?  WHERE CNTN_ID=?";

static DataSource _datasource = null;
//String itemXMLCLOB = null;
InputStream itemXMLCLOB;
int fileLength=0;
MetaDataFile compPresMetadata = null;
Connection conn = null;
MetaDataFile componentMeta = null;
String _RppAssetSchemaName = null;
String _RppAssetSchemaNameSpace = null;

public InsertFeedintoDB(Configuration config, Processor processor)
throws ConfigurationException {
super(config, processor);
log.info("InsertRPPFeedintoDB invoked");
}

// This method is called once for each TransportPackage that is
// deployed.
public void process(TransportPackage data) throws ProcessingException {
try {
contentXML = config.getParameterValue("PublishXMLTemplateName");
_GSAContentXML = config
.getParameterValue("GSAContentXMLTemplateName");
_GSAAssetXML = config.getParameterValue("GSAAssetTemplateName");
_DBSourceURL = config.getParameterValue("DBSourceURL");
_RppAssetSchemaName = config.getParameterValue("RppAssetSchemaName");
_RppAssetSchemaNameSpace = config.getParameterValue("RppAssetSchemaNameSpace");
log.debug("XML template value from config :" + "CONTENT XML:"
+ contentXML + "GSA CONTENT XML:" + _GSAContentXML
+ "GSA ASSET XML:" + _GSAAssetXML + "DB Url:"
+ _DBSourceURL);
getDataSource();
conn = getConnectionFromDataSource(_datasource);
ProcessorInstructions instructions = data
.getProcessorInstructions();
action = instructions.getAction();
MetaData compPresMetadataInfo = instructions
.getMetaData("ComponentPresentations");
MetaData compMetadataInfo = instructions.getMetaData("Components");
componentMeta = data.getMetaData("Components",
compMetadataInfo.getName());
compPresMetadata = data.getMetaData("ComponentPresentations",
compPresMetadataInfo.getName());
log.debug("Action " + action + " started for publication "
+ instructions.getPublicationId());

Section section = null;
Iterator<Section> Sections = instructions.getSections();
for (; Sections.hasNext(); processSection(section, data)) {
section = Sections.next();
}
// TODO: Insert into RPP DB
log.info("process started for inserting items into RPP Database");
} catch (ConfigurationException ex) {
log.error("Could not get custom configuration", ex.getMessage());
}
catch (Exception ex) {
log.error("Exception Occured", ex.getMessage());
}
finally {
try {
if (conn!=null)
conn.close();
} catch (Exception ex) {
log.error("InsertRPP DB Exception closing connection"
+ ex.getMessage());
}
}

}

protected void processSection(Section section, TransportPackage data) {
log.debug("RPP Processing Section " + section.getName());
Iterator iterator = section.getFileItems();
Object item;
for (; iterator.hasNext(); processItem(item, section, data)) {
item = iterator.next();
}
Section subSection;
for (Iterator i$ = section.getSubSections().iterator(); i$.hasNext(); processSection(
subSection, data))
subSection = (Section) i$.next();
}

protected void processItem(Object obj, Section section,
TransportPackage data) {
if (obj instanceof ComponentPresentationKey) {
log.debug("Entered ComponentPresentation");
ComponentPresentationKey cpKey = (ComponentPresentationKey) obj;
ComponentPresentation componentPresentation = (ComponentPresentation) compPresMetadata
.getMetaData(cpKey);
log.debug("componentPresentation " + componentPresentation);
log.debug("componentPresentationgetTemplateKey getTitle()"
+ componentPresentation.getTemplateKey().getTitle());
try {
File cpFile = new File((new StringBuilder())
.append(data.getLocationPath())
.append(section.getRelativePath())
.append(cpKey.getName()).toString());
fileLength = (int) cpFile.length();
itemXMLCLOB=(InputStream) new FileInputStream(cpFile); // Added to get input stream from file
//itemXMLCLOB = IOUtils.toString(new FileReader(cpFile));
/**log.debug("itemXMLCLOB"
+ itemXMLCLOB);**/
TCMURI compTCMID = componentPresentation.getComponentKey()
.getId();
Component component = (Component)componentMeta.getMetaData(componentPresentation.getComponentKey());
log.debug("component"
+ component);
log.debug("compTCMID"
+ compTCMID);
log.debug("component.getSchemaNamespace()"
+ component.getSchemaNamespace());

String compID = compTCMID.toString();
String schemaName = null;

String SGID = null;
if(component.getSchemaNamespace().equalsIgnoreCase(_RppAssetSchemaNameSpace))
{
schemaName = _RppAssetSchemaName;
if(component.getCustomMeta().getElementsByTagName("StructureGroupID").item(0)!=null)
SGID = component.getCustomMeta().getElementsByTagName("StructureGroupID").item(0).getChildNodes().item(0).getNodeValue();
log.debug("SGID " + SGID);
}
else if(component.getCustomMeta().getElementsByTagName("BasedOnSchema").getLength() > 0)
schemaName = component.getCustomMeta().getElementsByTagName("BasedOnSchema").item(0).getChildNodes().item(0).getNodeValue();


log.debug("schemaName " + schemaName);
if (componentPresentation.getTemplateKey().getTitle()
.equalsIgnoreCase(contentXML)) {

if (!checkIfRecordExists(compID)) {
log.debug("No record exists fo Id therfore inserting"
+ compID);
// insert row into table Publish itemXMLCLOB

insertComponent(compID, schemaName, contentXML);
} else {
updateComponent(compID, contentXML);
// update table into table Publish itemXMLCLOB
}

}
if (componentPresentation.getTemplateKey().getTitle()
.equalsIgnoreCase(_GSAContentXML)) {
if (!checkIfRecordExists(compID)) {
log.debug("No record exists fo Id therfore inserting"
+ compID);
// insert row into table Publish itemXMLCLOB
insertComponent(compID, schemaName, _GSAContentXML);
} else {
updateComponent(compID, _GSAContentXML);
// update table into table Publish itemXMLCLOB
}
}
if (componentPresentation.getTemplateKey().getTitle()
.equalsIgnoreCase(_GSAAssetXML)) {

if(!schemaName.equalsIgnoreCase(_RppAssetSchemaName) || (schemaName.equalsIgnoreCase(_RppAssetSchemaName) && !SGID.isEmpty()))
{
if (!checkIfRecordExists(compID)) {
log.debug("No record exists fo Id therfore inserting"
+ compID);
insertComponent(compID, schemaName, _GSAAssetXML);

} else {
log.debug("Record exists fo Id therfore updating"
+ compID);
updateComponent(compID, _GSAAssetXML);
}
}

}

} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
log.error("File NOT FOUND" + e.getMessage());

} catch (IOException e) {
// TODO Auto-generated catch block
log.error("IOException" + e.getMessage());
}
}
}

void insertComponent(String compID, String schemaName, String templateName) {
try {
log.debug("Insert started for Id" + compID);
PreparedStatement pstmt = null;
try {
Calendar calendar = Calendar.getInstance();
java.sql.Timestamp ourJavaTimestampObject = new java.sql.Timestamp(
calendar.getTime().getTime());
pstmt = conn.prepareStatement(SQL_INSERT_COMPONENT);
pstmt.setString(1, compID);
pstmt.setTimestamp(2, ourJavaTimestampObject);
if (templateName.equalsIgnoreCase(contentXML))
//pstmt.setString(3, itemXMLCLOB);
pstmt.setAsciiStream(3, itemXMLCLOB,fileLength);
else
pstmt.setString(3, null);
pstmt.setString(4, "P");
pstmt.setString(5, schemaName);
if (templateName.equalsIgnoreCase(_GSAContentXML))
//pstmt.setString(6, itemXMLCLOB);
pstmt.setAsciiStream(6, itemXMLCLOB,fileLength);
else
pstmt.setString(6, null);
if (templateName.equalsIgnoreCase(_GSAAssetXML))
//pstmt.setString(7, itemXMLCLOB);
pstmt.setAsciiStream(7, itemXMLCLOB,fileLength);
else
pstmt.setString(7, null);
pstmt.execute();
} finally {
try {
pstmt.close();
} catch (Exception ex) {
log.error("insertComponent Exception closing stmt"
+ ex.getMessage());
}
}
} catch (Exception ex) {
log.error("insertComponent Exception" + ex.getMessage());
}
}

void updateComponent(String compID, String templateName) {
try {
log.debug("Update started for Id" + compID);
PreparedStatement pstmt = null;
try {
Calendar calendar = Calendar.getInstance();
java.sql.Timestamp ourJavaTimestampObject = new java.sql.Timestamp(
calendar.getTime().getTime());
if (templateName.equalsIgnoreCase(contentXML))
pstmt = conn.prepareStatement(SQL_UPDATE_PUBLISHXML);
if (templateName.equalsIgnoreCase(_GSAContentXML))
pstmt = conn.prepareStatement(SQL_UPDATE_GSACONTENTXML);
if (templateName.equalsIgnoreCase(_GSAAssetXML))
pstmt = conn.prepareStatement(SQL_UPDATE_GSAASSETXML);
//pstmt.setString(1, itemXMLCLOB);
pstmt.setAsciiStream(1, itemXMLCLOB,fileLength);
pstmt.setTimestamp(2, ourJavaTimestampObject);
pstmt.setString(3, "P");
pstmt.setString(4, compID);
pstmt.execute();
} finally {
try {
pstmt.close();
} catch (Exception ex) {
log.error("updateComponent Exception closing stmt"
+ ex.getMessage());
}
}
} catch (Exception ex) {
log.error("updateComponent Exception" + ex.getMessage());
}
}

synchronized private static void getDataSource() {
try {
if (_datasource == null) {
Context ctx = new javax.naming.InitialContext();
_datasource = (DataSource) ctx.lookup(_DBSourceURL);
}
} catch (NamingException ne) {
log.error("Exception in getDataSource InsertRPPFeed"
+ ne.getMessage());
}
}

/**
* This method returns the Connection from the given DataSource.
*
* @param ds
* @return
*
*/
private static Connection getConnectionFromDataSource(DataSource ds) {
Connection conn = null;
boolean retry = false;
int numOfRetries = 0;
do {
try {
conn = ds.getConnection();
} catch (Exception exp) {
if (exp.getClass().getName()
.indexOf("StaleConnectionException") != -1) {
if (numOfRetries > 2) {
retry = true;
numOfRetries++;
} else {
log.error("Exception in getConnectionFromDataSource"
+ exp.getMessage());
}
} else {
log.error("Exception in getConnectionFromDataSource"
+ exp.getMessage());
}
}
} while (retry);
return conn;
}

protected boolean checkIfRecordExists(String componentID) {
PreparedStatement pstmt = null;
ResultSet rset = null;
boolean recordExists = false;
try {
pstmt = conn.prepareStatement(SQL_SELECTBY_CONTENTID);
pstmt.setString(1, componentID);
try {
rset = pstmt.executeQuery();
try {
while (rset.next())
recordExists = true;
} finally {
try {
rset.close();
} catch (Exception ex) {
log.error("checkIfRecordExists Exception closing resultset"
+ ex.getMessage());
}
}
} finally {
try {
pstmt.close();
} catch (Exception ex) {
log.error("checkIfRecordExists Exception closing stmt"
+ ex.getMessage());
}
}
} catch (Exception ex) {
log.error("checkIfRecordExists Exception" + ex.getMessage());
}

return recordExists;

}

}

How to get minor version in Tridion Workflow 2011 SP1 using Coreservice

If you had worked in Tridion Workflow,
You could have encountered a situation where you need to get the current version of workflow. eg 2.1 and 2.2 using Sample code.
Below is the sample code to retrieve current version of item

Background for Tridion Item Versions:
All Tridion major versions are checked in at as 1.0, 2.0, 3.0 +++++
However, when an item is entered in workflow it can be edited and first it enters with version 1.1 and if some one(eg: approver) modifies is when item is in workflow the version is 1.2 and If approver sends back to author and when author edits this content version would be 1.3 and so on... basically an item entered in workflow when edited will have version X.1, X.2, X.3 ... ++++

For eg: I had a requirement that a content entered inside a workflow when approvers/publishers edit the content. The content should be sent back to author.

Solution 1: You can either write an event system which doesn't let you save and close

So event system checks if component in workflow, then takes current activitydefinition and checks if its Description contains #READONLY# some thing like below

  private bool IsActivityReadOnly(ActivityInstance activity)
        {
            return activity != null && activity.ActivityDefinition.Description.Contains(READ_ONLY_PLACEHOLDER);
        }
And don't let Save and Close

Solution 2:  Convoluted One :)


After Content Creation there is Publish to Preview and Notify US Approver activity which always takes note of current revision of author creation

C# Code Snippet: In Publish to Preview and Notify US Approver
Takes note of versions revision which will give you X of 1.X
       FullVersionInfo creatorVersionInfo = newComp.VersionInfo as FullVersionInfo;
versionNumber = creatorVersionInfo.Revision.Value;


 I would always compare this number after approver approves/ publisher approves. If he/she changed content. In below diagram: After US approver appoves --> content goes to Businnes-Approvers. So before going to Business Approvers. Send to Business-Approvers Review will check version number with above versionNumber. If something in content is changed after  Author sent for for approval then it will send back to author


C# Code Snippet: Send to Business-Approvers Review

ComponentData newComp = CoreServiceClient.Read(GetTcmUri(component.Id, component.LocationInfo.ContextRepository.IdRef,
0), options) as ComponentData;
FullVersionInfo VersionInfo = newComp.VersionInfo as FullVersionInfo;
int version = VersionInfo.Revision.Value;
if (version != versionNumber)
// then use DecisionActivityFinishData to Send Back to Author 
                               else
                                        // then use DecisionActivityFinishData to Assign to Business Approvers Review





Assign Back to Author ( Who last worked on Content Edition on that component) - Workflow

Often, I get below requirement in workflow

On Reject --> Workflow should be assigned back to author.
However, what happens if Business admin does "Assign Activity" if  content is assigned back to a different author at any point of workflow (Since initial author was on vacation or may be even he/she left company) In that case content should be picked up by other author.

In this case a new author is assigned to the content. And On Reject content should be going back to original author.

On Assign Back to Author content should go back to Activity Name: Content Creation
and ASSIGNED TO LAST PERFORMER OF THIS ACTIVITY



SCRIPT CODE of  Assign Back to Author

                                                      C# Code


public void BackToAuthor(string workitemid)
{
try
{
Stopwatch _watch = new Stopwatch();
_watch.Start();
Logger.Debug("Entered RPPTridionWorkflow BackToAuthor");
WorkItemData workitem = (WorkItemData)CoreServiceClient.Read(workitemid, options);
ProcessInstanceData processInstance = (ProcessInstanceData)CoreServiceClient.Read(
  workitem.Process.IdRef, options);
ActivityData[] ieActivities = processInstance.Activities;

if (ieActivities != null)
{
int currentActivity = ieActivities.Length - 1;
var targetactivity = (ActivityInstanceData)CoreServiceClient.Read(
 processInstance.Activities[currentActivity].Id, options);
ActivityData creatorActivityData = getActivityDataBasedOnName(workitemid, "Content Creation");
if (creatorActivityData != null)
{
var finishData = new ActivityFinishData();

finishData.Message = "Item Rejected";
finishData.NextAssignee = new LinkToTrusteeData
{
//This is where I retrieve the ACTIVITY INFORMATION of activity " Content Creation" using method getActivityDataBasedOnName(workitemid, "Content Creation"); 
IdRef = creatorActivityData.Owner.IdRef,
Title = creatorActivityData.Owner.Title
};
CoreServiceClient.FinishActivity(targetactivity.Id, finishData, options);
}
else
Logger.Error("Content Creation Activity not Found in BacktoAuthor");

Logger.Debug("Closing Core Service session:  BackToAuthor [" + _watch.ElapsedMilliseconds + " ms]");
}
}
catch (Exception ex)
{
Logger.Error("Exception in BackToAuthor" + ex.Message);

}
finally
{
CoreServiceClient.Close();
}
}
<!------------------ This method reads list of activities performed during the workflow. and get the activity information based on name. It always gets information of the activity the last time this is performed  -------------->

public ActivityData getActivityDataBasedOnName(string workitemid, string activityName)
{
WorkItemData workitem = (WorkItemData)CoreServiceClient.Read(workitemid, options);
ProcessInstanceData processInstance = (ProcessInstanceData)CoreServiceClient.Read(
  workitem.Process.IdRef, options);
ActivityData[] ieActivities = processInstance.Activities;
ActivityData lastActivityData = null;
foreach (ActivityData actData in ieActivities)
{
if (actData.Title.Contains(activityName))
lastActivityData = actData;
}
return lastActivityData;
}