Latest Entries

@PostConstruct swallows exceptions, catch & log

I was having difficulty analizing a non-working web service that I had deployed on WebLogic 10.3.6. No error showed up in the logs and since the webservice was implemented using an in-only message exchange pattern (fire & forget) I didn't have a response message I could verify. After a more careful look in the logs I noticed the logging completely stopped after the web service invoked an EJB. Unable to debug any further I changed the in-only MEP to an in-out MEP (request & response) and noticed the following error in the response message:

"ExceptionEJB Exception: com.oracle.pitchfork.interfaces.LifecycleCallbackException: Failure to invoke private void ... on bean class  ... with args: []"

At some point I figured out it had to do with the @PostConstruct of one of my EJB's not working properly. As it happens an exception thrown while executing @PostConstruct is swallowed by WebLogic. A quick  search showed I am not the only one having this problem and similar behavior is observed in JBoss and GlassFish as well.

As a solution I've started wrapping my initialization code within a @PostConstruct in a try-catch block, logging any exception that occurs and rethrow the exception. Because I don't know what type of throwable I'm catching and the exception is lost anyway I rethrow the original exception wrapped in a RuntimeException. Checked exceptions are not allowed in methods annotated with @PostConstruct.

Example:

@Stateless
public class MyEJB implements MyEJBLocal {

    private static final Logger LOGGER = LoggerFactory.getLogger(MyEJB.class);

    @PostConstruct
    private void init() {
        try {
            // -- do stuff
        } catch (Throwable e) {
            LOGGER.error("Error occurred during initialization of MyEJB", e);
            throw new RuntimeException(e);
        }
    }
}

Oracle DB session analysis

During development I experienced some troubles with blocking sessions in Oracle DB. Not having much experience with the gv$sess and g$sess views in Oracle it took me quite some time to figure out which session was causing the problem.

