A topic that comes up quite frequently on the OTN and intradoc_users forums as well as in discussions with some of our customers is “How do I add custom functionality to the checkin page?” This usually revolves around an integration with an external database to lookup values for metadata in order to keep the systems in sync or relate the items in some way. Since I’ve done similar customizations on several occasions I figured why not pull some of that code together for a little How To article.
Some of the UCM development concepts that will be touched on in this post:
- Custom Components
- Custom Services
- Database Providers
- Custom Query Resources
- Custom Java Service Handlers
- MergeInclude
- Resource Includes
By the end of this post you should have a working lookup to an external database on your checkin page.
A few assumptions before we start:
- The reader is familiar with basic UCM component architecture
- The reader is familiar with creating components using the Component Wizard
- The reader is familiar with creating a database provider in the UCM interface
- The reader knows how to create a custom class and include it in a component.
- This post will focus on the pieces of code needed to implement the solution
For info on components and providers check out the documentation here and here.
The basic steps we will run through in this post are as follows:
- Create a custom component
- Create a service
- Create a query resource
- Create a database provider
- Update the provider config
- Create a custom java service handler
- Create the ServiceHandler definition
- Create the resource includes
1. Create a custom component
On your UCM development machine open up the Component Wizard from either the Start Menu or $IntradocDir/bin. Once there Click Add and create a new Component called “MyExternalLookupComponent” or whatever you prefer. Code examples further on will assume that component name, make changes as appropriate.
2. Create a service
Once your component is created click Add on the Resource Definition tab and pick Service to create a new Content Server Service. Creating a custom service gives us a container to put our logic for retrieving info from the external database and formatting the response that we will read in on the checkin page. Fill out the Add Service dialog with the following:
- Name: MY_EXTERNAL_LOOKUP
- Service Class: Service
- Template: <empty>
- Service Type: <empty>
- Access Level: Read
- Subjects Notified: <empty>
- Error Message: Unable to retrieve external lookup info
Add a Service action with the following values:
- Type: Java method
- Action: executeExternalLookup
- Parameters:
MyExternalProvider,QexternalInfo,MyExternalLookupRS - Error Message: Error retrieving external lookup info
Once you are done your service definition file under $IntradocDir/custom/MyExternalLookupComponent/resources/myexternallookupcomponent_services.htm should contain the following:
<@table MyExternalLookupComponent_Services@> <table border=1><caption><strong>Scripts For Custom Services</strong></caption> <tr> <td>Name</td><td>Attributes</td><td>Actions</td> </tr> <tr> <td>MY_EXTERNAL_LOOKUP</td> <td>Service 1 null null null<br> Unable to retrieve external lookup info</td> <td>3:executeExternalLookup:MyExternalProvider,QexternalInfo,MyExternalLookupRS::Error retrieving external lookup info</td> </tr> </table> <@end@>
The 3 parameter values on the service action map to:
- The name of our database provider (to be added in step 4)
- The name of our query resource (to be added in step 3)
- The name of the result set to return and be added to the binder (used in steps 6 and 8 )
3. Create a query resource
Once we have our service defined we need to create the query that will be used to retrieve data from the external database. Using a query resource allows us to keep the SQL from being hard-coded in our Java source, thereby making it easier to change. A query resource also means that the query is executed using a Java PreparedStatement, giving a performance boost over directly including SQL in our code.
In Component Wizard click Add on the Resource Definition tab again and select “Query”. Enter your SQL and parameters to that SQL. Your query definition file: $IntradocDir/custom/MyExternalLookupComponent/resources/myexternallookupcomponent_query.htm should contain code similar to below:
<@table MyExternalLookupComponent_Queries@> <table border=1><caption><strong>Query Definition Table</strong></caption> <tr> <td>name</td><td>queryStr</td><td>parameters</td> </tr> <tr> <td>QexternalInfo</td> <td>SELECT * FROM SomeTable WHERE lookupField = ?</td> <td>lookupField varchar</td> </tr> </table> <@end@>
Query parameters are specified by question marks (?) in the SQL statement. Their names in the parameters box are references to variables in the DataBinder. In a code sample further on we will show how the “lookupField” variable is added to the binder.
4. Create a database provider
In order for the content server to connect to to a different database a provider needs to be added. This can be achieved through the Provider page under the UCM Administration menu. The provider name used for this example is “MyExternalProvider”.
5. Update the provider config
Once you have added the new provider and restarted the content server there are a couple “undocumented” additions that need to be made manually to the provider’s configuration. In a text editor open up the provider.hda file for the provider you just created. This file is under $IntradocDir/data/providers/myexternalprovider/.
First we need to add the lines that tell our provider where to find our query resource we defined in step 3. Add the below 3 lines at the top of the LocalData section (adjust directory location and table name accordingly).
ProviderConfig=intradoc.server.DbProviderConfig QueryResourceFile=c:/stellent/10gr3/custom/MyExternalLookupComponent/resources/myexternallookupcomponent_query.htm QueryResourceTables=MyExternalLookupComponent_Queries
IMPORTANT: The QueryResourceFile variable is an absolute directory location to the file containing our query resource. When you deploy this component to your production box this must be changed to reflect the location of the file on that server. When you create the provider on production (or any other server) make sure this is the correct location. Make sure to restart the content server after changing provider.hda
The other lines that need to be added to the provider.hda are to workaround a bug with the 10gr3 version of UCM that Oracle has never bothered to fix (because this easy workaround exists). At the bottom of provider.hda add the following lines:
@ResultSet ColumnMap 2 column alias @end
6. Create a custom java service handler
A ServiceHandler class is where we write custom java methods that can be used as actions on a UCM service. It provides a mechanism to reuse methods between multiple services without having to write a custom service class. It also allows you to reuse those service methods between components as long as the component that contains the service handler is also enabled on the content server.
Open up the IDE of your choice (I recommend Eclipse) paste in the following code, compile and make sure that the class file is output in the /classes directory under your component directory. Note: UCM 10gr3 runs on version 1.5 of the JVM. Make sure that your classes are compiled with compatibility for that version turned on.
package com.mycompany.externallookup;
import intradoc.data.DataException;
import intradoc.data.DataResultSet;
import intradoc.data.Workspace;
import intradoc.provider.Provider;
import intradoc.provider.Providers;
import intradoc.server.ServiceHandler;
import intradoc.shared.SharedObjects;
public class MyExternalProviderHandler extends ServiceHandler
{
public void executeExternalLookup() throws DataException
{
String providerName = m_currentAction.getParamAt(0);
String queryName = m_currentAction.getParamAt(1);
String returnRsName = m_currentAction.getParamAt(2);
Workspace ws = getProviderWorkspace(providerName);
DataResultSet drs = new DataResultSet();
drs.copy(ws.createResultSet(queryName, m_binder));
m_binder.addResultSet(returnRsName, drs);
}
private Workspace getProviderWorkspace(String provider)
{
Workspace workspace = null;
Provider wsProvider = Providers.getProvider(provider);
if(wsProvider != null)
{
workspace = (Workspace)wsProvider.getProvider();
}
return workspace;
}
}
You will notice that the name of our method maps to the value we entered for “Action” when we created the service action. The above code reads in the parameters from our service, gets a reference to the external provider, executes the query, and adds the results back to the binder to be used later.
7. Create the ServiceHandler definition
In order for our service to be able to execute the method in our ServiceHandler class we need to add a resource definition for the ServiceHandler that is merged to the internal ServiceHandlers table in the content server. When a service action is called the content server looks for the method first in the class defined in the “Service Class” for that particular service. If it isn’t found there is checks the ServiceHandlers table to see if the method is defined in any custom handlers for the service.
To create this definition Click “Add” on the Resource Definition tab in Component Wizard and select “Resource – Static Table (HTML Format)”. For Table Name add “ServiceHandlers” after the prepopulated value. Click the “Merge To” checkbox and select ServiceHandlers from the dropdown. This will create the $IntradocDir/custom/MyExternalLookupComponent/resources/myexternallookupcomponent_resource.htm file containing the table we just defined with default values. Open up the file in a text editor and replace what was created with the following:
<@table MyExternalLookupComponent_ServiceHandlers@> <table border=1><caption><strong> <tr> <td>serviceName</td><td>handler</td><td>searchOrder</td> </tr> <tr> <td>Service</td><td>com.mycompany.externallookup.MyExternalProviderHandler</td><td>10</td> </tr> </table> <@end@>
8. Create the resource includes
Now that we have created the backend for querying our external database via UCM we can put in the UI pieces. To do so we have 3 resource includes to add to the $IntradocDir/custom/MyExternalLookupComponent/resources/myexternallookupcomponent_resource.htm file created in the previous step. Resource includes are the pieces of code that are used to render the UCM interface. The contain a mix of IdocScript (server side evaluated script), HTML, JavaScript, and CSS. All of the standard UCM pages are built using a set of resource includes. Using our custom component we can extend or override these components to customize the UI for our needs.
The first include we will look at is to add the Lookup button to the Checkin form itself. This is done by extending the “std_nameentry_row” resource include. This include defines the HTML that contains a row on a checkin, update, info, or search screen. Paste the following into our resource.htm file.
<@dynamichtml std_nameentry_row@>
<$if strEquals(fieldName, "xLookupField") and not (isInfo or isQuery)$>
<tr <$strTrimWs(inc("std_nameentry_row_attributes"))$>>
<td <$if captionFieldWidth$>width="<$captionFieldWidth$>"<$endif$> <$if isInfo$>align=right<$endif$>><$strTrimWs(inc(fieldCaptionInclude))$></td>
<td <$if isFieldInfoOnly$>colspan="100"<$endif$> <$if captionEntryWidth$>;width="<$captionEntryWidth$>"<$endif$>><$inc(fieldEntryInclude)$>
<!-- addition -->
<input type="button" value="Lookup" onclick="externalLookup(this.form.xLookupField);">
<!-- end addition -->
</td>
</tr>
<$else$>
<$include super.std_nameentry_row$>
<$endif$>
<@end@>
To add our button we simply put it inside the same table cell to the right of the input field. We define an onclick handler to call a javascript function called “externalLookup” which looks like this:
function externalLookup(lookupField)
{
var url="<$HttpCgiPath$>?IdcService=MY_EXTERNAL_LOOKUP&lookupField=" + lookupField.value + "&MergeInclude=my_external_lookup_response&IsJava=1×tamp="+new Date().getTime();
var callback =
{
success: function(originalRequest)
{
//get reference to checkin form
var checkinForm = originalRequest.argument.lookupField.form;
//get response as a javascript object
var obj = eval(originalRequest.responseText); //assuming response coming back as JSON
//populate checkin form fields
checkinForm.xFirstFieldToPopulate.value = obj.firstNewValue;
checkinForm.xSecondFieldToPopulate.value = obj.secondNewValue;
checkinForm.xThirdFieldToPopulate.value = obj.thirdNewValue;
},
failure:function(o){},
argument: {lookupField:lookupField}
};
var request = YAHOO.util.Connect.asyncRequest('GET', url, callback);
}
Add this javascript by extending the std_javascript_header_functions like so:
<@dynamichtml std_javascript_header_functions@>
<$include super.std_javascript_header_functions$>
//function here
<@end@>
The javascript function is where the proverbial “ajax magic” happens. I used YUI (Yahoo User Interface) library for this example since it is already installed with UCM. To ensure that the YAHOO.Util.Connect object is included on the checkin page add the following resource include to our resources.htm file:
<@dynamichtml std_checkin_html_head_declarations@> <$include super.std_checkin_html_head_declarations$> <pre><script type="text/javascript" src="/idc/resources/yui/yahoo/yahoo-min.js"></script> <script type="text/javascript" src="/idc/resources/yui/yahoo-dom-event/yahoo-dom-event.js"></script> <script type="text/javascript" src="/idc/resources/yui/event/event-min.js"></script></pre> <script type="text/javascript" src="/idc/resources/yui/connection/connection-min.js"></script> <@end@>
With the YUI connection object added to the page you need to make sure to modify the externalRequest function to populate the fields specific to your form. The function itself does an asynchronous request to a URL to pull back data to populate on the form. The URL contains the following query parameters:
- IdcService=MY_EXTERNAL_LOOKUP (our service we defined)
- lookupField=<value from our field> (this is passed in as “lookupField” in our binder where our query picks it up)
- MergeInclude=my_external_lookup_response (defined below, a specifically defined resource include to render as the response)
- IsJava=1 (used in conjunction with MergeInclude tells UCM to render the response exactly as the include defines)
- timestamp=<current time in milliseconds> (helps us avoid browser caching of the ajax response by making the request string “different” every time)
The final piece of the puzzle is our resource include that renders the response as JSON (JavaScript Object Notation). Sending back the response as JSON allows us to get a native object in our externalLookup function to use to populate our checkin form fields. The include looks something like:
<@dynamichtml my_external_lookup_response@>
<$if strEquals(IdcService, "MY_EXTERNAL_LOOKUP")$>
<$exec rsFirst("MyExternalLookupRS")$>
({
firstNewValue: '<$getValue("MyExternalLookupRS", "firstNewValue")$>',
secondNewValue: '<$getValue("MyExternalLookupRS", "secondNewValue")$>',
thirdNewValue: '<$getValue("MyExternalLookupRS", "thirdNewValue")$>'
})
<$endif$>
<@end@>
Our response include simply grabs values from the first row in the result set returned by our service handler and creates a JSON response.
To run the code make sure that your component is Enabled, restart your content server, then click the Lookup button on your checkin form. Hopefully everything works!
An example screenshot from one of the implementations we have done. This is not what the code in this article will look like except for the “Change” button on the page. Hopefully though this will give you a good example of what can be done.

May 28, 2010 at 4:50 pm |
[...] Read full story [...]
June 1, 2010 at 2:20 am |
Hi,
I try to develop the component, but I had problems with YAHOO.util.Connect.
I have a javascript error ‘YAHOO.util.Connect’ is null or not an object.
The component YahooUserInterfaceLibrary is installed on my UCM and enable.
When I lok in my check-in page, I don’t see the js file “connection.js” included in my page.
How can I solve this problem ?
THanks
Pierre
June 1, 2010 at 6:35 am |
Hey Pierre,
Thanks for running through the post.
To get the connection.js on the checkin page override the std_checkin_html_head_declarations include and add the following line after you include super.std_checkin_html_head_declarations (I will update the post to reflect this).
<script type=”text/javascript” src=”/idc/resources/yui/connection/connection.js”></script>
June 1, 2010 at 6:59 am |
Sorry, I didn’t see your reply.
June 1, 2010 at 6:57 am |
Ok,
I add :
<script src="resources/yui/yahoo-dom-event/yahoo-dom-event.js”>
<script src="resources/yui/connection/connection-min.js”>
<script src="resources/yui/yahoo/yahoo-min.js”>
<script src="resources/yui/event/event-min.js”>
and this problem is solved.
But, I have another problem with the line :
“var obj = eval(originalRequest.responseText);”
I have a “Unterminated string constant”.
June 1, 2010 at 7:04 am |
I updated the my_external_lookup_response include in the post to fix the unterminated string constant issue. Was missing a quote.
June 1, 2010 at 7:13 am
Again, I did not see your post before to answer.
You are right. THanks a lot for your post and your quick answers.
June 1, 2010 at 7:12 am |
Ok, it was my mistake. I forgot a ‘.
It works perfectly.
Thanks a lot for your post.
It is very useful.
June 2, 2010 at 5:47 am |
[...] How To: Adding an external database AJAX lookup to a UCM checkin … [...]
June 3, 2010 at 4:12 am |
Hi Andy,
Thanks for the post. Apart from providing a solution to a popular request it also shows the approach develoopers must follow while working on similar problems.
regards,
deepak
June 28, 2010 at 10:28 am |
Hi Andy, really nice mini tuto, I learnt some stuff from it.
It’d be interesting tho if you could post how your changes look like (i.e check-in page with lookup list showing) to give some people an idea of the final result.
Cheers,
June 28, 2010 at 11:32 am |
Thanks malky!
I added a screenshot from one of the implementations we have done to give people an idea of what can be done. In the case of the screenshot it does a custom lookup into the content server itself instead of to an external source. The same concepts apply though.
June 29, 2010 at 8:54 am
Great! it looks awesome!!
August 16, 2010 at 3:13 pm |
Hi,
nice tutorial. I got it working.
The query in the lookup works great and I can get information into my checkin page, but I need to execute also an Insert into a temp database table. How can I implement it?
Thanks for any idea.
Andres
August 17, 2010 at 7:25 am |
Hey Andres,
Doing an insert is just as easy as doing a query. Add a new query to your query resource file and call it something like “ItempData”. Write the query as a standard SQL INSERT statement.
Once you have that you can add another method to the ServiceHandler that will handle the insert. The main difference is that we are no longer returning a resultset and the call to do an insert is ws.execute(“ItempData”, m_binder); instead of ws.createResultSet().
Hope that helps.
August 17, 2010 at 10:28 am
Andy,
thanks for the quick response. I will test and let you know if I need additional help.
Andres
August 23, 2010 at 12:03 pm
Andy,
Insert works great. Thanks.
Another question,
how to execute a procedure/function ?
Thanks
Andres
November 6, 2010 at 6:07 pm |
Solid information. This was my 1st time to this blog. Thanks for sharing this. I must revisit this website. I was a boat repairman for a long time. Our repair tip of the century is: Do not make an attempt a extremely difficult work job without a professional. This will cost you additional money in the end. Thanks once again…..
December 9, 2010 at 10:32 am |
thank you very much very useful post
January 28, 2011 at 7:54 am |
.’* I am really thankful to this topic because it really gives great information ‘–
March 7, 2011 at 4:29 am |
HHello guys
i am facing a problem to enter a yahoo chat room. When i try to enter a room i see this error yahoo.util.connect is nul or not an object and because of this i cant enter in room pls let me know how to solve this problem.
Thanks
March 8, 2011 at 9:04 pm |
Hi Andy, great article. I’m having trouble getting Eclipse to place the class file into MyExternalProviderHandler/classes folder. Since there’s a package (externallookup) it keeps outputting to MyExternalProviderHandler/classes/externallookup. Then appears to not find the class by throwing this error:
Unable to create service handler. Unable to instantiate java class code for ‘externallookup.MyExternalProviderHandler’ at location ‘externallookup.MyExternalProviderHandler’ with default location at ‘externallookup.MyExternalProviderHandler’. java.lang.ClassNotFoundException: externallookup.MyExternalProviderHandler
Can you advise on how to fix this?
March 9, 2011 at 7:28 am |
Hey Greg,
One thing I would try to fix that issue is to Build the component and then actually install it via the Component Manager. Sometimes the content server doesn’t add the component classpath entries to the server classpath until it actually goes through the “install” process.
As for where Eclipse is placing the file if you have a package of externallookup in your class then /classes/externallookup/ is actually the correct place for the file to be.
Hope that helps,
Andy
March 9, 2011 at 7:34 am |
Thanks I will try your suggestion.
So even if you have the package in the class file as:
package externallookup
You don’t actually have to have the package in the Eclipse tree hierarchy on the left?
March 9, 2011 at 7:53 am |
If you have a package in your class Eclipse will automatically put it into the tree hierarchy in that same package. In fact if there is a discrepancy then Eclipse will throw you an error on the package line in the file with auto correct options for changing the package or moving the file to correct location.
April 7, 2011 at 6:38 am |
Andy,
Thanks for the great post – What I don’t understand, however, is where the popup dialog “Vendor Lookup” was generated.
April 7, 2011 at 6:43 am |
Hey Mark,
Like I mentioned in the post the code samples that are above do not contain the Vendor Lookup Popup. It is merely showing an extension of what you could do with the code to provide an actual lookup interface as opposed to just automatically filling in fields.
June 14, 2011 at 12:09 pm |
Hello,
Im just creating a component to make a change in std_nameentry_row. I also create a filed with the name “LookupField”. However, when I go to a chekin form I see the field but not the button on its right…
any ideas? How can I be sure that my code is being executed?
thank you
ps: using ucm 11g
August 28, 2011 at 4:45 pm |
Hi, I have done the whole code setup and getting the html includes in my check-in page but while clicking the lookup button i get the below exception for executing the service
intradoc.common.ServiceException: !csInvalidAuthorizationToken
at intradoc.server.ServiceSecurityImplementor.validateAuthorizationToke
(ServiceSecurityImplementor.java:900)
at intradoc.server.ServiceSecurityImplementor.globalSecurityCheck(Servi
eSecurityImplementor.java:181)
at intradoc.upload.UploadSecurityImplementor.globalSecurityCheck(Upload
ecurityImplementor.java:57)
at intradoc.server.Service.globalSecurityCheck(Service.java:2671)
at intradoc.server.ServiceRequestImplementor.doRequest(ServiceRequestIm
lementor.java:678)
at intradoc.server.Service.doRequest(Service.java:1890)
at intradoc.server.ServiceManager.processCommand(ServiceManager.java:43
)
at intradoc.server.IdcServerThread.processRequest(IdcServerThread.java:
65)
at intradoc.idcwls.IdcServletRequestUtils.doRequest(IdcServletRequestUt
ls.java:1343)
August 28, 2011 at 5:22 pm |
Sorry All, the problem got resolved by increasing the access right of the service
December 14, 2011 at 11:25 am |
ayaskanth, Can you explain how you increased the access right?
September 13, 2011 at 10:08 pm |
Excellent site. Plenty of handy info in this article. I’m mailing it to a couple of good friends ans also revealing in delicious. As well as, thanks for your sweat!
October 10, 2011 at 11:31 am |
.
Hello,
Ours is a 10gR3 UCM application.
We have a custom component which queries a table which is not present in the UCM
schema. The table is in an another schema and we have not configured any
provider and it works fine.
I was going through Bex’s book and fishbowl solution blog. There they have
specified a provider needs to be configured for this.
Is the provider configuration needed when we access a table in a completely
different database and not required when we access a table in a different schema
which is in the same database as the UCM schema.
Request you to please clarify my doubt
Thanks,
Selvam S
November 23, 2011 at 8:07 pm |
[xbox360|xbox 360 games|games on xbox 360]…
[...]How To: Adding an external database AJAX lookup to a UCM checkin form « C4[...]…
December 14, 2011 at 10:39 pm |
FMS File Date Changer is an easy-to-use tool to allow you to change or modify the dates…
[...]How To: Adding an external database AJAX lookup to a UCM checkin form « C4 Blog by Fishbowl Solutions[...]…