I made a query showing the more interesting session details and metrics:
- Session details like machine, user, sid, ...
- Blocking status
- Number of hours running
- Average hourly IO count (our DBA is always pointing to limiting our IO, but I've never actually measured it)
- Average hourly processing time in milliseconds
- Program memory usage (PGA only)

(tested on Oracle Database 11g Enterprise Edition Release 11.2.0.3.0) 

Installing deployable libraries (e.g. JSF/JSLT) on weblogic 10.3

Weblogic comes with a set of deployable libraries available in  {WL_HOME}/wlserver_10.3/common/deployable-libraries that can easily be deployed as shared libraries. The deployable libraries available on my WLS 10.3.5 installation are:

  • active-cache-1.0.jar          
  • jsf-1.2.war            
  • jstl-1.2.war
  • jackson-core-asl-1.1.1.war
  • jsf-2.0.war rome-1.0.war
  • jackson-jaxrs-1.1.1.war      
  • jsf-myfaces-1.1.7.war  
  • toplink-grid-1.0.jar
  • jackson-mapper-asl-1.1.1.war  
  • jsf-ri-1.1.1.war       
  • weblogic-sca-1.1.war
  • jersey-bundle-1.1.5.1.war    
  • jsr311-api-1.1.1.war
  • jettison-1.1.war              
  • jstl-1.1.2.war

Because I prefer my EAR, WAR and EJB archives to be self-contained I've experimented little with these shared libraries thus far. However I ran into a problem where my @EJB wouldn't load in a managed bean. The cause was the following startup error:

<Jan 3, 2013 11:27:42 PM CET> <Error> <javax.enterprise.resource.webcontainer.jsf.application> <BEA-000000> <JSF1030: The specified InjectionProvider implementation 'com.bea.faces.WeblogicInjectionProvider' cannot be loaded.> 

The solution was to install the shared JSF 2.0 weblogic deployable and update my weblogic.xml like this:


This made my application use the shared library instead of using its own JSF bundled library. Not sure why this made any difference at all, possibly a versioning problem, but haven't found out since and it has been running without issues.

To install and configure the JSF 2.0 Weblogic deployable archive have a look here:
http://docs.oracle.com/cd/E21764_01/web.1111/e13712/configurejsfandjtsl.htm

The idea is to use weblogic.Deployer located in {WL_HOME}wlserver_10.3/server/lib/weblogic.jar. Because I had some problems getting the weblogic.jar in my classpath I included it directly in the java command like this:

java -cp /u01/app/oracle/Middleware/wlserver_10.3/server/lib/weblogic.jar weblogic.Deployer -adminurl t3://localhost:7001 -user weblogic -password Welcome1 -deploy -library /u01/app/oracle/Middleware/wlserver_10.3/common/deployable-libraries/jsf-2.0.war

Oracle XE deadly slow on Fedora 17, a hostname issue

For some time I had been struggling with an extremely slow locally installed Oracle XE database. Its performance had been dismal. Just to give you an example, creating tablespaces using RCU took ages:


Normally installations like this take less than 25% of the time it took this time.

Some context: I have a nice laptop (8gb ram, Intel i5-2520M CPU @ 2.50GHz dual core) and had been running Windows 7 fluently with an Oracle XE, SOA Suite and WebCenter setup prior to my new OS installation. Recently I decided (after an horrendous experience with Windows 8) to switch to linux, Oracle Linux 6.1 in particular. Unfortunately I had too many problems installing the latest browser version for Firefox and Chrome in Oracle Linux and I decided to switch to Fedora 17 instead. Fedora has come with some difficulties I admit; GNOME 3 for example is so slow out-of-the-box it was simply unacceptable. KDE however has been running fine thus far and I don't really care which desktop environment I'm using as long as I can find stuff (i.e. non-windows 8).

The problems: after installing Oracle XE I tried loading APEX on http://localhost:9090. This hardly worked at all; perhaps after two or three minutes a page finally loaded. The cause of this was accidentily reveiled during a SOA Suite 11g domain configuration:


Ignoring this error at first and finishing the SOA Suite installation I found parts of the domain installation were missing; most importantly the shell scripts I normally use to administer the WebLogic domain.

The solution: the python class referenced in the installation error is part of jython, the runtime which WebLogic Scripting Tool uses to run python code for administering WebLogic servers. Likely some network setting on my installation was faulty, which one however I wasn't sure.

Having no clue whatsoever I simply tried to connect to the admin server I -attempted- to create during the SOA Suite install. This is what happened:



Apparently my machine was unable to find my local address. Weird... after scrolling down the reason became obvious:


UnknownHostException "krijger-lap"? What is "krijger-lap"? I couldn't remember entering this anywhere, although my lastname is indeed "krijger". Checking my hostname reveiled the flaws of my configuration:


This krijger-lap value was not set in the /etc/hosts-file, how could my machine find it? After adding my hostname to my hosts-file Oracle XE started to perform and SOA Suite domain configuration went without a hitch.

It did surprise me how much Oracle XE's behavior was affected by this misconfiguration without throwing any noticeable error whatsoever.

Changing locale of ADF error messages

Working with ADF (11.1.1.6) on a Dutch environment I get error message like these:

Caused by: oracle.mds.core.MetadataNotFoundException: MDS-00013: Geen metagegevens gevonden voor metagegevensobject "/nl/svb/ddb/dossierviewer/view/DataControls.dcx"

A query in Google with the error type and/or code (in this case MetadataNotFoundException and MDS-00013), despite this apparant unique signature, often yields several different errors in blogs, forums and documentation. Rather than looking for the right one or translating the native (in my case the Dutch) message, you can force the (internal) server to use a different locale.

Choose “Application” from the menubar and click “Application Properties -> Run -> Application Server Properties”. Next go to the "Launch Settings"-tab (see also screenshot below). In this tab add the following java options:

 -Duser.language=en -Duser.country=US

Change the language ('en') and territory suffix ('US') to your liking.


If you want to change the locale only for a single project rather than all your applications; select the project, right-click and go to "Project Properties -> Run/Debug/Profile" and select a run configuration. Click "Edit" and add the user.language and user.country java options. Restart the server afterwards.


If you are working on a separate server you can add the java options directly to the startup script of your webserver as well (for WebLogic look for setDomainEnv.cmd/.sh).

Code quality; an introspective exercise

As a software developer, do the following exercise:

Think of three code quality attributes you value most when working on a codebase developed by someone else. Afterwards, explain your answer.

... you have your answer? The first three quality attributes that popped in my head were:

Readability, Cohesiveness, Predictable structure

(select to read)

This selection was an instinctive reaction. Let me attempt to rationalize.

Readability

A fair guess would be my first  choice "readability" is a popular answer. A personal selection of readable languages (including DSL's) would be C, PHP, and SQL. Difficult languages I consider C++, Perl and VBA. Because this selection is very personal I invite you to make your own list. My selection was biased due to my programming background (web and limited embedded software) and education (business & IT and limited mathematics).

My rationale;

Perl vs PHP: when I was writing my first dynamic website, just a couple of forms with a database, I discovered PHP provided quicker results with less learning. It was as simple as that. If my goal had been to write an IRC bot it would be fair to say Perl would have been my choice. PHP simply became more familair.

SQL vs VBA: these languages (or DSL's) favour the use of words above brackets and semicolons normally used in programming  languages. Examples:

SQL:
SELECT a.title, a.description, b.name AS category
FROM documents a
LEFT JOIN document_categories b
WHERE a.category_id = b.id
ORDER BY a.title ASC

VBA:
Public Function RepeatStr(aString As String, aNumber As Integer) As String
Dim bString As String
 
bString = aString
If aNumber > 1 Then
    For i = 2 To aNumber
        bString = bString & aString
    Next i
End If
RepeatStr = bString
End Function

SQL statements always follow the same ordered structure: SELECT ... FROM ... WHERE .... Due to its rigid structure each SQL statement is relatively easy to understand (up to some point that is, endless lists of subqueries will get you in the end). I have always liked this approach, it costs little cognitive strain to grasp the meaning of the statement.

VBA operates in a much more general context than SQL does; the application domain vs the database domain. The structure of a VBA program is much less predictable than SQL. VBA uses keywords like then, end if, and end function where most languages represent the same constructs  by parentheses and brackets. I argue in VBA this choice has cluttered the source code distracting from the code's purpose. Let's rewrite the VBA example in a slightly different syntax:

public function repeatStr(aString: String, aNumber: Integer): String {
    var bString: String
 
    bString = aString
    if (aNumber > 1) {
        for (i = 2; i <= aNumber; i++) {
            bString = bString & aString
        }
    }
    repeatStr = bString
}

Make your own choice which syntax you'd prefer.

VBA's choice to discard if condition brackets is taken for the wrong reasons; now the grouping of the code is done with more cognitive strain, particularly for those new to the language. If the VBA keywords would have been written in uppercase there would be less of a problem; you'd recognize the grouping pattern while hardly being aware of it (similar to the SQL example). 

C vs C++: despite having programmed only a little in both languages I've always preferred C over C++. C is in itself difficult enough, C++ adds a whole new dimension of complexity. C++ allows for many more ways of building and structuring the same program. C in that respect is much more limited but also makes it more predictable and easier to understand. While I'm a fan of OO I believe a language should shed as much as it can from other paradigms to make the language predictable. I dislike Javascript for the same reason; there are simply too many ways to tackle the same problem increasing cognitive strain when maintaining code.

The disadvantage of limiting development options is you might not get the most performant code, or even the most readable. But whenever I am looking at someone else's code it is great knowing whatever he has developed there can't be a million ways in which he could have done it; I will recognize quickly where and how it has been done.

My selection of subcharacteristics of the quality readability would thus be:

  • Context: using one type of syntax might make much more sense in one environment than in the other.
  • Clutter: less syntax clutter to achieve the same goal makes it less straining to identify the more important parts of the code.
  • Predictability: having fewer options to tackle the same problem makes the use and behavior of the program easier to understand.

Cohesiveness

Cohesion refers to the degree to which the elements of a module belong together (Yourdon, E.; Constantine, L L. Structured Design: Fundamentals of a Discipline of Computer Program and Systems Design. Yourdon Press, 1979). It is a measure of how "strong-related the function of a given software module is". Most programming languages provide some mechanism to introduce modularity: functions, classes and on a higher level namespaces, projects, etc. In practical terms cohesiveness expresses how well the parts of the code fulfills one common purpose.

Utility classes are an example of low cohesiveness; they are just a named collection of functions which the developer beliefs to be of general use. They serve a very broad audience and don't tell the coder anything apart from what the code does. Certainly, utility classes have their uses and I have been using them in every project thus far. But preferably the code expresses not what it is doing but what it intents. The difference is subtle but significant, consider the following code:

class StringUtils {
    public static String ucfirst(String text) {
        return text.substring(0, 1).toUpperCase() + 
                text.substring(1).toLowerCase();
    }
}

// Use case
String paragraph = "my paragraph";
System.out.println(StringUtils.ucfirst(paragraph));

Compared to:

class ParagraphFormatter {
    private String text;
    public Paragraph(String text) { this.text = text; }
    public String format() {
        return text.substring(0, 1).toUpperCase() + 
                text.substring(1).toLowerCase();
    }
    public String toString() { return this.text }
}

// Use case
ParagraphFormatter p = new ParagraphFormatter("my text");
p.format();
System.out.println(p.toString());

While both pieces of code attempt the same thing their approach is drastically different. The first example merely states what it is doing; it transforms the first character of the paragraph to uppercase. The second code example encapsulates the paragraph and modifies it. You'll notice the second example is much more elaborate and more cumbersome to use. Also in practice I would likely use the StringUtils.ucfirst(...) method within the ParagraphFormatter.format() method rendering the whole ParagraphFormatter class useless. Why then opt for the second solution?

Consider the use case changes and all first sentences in each paragraph ought to be formatted in bold. In both examples you'd need a new method to format the first word in bold. Afterwards in the first example you would need to identify all pieces of code in the application where a paragraph is being formatted. If you're working on a project with several developers my guess is they've been using StringUtils.ucfirst(...) everywhere and in only a few instances used it to format a paragraph. If you're lucky the cases where they did are easily identified (naming a variable paragraph instead of text or string), but this is unlikely. The job of formatting a paragraph has been scattered throughout the application, whereas the second example -albeit a bit more work- centralized this responsibility.

A slight warning though; don't overdo it, otherwise the YAGNI god will come down and smack you.

Predictable structure

Having already discussed predictabiliy briefly in the section on readability, allow me to take it one step further.

When I started writing PHP I wrote my code in the ugliest way possible; an article.php file with a mix of HTML, javascript and PHP all in one. I can see you shuddering and going green; I did too when I maintained that piece of code years later. The stupid thing is, what I needed to change was done very quickly; I navigated to the correct page on the website, saw in the URL it was article.php, opened the file, hit ctrl + f and found what I was looking for. The simple structure of a page acting as a container of everything displayed in it allowed for quick maintenance. No web framework I've come across has beaten this level of simplicity.

This approach does have drawbacks ofcourse, particularly code duplication and poor separation of responsibilities. These days I opt for using a framework with a strong vision on how to structure your program which maintains a bit of the predictability many frameworks lack. This was actually the key reason why I chose to use Symfony 2 rather than Zend Framework.

Assuming you're not familair with either of these PHP frameworks, Zend Framework is similar to a toolbox with useful utilities. You have a Mail class for sending mail, View and Controller classes to adopt MVC, and many more. It is unlikely anything you need won't be available to you.
Symfony 2 is a service oriented framework inspired by the domain driven design philosophy to structure a software application. Zend Framework really gives you choice, Symfony 2 might argue you have choice but you're unlikely to deviate much from the documented path (or stubbornly attempt to do so in the beginning and slowly grow to like the whole framework in the end).

The advantage is when a developer has developed something in Symfony 2 and I am familair with Symfony's philosophy it is a fair guess I'll be able to maintain it easily. When a developer has built something in Zend Framework he could have structured it in any way he saw fit, perhaps wrote his own meta framework on top of it all *shudder*. Even in the unlikely case he has followed a philosophy, it is even more unlikely he has documented it thoroughly. You've lost predictability and added a great deal of suprise & learning to the maintainer.

Adding a file to Framework Folders in WebCenter Content using RIDC

Lately I've been experimenting with RIDC (Remote Intradoc Client), a light-weight communication protocol for Oracle WebCenter Content.

One requirement I was trying to solve was adding a file to a Framwork Folder. "Framework Folders" is a new  folder hierachy component introduced in PatchSet 5 (11.1.1.6) and replaces the older "Folders_g" component. Framework Folders fixed the scalability issues which occurred using Folders_g.

Before I discuss my approach, let me make it clear I am unfamiliar with the old Folders_g approach and I have limited experience using WebCenter Content. Nevertheless the material I found on this subject on the web is sufficiently limited to warrent this blogpost.

I'll split this blogpost in two parts:

  1. Uploading a File in WebCenter Content PS5
  2. Adding a file to a Framework Folder in WebCenter Content using RIDC

1. Uploading a File in WebCenter Content PS5

Context

You have got a WebCenter Content (WCC) server running (PS5 minimum) and have decided on using RIDC to integrate your (independent) software component with WCC.

Problem

You want to upload a file in WebCenter Content.

Solution

Create a RIDC connection and use the CHECKIN_UNIVERSAL service to upload the file to the content server.

Detailed Solution

In your IDE (I am using Eclipse) create a new standard Java project. First we need to establish a connection with our content server. There are several methods available to do this but currently the most popular one is using Remote Intradoc Client (RIDC). RIDC supports several connection types (idc, idcs, http, https, and jax-ws) and depending on the connection type you need to import one or more dependencies. Because I want to keep things simple and I am connecting to a server running on localhost I will be using the idc protocol. For this protocol you only need the oracle.ucm.ridc-11.1.1.jar which you can find in the UCM distribution folder of your WCC install. In my case this is: D:\Oracle\WebCenter\Oracle_ECM1\ucm\Distribution\RIDC

Time to create the connection:


What suprised me a little was the username and password are stored separately from the client and are added to each individual service request later on (you'll see this in the next code example). This allows some flexibility if you want to swap user credentials over the same connection... a flexibility I won't be using here. If you don't need this flexibility in your application you might want to create a connection wrapper for the IdcClient and IdcContext (and possibily the IdcClientManager as well) to create more readable code.

In the example I'm using the "sysadmin" account which is allowed to do pretty much anything in WCC. In production code you're advised to connect using the most limited account sufficient to fulfill the user requirement (principle of least privilege).

Checking in a file in WCC PS5 is no different than prior versions of WCC as far as I know. I'll be using the CHECKIN_UNIVERSAL service which is the most versatile checkin service WCC provides. Depending on whether the file already exists and whether it is in a workflow or not it executes a more specific checkin service.

Whenever using WCC services, read their specification carefully. If you get any parameter names wrong you're likely to receive vague error messages. and work throuh several hours of unnecessary debugging.

This next piece of code demonstrates CHECKIN_UNIVERSAL:
The new method uploadFile takes three parameters: a docType, securityGroup and filePath. The filePath is converted to a oracle.stellent.ridc.model.TransferFile which RIDC uses to transport the file over the IDC protocol.

By default there are two security groups you can play with: Public and Secure. You can create your own security groups to separate content better (for example by department). Finally the docType describes the content type. You can retrieve the available content types using GET_DOCTYPES, the most likely candidate however is "Document".

If you would have read the previous code example carefully you would have noticed the DataBinder class. This DataBinder is the data container used throughout WCC and contains four components:

  • LocalData: all data associated with the request and response itself. These include request parameters you set yourself and any response parameters set by the content server.
  • Options: I have no idea yet what these do, I haven't seen them used in my RIDC experiments.
  • ResultSets: data stored in key/value pairs which have been requested by the user through a service call. These key/value pairs are stored in a named set. Depending on the service the response contains zero, one or multiple named sets.
  • Files: used for TransferFiles.

I have been unable to find exact definitions of these DataBinder-components in the Oracle documentation. I like to think of LocalData as the header data and ResultSets & Files as the message body (or bodies), much like the HTTP protocol.

To get a better understanding of the DataBinder during development and debugging I've printed the DataBinder three times in the previous code (if you hadn't noticed; despite the admirable laziness of your geek-reading skillz, I do ask you to pay better attention in the future).

Just after creating a new DataBinder it contains the following:

DataBinder {
    Files {
        none
    }
    LocalData {
        UserDateFormat = iso8601
        UserTimeZone = UTC
    }
    Options {
        none
    }
    ResultSets {
        none
    }
}

By default the user's date format (iso8601) and timezone are set. Fair enough.

After setting the request parameters the DataBinder changes to something like this:

DataBinder {
    Files {
        File name = primaryFile
        Content length = {testfile.png [application/octet-stream,71754]}    }
    LocalData {
        IdcService = CHECKIN_UNIVERSAL
        UserDateFormat = iso8601
        UserTimeZone = UTC
        dDocAuthor = sysadmin
        dDocTitle = Test Folder upload
        dDocType = Document
        dSecurityGroup = Public
    }
    Options {
        none
    }
    ResultSets {
        none
    }
}

Not much suprising here; the primaryFile is added to the Files section of the DataBinder and the other request parameters are set as LocalData. More interesting is the DataBinder response after executing the service request:

DataBinder {
    Files {
        none
    }
    LocalData {
        ClientEncoding = UTF-8
        DocExists = 
        IdcService = CHECKIN_UNIVERSAL
        LockedContents1 = dDocName:CS000876
        NoHttpHeaders = 0
        RenditionId = webViewableFile
        StatusCode = 0
        StatusMessage = Inhouditem 'CS000876' is ingecheckt.
        StorageRule = DispByContentId
        UserDateFormat = iso8601
        UserTimeZone = UTC
        VaultfilePath = d:/tools/Oracle/WEBCEN~1/USER_P~1/domains/WEBCEN~1/ucm/cs/vault/document/mgnz/mdaw/875.png
        WebfilePath = d:/tools/Oracle/WEBCEN~1/USER_P~1/domains/WEBCEN~1/ucm/cs/weblayout/groups/public/documents/document/mgnz/mdaw/~edisp/cs000876~1.png
        auditAction = wwCreate
        auditActionClass = wwCreate
        auditObjectType = wwRmaRecord
        auditResultSet = DOC_INFO
        blDateFormat = yyyy-MM-dd HH:mm:ssZ!tUTC!mAM,PM
        blFieldTypes = xCategoryFolder text,dSubscriptionNotifyDate date,xHasFixedClone int,xWebFlag text,xIPMSYS_STATUS text,xReportDataSourceType text,xIsFixedClone int,xStorageRule text,xIPMSYS_REDACTION int,xForceFolderSecurity text,xFreezeID memo,xReadOnly text,xIsEditable text,xWCTags text,xInhibitUpdate text,xWCWorkflowAssignment text,xHideIncludeChildFolders int,dCreateDate date,xCategoryID bigtext,xHidden text,dSubscriptionCreateDate date,xReportScheduledInfo text,xCpdIsLocked int,StatusMessage message,xCollectionID int,dSubscriptionUsedDate date,xLifeCycleID bigtext,xIPMSYS_APP_ID text,xWCWorkflowApproverUserList memo,xComments memo,xIsRecord text,xReportContentType text,dActionDate date,xWCPageId bigtext,xCpdIsTemplateEnabled int,xRecordFilingDate date,xReportType text,xIPMSYS_BATCH_ID1 int,xIPMSYS_PARENT_ID int,dOutDate date,xExternalDataSet bigtext,xIsFrozen text,xIsDeletable text,xReportFormat text,xPartitionId text,dInDate date,xIPMSYS_BATCH_SEQ text,dMessage message,xReportTemplate text,xReportDataSourceParams text,xIPMSYS_SCKEY text,xFreezeReason memo,dReleaseDate date,xFolderID bigtext,xReportDataSource text,xIsRevisionable text,xIdcProfile text
        changedMonikers = 
        changedSubjects = documents,1349171311089
        dAction = Checkin
        dActionDate = 2012-10-08 15:36:00Z
        dActionMillis = 52514543
        dClbraName = 
        dConversion = PassThru
        dCreateDate = 2012-10-08 15:36:14Z
        dDocAccount = 
        dDocAuthor = sysadmin
        dDocCreatedDate = {ts '2012-10-08 17:36:14.522'}
        dDocCreator = sysadmin
        dDocID = 932
        dDocLastModifiedDate = {ts '2012-10-08 17:36:14.522'}
        dDocLastModifier = sysadmin
        dDocName = CS000876
        dDocOwner = sysadmin
        dDocTitle = Test Folder upload
        dDocType = Document
        dDocType:rule = IpmSystemFields_Restricted
        dExtension = png
        dFileSize = 71754
        dFormat = image/png
        dID = 875
        dInDate = 2012-10-08 15:36:14Z
        dIsPrimary = 1
        dIsWebFormat = 0
        dLastModifiedDate = {ts '2012-10-08 17:36:14.545'}
        dLocation = 
        dOriginalName = testfile.png
        dOutDate = 
        dProcessingState = Y
        dPublishState = 
        dPublishType = 
        dRawDocID = 931
        dReleaseState = N
        dRevClassID = 876
        dRevLabel = 1
        dRevRank = 0
        dRevisionID = 1
        dRmaProcessState = O
        dSecurityGroup = Public
        dSecurityGroup:rule = IpmSystemFields_Restricted
        dStatus = DONE
        dUser = sysadmin
        dVitalState = O
        dWebExtension = png
        dWebOriginalName = CS000876~1.png
        dWorkflowState = 
        dpAction = CheckinNew
        dpEvent = OnImport
        dpTriggerField = xIdcProfile
        idcToken = 
        isCheckin = 1
        isDocProfileDone = 1
        isDocProfileUsed = true
        isEditMode = 1
        isInfoOnly = 
        isNew = 1
        isStatusChanged = 1
        localizedForResponse = 1
        mustAllowDeletes = 
        mustAllowEdits = 
        mustAllowRevisions = 
        noDocLock = 1
        prevReleaseState = 
        primaryFile = testfile.png
        primaryFile:path = d:/tools/Oracle/WEBCEN~1/USER_P~1/domains/WEBCEN~1/ucm/cs/vault/~temp/2015661264.png
        refreshMonikers = 
        refreshSubMonikers = 
        refreshSubjects = 
        reserveLocation = false
        scriptableActionErr = 
        scriptableActionFlags = 12
        scriptableActionFunction = determineCheckin
        scriptableActionParams = 
        scriptableActionType = 3
        xCategoryFolder = 
        xCategoryFolder:isSetDefault = 1
        xCategoryID = 
        xCategoryID:isSetDefault = 1
        xClbraAliasList = 
        xClbraUserList = 
        xCollectionID = 0
        xCollectionID:isSetDefault = 1
        xComments = 
        xComments:isSetDefault = 1
        xCpdIsLocked = 0
        xCpdIsLocked:isSetDefault = 1
        xCpdIsTemplateEnabled = 0
        xCpdIsTemplateEnabled:isSetDefault = 1
        xExternalDataSet = 
        xExternalDataSet:isSetDefault = 1
        xFolderID = 
        xFolderID:isSetDefault = 1
        xForceFolderSecurity = FALSE
        xForceFolderSecurity:isSetDefault = 1
        xFreezeID = 0
        xFreezeID:isSetDefault = 1
        xFreezeReason = 
        xFreezeReason:isSetDefault = 1
        xHasFixedClone = 0
        xHasFixedClone:isSetDefault = 1
        xHidden = FALSE
        xHidden:isSetDefault = 1
        xHideIncludeChildFolders = 0
        xHideIncludeChildFolders:isSetDefault = 1
        xIPMSYS_APP_ID = 
        xIPMSYS_APP_ID:isSetDefault = 1
        xIPMSYS_APP_ID:rule = IpmSystemFields_Hide
        xIPMSYS_BATCH_ID1 = 0
        xIPMSYS_BATCH_ID1:isSetDefault = 1
        xIPMSYS_BATCH_ID1:rule = IpmSystemFields_Hide
        xIPMSYS_BATCH_SEQ = 
        xIPMSYS_BATCH_SEQ:isSetDefault = 1
        xIPMSYS_BATCH_SEQ:rule = IpmSystemFields_Hide
        xIPMSYS_PARENT_ID = 0
        xIPMSYS_PARENT_ID:isSetDefault = 1
        xIPMSYS_PARENT_ID:rule = IpmSystemFields_Hide
        xIPMSYS_REDACTION = 0
        xIPMSYS_REDACTION:isSetDefault = 1
        xIPMSYS_REDACTION:rule = IpmSystemFields_Hide
        xIPMSYS_SCKEY = 
        xIPMSYS_SCKEY:isSetDefault = 1
        xIPMSYS_SCKEY:rule = IpmSystemFields_Hide
        xIPMSYS_STATUS = 
        xIPMSYS_STATUS:isSetDefault = 1
        xIPMSYS_STATUS:rule = IpmSystemFields_Hide
        xIdcProfile = 
        xIdcProfile:isSetDefault = 1
        xInhibitUpdate = FALSE
        xInhibitUpdate:isSetDefault = 1
        xIsDeletable = 1
        xIsDeletable:isSetDefault = 1
        xIsEditable = 1
        xIsEditable:isSetDefault = 1
        xIsFixedClone = 0
        xIsFixedClone:isSetDefault = 1
        xIsFrozen = 0
        xIsFrozen:isSetDefault = 1
        xIsRecord = 0
        xIsRecord:isSetDefault = 1
        xIsRevisionable = 1
        xIsRevisionable:isSetDefault = 1
        xLifeCycleID = 
        xLifeCycleID:isSetDefault = 1
        xPartitionId = 
        xPartitionId:isSetDefault = 1
        xReadOnly = FALSE
        xReadOnly:isSetDefault = 1
        xRecordFilingDate = 2012-10-08 15:36:14Z
        xRecordFilingDate:isSetDefault = 1
        xReportContentType = 
        xReportContentType:isSetDefault = 1
        xReportDataSource = 
        xReportDataSource:isSetDefault = 1
        xReportDataSourceParams = 
        xReportDataSourceParams:isSetDefault = 1
        xReportDataSourceType = 
        xReportDataSourceType:isSetDefault = 1
        xReportFormat = 
        xReportFormat:isSetDefault = 1
        xReportScheduledInfo = 
        xReportScheduledInfo:isSetDefault = 1
        xReportTemplate = 
        xReportTemplate:isSetDefault = 1
        xReportType = 
        xReportType:isSetDefault = 1
        xStorageRule = DispByContentId
        xStorageRule:isSetDefault = 1
        xWCPageId = 
        xWCPageId:isSetDefault = 1
        xWCTags = 
        xWCTags:isSetDefault = 1
        xWCWorkflowApproverUserList = 
        xWCWorkflowApproverUserList:isSetDefault = 1
        xWCWorkflowAssignment = 
        xWCWorkflowAssignment:isSetDefault = 1
        xWebFlag = 
        xWebFlag:isSetDefault = 1
    }
    Options {
        none
    }
    ResultSets {
        none
    }
}

This long dataset provides a wealth of information but also makes it challenging to find what you need. One important parameter in this case is the dDocName (value = CS000864), also known as the content ID. This is the generated content ID of our uploaded file. Many other WCC services require this content ID as a service request parameter and if your third-party application has its own hydrated domain model you'll likely want to store this value somewhere.

Other parameters you are likely going to need are StatusCode and StatusMessage which contain the service request execution result. So far I've found a StatusCode of 0 and null (not set) indicate success, and anything else requires explicit processing like error handling or warning messages.

If you're interested in the code how to print the DataBinder (using ridcoo.ridc.utils.RIDCUtils) go to the bottom of this blog.

This section showed a minimalist approach to adding a file in WebCenter Content. Let us now look at how to add our newly created file to a Framework Folder.

2. Adding a file to a Framework Folder in WebCenter Content using RIDC

Context

You have a WebCenter Content (WCC) server running (PS5 minimum), enabled the Framework Folders component and have decided on using RIDC to add a file to a folder in WebCenter Content.

Problem

There is no service request parameter in the CHECKIN service to specify in which folder to put the uploaded document.

Solution

The short answer is you cannot put a file in a content server folder; you can only create a link to the file in a Framework Folder. Think of it as creating a shortcut or symbolic link instead of copying the file to the actual folder.

While I'm unfamiliar with the old Folders_g (or Contribution Folders) component, I believe this is a radical change from the old model which makes Framework Folders more scalable than Folders_g.

To create a link to the file in a Framework Folder you can use the FLD_CREATE_FILE service after you've uploaded the document in WCC.

Be aware, the documentation of FLD_CREATE_FILE is yet, well... *cough*... slightly, uhm... limited. So you might want to keep reading.

Detailed solution

Due to ambiguity in the WCC Service API I lost quite some time solving naming bugs. The following definition list is a heads up:

  • Contribution folders: a collection of features that used to be the Folders and Folders_g components (don't ask me what this means, I got it straight from the documentation here). The main feature of these components is a directory structure users can checkin and checkout content from. Additional features include search, locking, moving, updating and securing content items. Everything related to contribution folders is consistently referred to as a COLLECTION. All contribution folder services are prefixed with COLLECTION_, and service parameter have dCollectionXXX as their name. Most documentation on the web on how to add and change content using RIDC will use the old contribution folders functionality so you're likely to see it from time to time. Just know this: contribution folders should be considered deprecated.
  • Archive: the WebCenter Content archive is a feature I'm yet unfamiliar with. I do know from (painful) experience it adds to the ambiguous language of the WCC service API. Services like ADD_COLLECTION and REMOVE_COLLECTION refer to an Archive Collection rather than a Contribution Folder/Collection. Just remember, service names starting with COLLECTION_ are Contribution Folder services and the rest isn't.
  • Records management: records describe a physical or digital asset. Because records management comes with WebCenter Content you can easily create records for digital assets stored in the content server. Records are often used for retention management. Records are stored in folders for better manageability (Yay!), but unfortunately for us developers the RecordFolder is referred to as simply Folder. The database table used for storing RecordFolders is called FOLDERS and any services with [A-Z_]_FOLDER_[A-Z_] as their name are a RecordFolder service. Finally, record service parameters usually start with dFolder (for example dFolderID).
    I'm guessing this situation came about because records management and content management used to be two different products and later got integrated without resolving the language ambiguity.
  • Framework Folders: Framework Folders replace the Contribution Folders features. The documentation refers to them as Folders Services which can be confusing for developers who have used the old Folders component, and for us newbees it is confusing with Records Folders. Fortunately the FLD_ prefix naming convention is applied consistently and makes the new Folders Services clearly stand out. Folder Service parameters are usually prefixed with an "f" instead of a "d" to avoid confusion  between framework folders and records management service parameters.

If you're a bit confused by this list, don't worry, I still am. Just be careful, that's all. (Ubiqutuous language ... ahum)

The documentation on (Framework) Folder Services is quite brief. The documentation on 8-10-2012 for FLD_CREATE_FILE reads as follows:

8.2.4 FLD_CREATE_FILE

Service that creates a link to a document in Folders.
Service Class: intradoc.folders.FoldersService

Location: IdcHomeDir/resources/frameworkfolders_service.htm

Required Service Parameters

fParentGUID: The GUID of the parent folder in which the new link will be created.

Optional Service Parameters

$fileMeta: Metadata to be assigned to the link.

(I have been unable to find a definition of $fileMeta)
With this description you won't get far. The service appears to take only one service parameter: fParentGUID. There is no service parameter describing which file you want to link to. Not getting any further I found a post on OTN of someone who experienced the exact same problem. The answer didn't get me far but the author describes two other service parameters: dDocName and fFileType. dDocName is the content item ID, and fFileType is more like a 'fRelationshipType' or 'fLinkType' describing the type of link/relationship between the folder and file. The only two possible values for fFileType are "owner" and "soft". An "owner" link you can think of as the actual location of the file, "soft"-links are mere shortcuts to this file. You can have only one "owner" link and any number of "soft" links (thank you for this information user #938535 on OTN).

I simply tried calling FLD_CREATE_FILE with these three parameters and it worked. As it turns out all three parameters are required and you get vague error messages when you leave them out.

Knowing this we can now create a file link in a Framework Folder:


Two new methods were added: createFileLinkInFolder which does the actual work of creating a file link in the folder, and getFolderGUID which retrieves the folder GUID of a folder path from the content server. In addition several changes were made to main(String[] args) to upload a file, retrieve its content ID and invoke the folder services.

None of these changes should raise any eyebrows and I leave it to you to ask questions if you don't understand.

The standard output when executing this code should look something like this:

Connecting to content server at idc://localhost:4444 using username sysadmin and password
Succesfully connected RIDC client to idc://localhost:4444
Invoking service FLD_INFO (path="/Test")
Folder /Test has GUID: 688381C27711285F5D8227E6FC4EDDA5
Invoking service FLD_CREATE_FILE (folderGUID="688381C27711285F5D8227E6FC4EDDA5")
DataBinder {
    Files {
        none
    }
    LocalData {
        ClientEncoding = UTF-8
        IdcService = FLD_CREATE_FILE
        IsJava = 1
        NoHttpHeaders = 0
        UserDateFormat = iso8601
        UserTimeZone = UTC
        blDateFormat = yyyy-MM-dd HH:mm:ssZ!tUTC!mAM,PM
        blFieldTypes = fLastModifiedDate date,xCategoryFolder text,xHasFixedClone int,xWebFlag text,xIPMSYS_STATUS text,xReportDataSourceType text,xIsFixedClone int,xStorageRule text,xIPMSYS_REDACTION int,xForceFolderSecurity text,xFreezeID memo,xReadOnly text,xIsEditable text,xWCTags text,fCreateDate date,xInhibitUpdate text,xWCWorkflowAssignment text,xHideIncludeChildFolders int,dCreateDate date,xCategoryID bigtext,xHidden text,xReportScheduledInfo text,xCpdIsLocked int,StatusMessage message,xCollectionID int,xLifeCycleID bigtext,xIPMSYS_APP_ID text,xComments memo,xWCWorkflowApproverUserList memo,xIsRecord text,xReportContentType text,xWCPageId bigtext,xCpdIsTemplateEnabled int,xRecordFilingDate date,xReportType text,xIPMSYS_BATCH_ID1 int,xIPMSYS_PARENT_ID int,dOutDate date,xExternalDataSet bigtext,xIsDeletable text,xIsFrozen text,xReportFormat text,xPartitionId text,xIPMSYS_BATCH_SEQ text,dInDate date,dMessage message,xReportTemplate text,xReportDataSourceParams text,xIPMSYS_SCKEY text,xFreezeReason memo,dReleaseDate date,xFolderID bigtext,xReportDataSource text,xIsRevisionable text,xIdcProfile text
        changedMonikers = 
        changedSubjects = 
        computePermissions = 1
        dDocAccount = 
        dDocAuthor = sysadmin
        dDocName = CS000876
        dDocTitle = Test Folder upload
        dDocType = Document
        dExtension = png
        dInDate = 2012-10-08 15:36:14Z
        dOriginalName = testfile.png
        dProcessingState = Y
        dPublishedRevisionID = 
        dRevClassID = 876
        dRevLabel = 1
        dRevisionID = 1
        dSecurityGroup = Public
        dUser = sysadmin
        doSorting = 0
        fApplication = framework
        fClbraAliasList = 
        fClbraRoleList = 
        fClbraUserList = 
        fCreateDate = 2012-10-08 15:36:14Z
        fCreator = sysadmin
        fDocAccount = 
        fFileGUID = CBFB1BE10FA920B859524B54EB8EFA67
        fFileName = testfile.png
        fFileType = owner
        fFolderGUID = 688381C27711285F5D8227E6FC4EDDA5
        fInhibitPropagation = 17
        fLastModifiedDate = 2012-10-08 15:36:14Z
        fLastModifier = sysadmin
        fLinkRank = 0
        fOwner = sysadmin
        fParentGUID = 688381C27711285F5D8227E6FC4EDDA5
        fParentGUID_display = /Test
        fPublishedFileName = testfile.png
        fSecurityGroup = Public
        fldBrowsingMode = contribution
        idcToken = 
        itemCount = -1
        itemStartRow = 0
        itemType = 2
        localizedForResponse = 1
        refreshMonikers = 
        refreshSubMonikers = 
        refreshSubjects = 
        xClbraAliasList = 
        xClbraRoleList = 
        xClbraUserList = 
    }
    Options {
        none
    }
    ResultSets {
        none
    }
}

3. Resources


The code I used to print the DataBinder (RIDCUtils.dataBinderToString()) can be found here (it does the job and that's it!):

 

RSS Feed. This blog is proudly powered by Blogger and uses Modern Clix, a theme by Rodrigo Galindez.