Showing posts with label webcenter. Show all posts
Showing posts with label webcenter. Show all posts

Friday, January 29, 2016

RIDC: Checkin your file to Oracle HCM Cloud

In this blog I will talking about my findings of using Oracle HCM Cloud as storage. You can actually checkin/checkout your files here. There are various reasons you may want to store your files there. Few could be
1. FBL
2. HDL
3. You Paas Application needs to store some file (Although I will prefer storage cloud but then you might need a separate license. Why not to use UCM of Oracle HCM Cloud itself).

Before coming to actual implementation, I would like to clear few things.

1. If saas url is https://hcm-abc.oracleoutsourcing.com/homePage/faces/AtkHomePageWelcome, What will be content-server url
Ans: https://fs-abc.oracleoutsourcing.com/cs/idcplg (Same SaaS username/password will work)


2. As we are going to use RIDC apis we need to understand supported protocols:

Intradoc (with SSL also): This is a direct connection between client and server. Server must be configured to accept request from selected clients. You must be setting IntradocServerPort and SocketHostAddressSecurityFilter in WCC_domain/ucm/cs/config/config.cfg file or (Web UI hos:port/cs/idplg >> Administration >> Admin Server >> General Configuration




As this connection is done over trusted network, it does not need any password authentication. Only provide username and do transaction.

IdcContext userContext = new IdcContext("HCM_ADMIN"); //No need of password.
Intradoc communication can also be done over SSL. You should do below line of coding for that.
// create the manager
IdcClientManager manager = new IdcClientManager();
// build a client that will communicate using the intradoc protocol
IdcClient idcClient = manager.createClient("idc://localhost:4444");
// set the SSL socket options
IntradocClientConfig config = (IntradocClientConfig) idcClient.getConfig();
config.setKeystoreFile("ketstore/client_keystore");  //location of keystore file
config.setKeystorePassword ("password");      // keystore password
config.setKeystoreAlias("SecureClient");  //keystore alias
config.setKeystoreAliasPassword("password");  //password for keystore alias

In case of SaaS, intradoc may not be useful as we can’t configure SocketHostAddressSecurityFilter property of UCM server.

http/https: RIDC communicates with the web server for Oracle Content Server using the Apache HttpClient package. Unlike Intradoc, this protocol requires authentication credentials for each request. It does not require any configuration on server side.
If you want to use http/https, you need to use url as 
// create the manager
IdcClientManager manager = new IdcClientManager();

IdcClient idcClient = manager.createClient("https://host.oracleoutsourcing.com/_dav/cs/idcplg");
//NOTE: we have used _dav in url. Otherwise you will receive //oracle.stellent.ridc.protocol.http.HttpProtocolException: Http status: //HTTP/1.1 302 Moved Temporarily //ERROR

//If you are using https, you need to set truststores as 
System.setProperty("javax.net.ssl.trustStore", "D:/jks/Cert_DER.jks");
System.setProperty("javax.net.ssl.trustStorePassword","mypassword");

//For usercontext you should set AuthScheme as Basic
IdcContext userContext = new IdcContext("username", "password");             
userContext.setAuthScheme(IdcContext.HttpAuthScheme.BasicAuth);

JAX-WS: The JAX-WS protocol is supported only in Oracle WebCenter Content 11g with Oracle Content Server running in Oracle WebLogic Server. To provide JAX-WS support, several additional JAR files are required.


NOW COMING TO THE MAIN PART: Steps to import a file on Oracle HCM Cloud (UCM) server

1. No special configuration is needed on server side
2. Get url to make connection. It should be https://host.oracleoutsourcing.com/_dav/cs/idcplg
Replace host with your environment and don’t forget _dav
3. Get username/password who has access to UCM. You can use your SaaS username/password to login to https://host.oracleoutsourcing.com/cs
If you are able to login successfully then check if you can upload/download documents etc. If you can do it through UI, you should be able to do it programmatically.
4. Create keystore and install certificate. (THIS STEP IS IMPORTANT and ERROR PRON)
  • Launch https://host.oracleoutsourcing.com/cs in IE
  • Install certificate in browser 
    Lock-Icon-in-url >> View Certificate >> Install Certificate
            

  • Export certificate from browser Tools >> Internet Options >> Content >> Certificate >> Other People >> Select Certificate >> Export

  • Import downloaded certificate in a keystore
  • If you do not have a keystore already and you want to create new keystore altogether.  
    NOTE: In case of weblogic web program you may want to get existing keystore and then add new certificate instead of creating new keystore. 
    keytool -import -file D:\jks\Cert_DER.cer -alias orakey -keypass mypassword -keystore D:\jks\Cert_DER.jks -storepass mypassword
    This command will cerate a keystore (jks file) with a key (named orakey). Password for key and keystore is same as ‘mypassword’. Certificate is imported in this keystore. 

    If you already have keystore and you want to import certificate in it 
    keytool -importcert -file D:\jks\Cert_DER.cer –keystore D:\oldjks\Cert_DER.jks -alias orakey -storepass <whatever-is-keystore-password>

    If you want to list keystore data you can 
    keytool -list -v -keystore D:\jks\Cert_DER.jks -storepass mypassword

Following above keystore steps your keystore should be ready with appropriate certificate.

Now we need to write a java code with RIDC apis and configure it to use jks file we just created. 
NOTE: If you are using weblogic server then you should configure weblogic keystore instead of referring jks from code.

5. Java Code: Let say you have placed your jks as D:/jks/Cert_DER.jks and file that you want to import at D:/file/test.txt

import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.IOException;
import oracle.stellent.ridc.IdcClient;
import oracle.stellent.ridc.IdcClientException;
import oracle.stellent.ridc.IdcClientManager;
import oracle.stellent.ridc.IdcContext;
import oracle.stellent.ridc.model.DataBinder;
import oracle.stellent.ridc.model.TransferFile;
import oracle.stellent.ridc.protocol.ServiceResponse;

public class RIDCUtil {
      public static void main(String[] arg) throws Exception {
             try {
                            IdcClientManager m_clientManager = new IdcClientManager();
                            IdcClient idcClient = m_clientManager.createClient("https://host.oracleoutsourcing.com/_dav/cs/idcplg");
                            IdcContext userContext = new IdcContext("username", "pwd");
 userContext.setAuthScheme(IdcContext.HttpAuthScheme.BasicAuth);
 System.setProperty("javax.net.ssl.trustStore", "D:/jks/Cert_DER.jks");
           System.setProperty("javax.net.ssl.trustStorePassword","mypassword");
 checkin(idcClient, userContext, "D:/file/test.txt", // Replace with fully qualified path to source file
          "Document", // content type
          "Test Document to check RIDC", // doc title
           userContext.getUser(), // author
           "FAFusionImportExport", // security group
           "hcm$/dataloader$/import$", // account
           "TEST_DOC_FOR_RIDC") // dDocName - this is the ContentId
                        ;
        } catch (IdcClientException e) {
                e.printStackTrace();
            }
    }

/** * Method description * *
     * @param idcClient *
     * @param userContext *
     * @param sourceFileFQP fully qualified path to source content *
     * @param contentType content type *
     * @param dDocTitle doc title *
     * @param dDocAuthor author *
     * @param dSecurityGroup security group *
     * @param dDocAccount account *
     * @param dDocName dDocName * *
     * @throws IdcClientException */
    public static void checkin(IdcClient idcClient, IdcContext userContext,
                               String sourceFileFQP, String contentType,
                               String dDocTitle, String dDocAuthor,
                               String dSecurityGroup, String dDocAccount,
                               String dDocName) throws IdcClientException {
                                   
        InputStream is = null;
        try {
            String fileName = sourceFileFQP.substring(sourceFileFQP.lastIndexOf('/') + 1);
            is = new FileInputStream(sourceFileFQP);
            long fileLength = new File(sourceFileFQP).length();
            TransferFile primaryFile = new TransferFile();
            primaryFile.setInputStream(is);
            primaryFile.setContentType(contentType);
            primaryFile.setFileName(fileName);
            primaryFile.setContentLength(fileLength); // note!!! when using HTTP protocol (not intradoc/jaxws) - one must explicitly // set the Content Length when supplying an InputStream to the transfer file // e.g.
            primaryFile.setContentLength(fileLength); // otherwise, a 0-byte file results on the server
            DataBinder request = idcClient.createBinder();
            request.putLocal("IdcService", "CHECKIN_UNIVERSAL");
            request.addFile("primaryFile", primaryFile);
            request.putLocal("dDocTitle", dDocTitle);
            request.putLocal("dDocAuthor", dDocAuthor);
            request.putLocal("dDocType", contentType);
            request.putLocal("dSecurityGroup", dSecurityGroup);
            // if server is setup to use accounts - an account MUST be specified
            // // even if it is the empty string; supplying null results in Content server
            // // attempting to apply an account named "null" to the content!
            request.putLocal("dDocAccount", dDocAccount == null ? "" : dDocAccount);
            if (dDocName != null && dDocName.trim().length() > 0) {
                request.putLocal("dDocName", dDocName);
            }
            // execute the request
            ServiceResponse response = idcClient.sendRequest(userContext, request); // throws IdcClientException
            // get the binder - get a binder closes the response automatically
            DataBinder responseBinder = response.getResponseAsBinder(); // throws IdcClientException
        } catch (IOException e) {
            e.printStackTrace(System.out);
        } finally {
            if (is != null) {
                try { is.close();
            } catch (IOException ignore) {
                    }
                }
            }
        }
    }

If you are making your RIDC call using a web program deployed on weblogic server, you should not set javax.net.ssl.trustStore  and javax.net.ssl.trustStorePassword property in java code but you should configure jks file in weblogic.



6. Above code can be improved in many way. You may want to checkin file in a particular folder, you may want to see if file already present, You may want to handle exception gracefully, you may want to take all hardcoded strings (ucm-url, ucm-username/password, keystore-location/password, content-type, security-group, account etc.) to a property file. Here it is just a POC.


7.  To run java program from command prompt, generate a jar file containing RIDCUtil (say MyIntegrationJar.jar). Place all required jars in one directory and run below command from that directory.

C:\jdk1.6.0_31_64\bin\java -classpath .\MyIntegrationJar.jar;.\oracle.ucm.ridc-11.1.1.jar;.\commons-httpclient-3.1.jar;.\commons-logging-1.0.4.jar;.\commons-codec-1.2.jar RIDCUtil

8. You can verify document in UCM by login into UCM https://host.oracleoutsourcing.com/cs and searching the document 



Below are the various issue that you may face while implementing it. Most of them are actually certificate related


Issues Solutions
Caused by: java.security.InvalidAlgorithmParameterException: the trustAnchors parameter must be non-empty
at java.security.cert.PKIXParameters.setTrustAnchors(PKIXParameters.java:183)
at java.security.cert.PKIXParameters.(PKIXParameters.java:103)
at java.security.cert.PKIXBuilderParameters.(PKIXBuilderParameters.java:87)
at sun.security.validator.PKIXValidator.(PKIXValidator.java:55)
jks file cannot be located. Check if you have given correct path for jks file.NOTE: On windows you should use / instead of \\.
Caused by: java.net.SocketException: java.security.NoSuchAlgorithmException: Error constructing implementation (algorithm: Default, provider: SunJSSE, class: com.sun.net.ssl.internal.ssl.DefaultSSLContextImpl)
at javax.net.ssl.DefaultSSLSocketFactory.throwException(SSLSocketFactory.java:179)
at javax.net.ssl.DefaultSSLSocketFactory.createSocket(SSLSocketFactory.java:212)

Caused by: java.security.NoSuchAlgorithmException: Error constructing implementation (algorithm: Default, provider: SunJSSE, class: com.sun.net.ssl.internal.ssl.DefaultSSLContextImpl)
at java.security.Provider$Service.newInstance(Provider.java:1245)
at sun.security.jca.GetInstance.getInstance(GetInstance.java:220)

Caused by: java.io.IOException: Keystore was tampered with, or password was incorrect
at sun.security.provider.JavaKeyStore.engineLoad(JavaKeyStore.java:771)
at sun.security.provider.JavaKeyStore$JKS.engineLoad(JavaKeyStore.java:38)
at java.security.KeyStore.load(KeyStore.java:1185)

Caused by: java.security.UnrecoverableKeyException: Password verification failed
at sun.security.provider.JavaKeyStore.engineLoad(JavaKeyStore.java:769)
... 30 more
keystore or key password is wrong. I generally keep keystore and key password same and provide that.
oracle.stellent.ridc.protocol.http.HttpProtocolException: Http status: HTTP/1.1 302 Moved Temporarily
at oracle.stellent.ridc.protocol.http.IdcHttpProtocol.writeRequest(IdcHttpProtocol.java:407)
at oracle.stellent.ridc.IdcClient.sendRequest(IdcClient.java:179)
Use _dav in url. By default cs/idcplg url is for web access and it redirects user to login page. If you add _dav in url, then you can pass username/password with request and they will be used to login.
oracle.stellent.ridc.protocol.ProtocolException: javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
at oracle.stellent.ridc.protocol.http.IdcHttpProtocol.sendRequest(IdcHttpProtocol.java:661)
at oracle.stellent.ridc.protocol.http.IdcHttpProtocol.sendRequest(IdcHttpProtocol.java:623)
at oracle.stellent.ridc.protocol.http.auth.BasicAuthHandler.sendAuthenticatedRequest(BasicAuthHandler.java:91)
Caused by: javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
at com.sun.net.ssl.internal.ssl.Alerts.getSSLException(Alerts.java:174)
at com.sun.net.ssl.internal.ssl.SSLSocketImpl.fatal(SSLSocketImpl.java:1731)
at com.sun.net.ssl.internal.ssl.Handshaker.fatalSE(Handshaker.java:241)
at com.sun.net.ssl.internal.ssl.Handshaker.fatalSE(Handshaker.java:235)
at com.sun.net.ssl.internal.ssl.ClientHandshaker.serverCertificate(ClientHandshaker.java:1206)
…..
Caused by: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
at sun.security.validator.PKIXValidator.doBuild(PKIXValidator.java:323)
at sun.security.validator.PKIXValidator.engineValidate(PKIXValidator.java:217)
at sun.security.validator.Validator.validate(Validator.java:218)
at com.sun.net.ssl.internal.ssl.X509TrustManagerImpl.validate(X509TrustManagerImpl.java:126)
at com.sun.net.ssl.internal.ssl.X509TrustManagerImpl.checkServerTrusted(X509TrustManagerImpl.java:209)
at com.sun.net.ssl.internal.ssl.X509TrustManagerImpl.checkServerTrusted(X509TrustManagerImpl.java:249)
at com.sun.net.ssl.internal.ssl.ClientHandshaker.serverCertificate(ClientHandshaker.java:1185)
... 31 more
Caused by: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
at sun.security.provider.certpath.SunCertPathBuilder.engineBuild(SunCertPathBuilder.java:174)
at java.security.cert.CertPathBuilder.build(CertPathBuilder.java:238)
at sun.security.validator.PKIXValidator.doBuild(PKIXValidator.java:318)
... 37 more
It means, Java has located keystore but using that keystore its not able to make handshake. There could be multiple reasons for that
  1. If you are using standalone java program, you might not have added below java code System.setProperty("javax.net.ssl.trustStore", "D:/jks/Cert_DER.jks"); System.setProperty("javax.net.ssl.trustStorePassword","mypassword");
  2. If you are using web program on weblogic, You might not have configured your jks as keystore.
  3. Your path of jks is wrong
  4. For windows also you should use / to define path. You might have used \\ or \
  5. Your jks file does not have certificate that is needed to communicate with server. Verify it using keytool –list command
  6. Your jks file has expired certificate. Verify it using keytool –list (Valid From and Valit To). If expired, download new cer, import it in keystore and mention new keystore in code.
oracle.stellent.ridc.protocol.ServiceException: Content item 'TEST_DOC_FOR_RIDC' was not successfully checked in. The content item is not currently checked out.
at oracle.stellent.ridc.protocol.ServiceResponse.getResponseAsBinder(ServiceResponse.java:142)
at oracle.stellent.ridc.protocol.ServiceResponse.getResponseAsBinder(ServiceResponse.java:108)
It means content is already present in server and it must be checkedout before check in. Ideally you should search content first using GET_SEARCH_RESULTS service and dIsCheckedOut metadata. If content is not checked out you should check-out first and then check-in.
org.apache.commons.httpclient.auth.AuthChallengeProcessor selectAuthScheme
INFO: basic authentication scheme selected
org.apache.commons.httpclient.HttpMethodDirector processWWWAuthChallenge
INFO: No credentials available for BASIC 'UCM WebDAV Server'@host.oracleoutsourcing.com:443
These are just warnings but its because you missed to set authentication scheme userContext.setAuthScheme(IdcContext.HttpAuthScheme.BasicAuth);

Wednesday, February 4, 2015

Consuming JSR 286 Portlet from WSRP producer in a WebCenter application

In our previous blog (Deploy JSR 286 Portlet) we have created a portlet and deployed it on server. Now in this blog we will consume it on WebCenter application

Our WSDL url from deployed application is http://127.0.0.1:7101/jsr286/portlets/wsrp2?WSDL
We will follow these steps
a. Create a WSRP producer connection
b. Add portlet to a page
c. Run page and see portlet content

Step1:Create a WSRP producer connection: Open ADF application in which you want to add portlets
Navigate to Application Resources > Connection > New Connection > WSRP Producer
Provide registration name, wsdl url, security details (if any) in WSRP Production Connection wizard.
Once connection is done verify if by expanding it. We should be able to see portlets deployed on WSRP producer
You will notice following changes in your project
1. Portlet configuration settings are added in adf-config.xml file
2. If its only ADF project, jdev will add required WebCenter libraries
3. Lots of pxml files got added in <application>\mds\oracle\adf\portlet directory. These files contain information about portlets

Step2: Add portlet on a page:
Create an adf page and drag and drop portlet from connection to page.
You will see an entry in page like

Similarly we can add Employee portlet in second facet of panel splitter to see Department and Employee details side by side

We will notice that everytime we drop a portlet on a page, it creates an instance of portlet, which is referred as portletInstance in binding. Similar directory structure also get created in <application>\mds\oracle\adf\portlet\<Producer>\ap directory.

Step3: Run the page:

Friday, August 15, 2014

WebCenter: Content Presenter and Secured images

In WebCenter we use sitestudio and content presenter to show content from UCM. This blog could be useful for you if you find following behavior
1. Content presenter is not able to show images
2. You are able to see images sometime but not always
3. One browser shows images but other is not able to show
4. You are able to see images but your friend is not or vice versa.

Answer of all these problems lies in they way images are accessed using content presenter.

If we do F12 and see the url of image generated by content presenter, we found that its a direct URL to content server. For example

http://<server-name>:16200/cs/idcplg?IdcService=GET_FILE&dDocName=MY_Image&RevisionSelectionMethod=LatestReleased

This url is directly pointing to content server. Now content server can only give images if either of this is true
1. Image document is public. (Immediate solution but not feasible in for secured images)
2. You are explicitly logged into content server (may in another tab) and you have access to image document. (Cause which is responsible why we see images sometime)
3. You have SSO enabled so that your webcenter session can be used to access content server. (Best solution)

In above three cases you will be able to see image otherwise you would not.

Disclaimer: Any views or opinions presented in this blog are solely those of the author and do not necessarily represent those of the company.

Saturday, July 19, 2014

ADF Good (Best) Practice: Register bean in backingbeans cope

This blog is to highlight why we should register our beans in backing bean scope.

ADF supports following scopes: backingbean, request, view, pageFlow, session and application.

As a good practice we should register our beans in backingbean scope.

1. Why we should not use view, pageFlow, session, application: System needs to serialize any information in these scopes during fail over and also when user times out. Generally we bind UI components with a bean and these components are not serializable so if bean is registered in above scopes it will not get serialized and serialization will fail.


2. Why we should not use requestScope: Serialization is not a problem if bean is registered in request scope because system does not serialize any object that is present in request scope. That make sense also because anything that is in request scope should be lost in next request so what is the point to serialize that information. BUT still we should not register our beans in request scope. That is because if we use a task-flow on a page twice and task-flow has a request-scope bean, both instances of task-flow will start sharing same instance of bean. It can create a lot of confusion.


3. Why should we use backing-bean Scope: Backing bean scope is almost same a request scope in terms of life cyle so system need not to serialize any such bean. Also we get advantage that system create two instances of bean if we use task-flow twice on a page.


4. Is it good enough: Now question is "Can we meet all requirements by storing beans in backing-bean scope?". I would say most of the time YES, you can. Mostly we need to put certain INFORMATION on higher scopes (like pageFlow, session, application) and to store that we don't need to put bean in that scope. We can directly put INFORMATION (or value) in scope. For example we have a 4 page task-flow, which has employee search on first page and user can select an employee on first page. After selecting employee user can navigate to page 2, 3, 4. On page 4 we need to know selected employee record.
To achieve this one developer do following
   a. Bind employee table of first page on a page-flow bean
   b. Bind table with the bean.
   c. On page 4, get page-flow bean and then get table and then try to get selected employee row.

Other developer can follow this approach
   a. Bind employee table of first page on backingbean-scoped bean
   b. Bind table with that bean
   c. While navigating to page-2 get selected row from employee table and store its (employee-id or any other needed information) in page-flow scope. If you need complete employee information you can create employee-pojo (object) and set its attribute and then store employee object in page-flow
   d. Access employee information from page-flow scope.

[IF you are using bindings and on selection you have selection listener which makes employee row current, you can directly create binding of same vo on page-4. That could be even better]

We see mostly we need to information on different pages, so its better to store those information on needed scope rather than bean.


5. If you really want to store bean in higher scopes: If you really need bean in higher scopes atleast use ComponentReference.

http://myfaces.apache.org/trinidad/trinidad-api/apidocs/org/apache/myfaces/trinidad/util/ComponentReference.html



Disclaimer: Any views or opinions presented in this blog are solely those of the author and do not necessarily represent those of the company.

Sunday, March 2, 2014

Adding Custom search results in WebCenter Search

WebCenter provides a Search task-flow, which can be used to search in all or specific webcenter services. Common services supported by WebCenter are Announcement, Discussion, Events, Related Resources, Documents, Page, People Connection, Tags, Blogs etc. 

Webcenter global serach can by default perform search in above mentioned services.


Our aim here is to perform webcenter search in other places as well. For example we can have data in our custom table and we want that also to appear in webcenter search. For this example I will try to show data from Employee table in webcenter search.

 Here are the steps
1. Have employee table, populate data in that table.
2. Have ADF Model (EO/VO/AM) which can query Employee table.
3. Add bindings to a page
4. Register your service
5. Implement QueryManager and QueryExecuter classes.
6. Create a page EmployeeDetailPg.jspx which should show the detail of an employee.
7. Use webcenter provided Search task-flow to perform global search

============================================================
1. Have Employee table and populate data in that table: For this demo I am using HR.EMPLOYEES table.

2. Have ADF Model (EO/VO/AM) which can query employee table:
 
   a. VO should have a view criteria as 'SearchEmployee', which can search employee data based on first_name, middle_name, last_name, email, phone_number as




   b. Add this VO in AM as View Instance.


  c. Create EmployeeResourceRow class which represents every employee row in search.

    package com.san.model.search.employee;

import java.io.Serializable;

public class EmployeeResourceRow implements Serializable{
    private static final long serialVersionUID = 1090909090909L;
    private String resourceId;
    private String title;
    private String creator;
    private String lastModified;
    private int size;
    /**Constructor to create an instance of ResourceRow.
     * @param resourceId
     * @param title
     * @param creator
     * @param lastModified
     * @param size
     */
    public EmployeeResourceRow(String resourceId, String title,    String creator, String lastModified,    int size){
        this.resourceId = resourceId;
        this.title = title;
        this.creator = creator;
        this.lastModified = lastModified;
        this.size = size;
    }

    public void setResourceId(String resourceId) {
        this.resourceId = resourceId;
    }

    public String getResourceId() {
        return resourceId;
    }


   .... other getters/sertters

}

  d. Generate AMImpl class and add a method getSearchResult, which should query employeeVO and create EmployeeResourceRow based on each vo row.


    public List getSearchResult(String keyword){
       
      
        ViewObjectImpl vo = this.getEmployeeVO();
        if(keyword != null){
            vo.appendViewCriteria(vo.getViewCriteria("SearchEmployee"));
            vo.setNamedWhereClauseParam("bind_searchKeyword", keyword);
        }
        vo.executeQuery();

        List searchResultList = new ArrayList();
        while (vo.hasNext()) {
            Row resRow = vo.next();
            String resourceId = (String)resRow.getAttribute("EmployeeId").toString();
            String title = (String)resRow.getAttribute("FirstName");
            searchResultList.add(new EmployeeResourceRow(resourceId, title, null, null, 0));
        }
        return searchResultList;
    }


3. Add bindings (Drop AM method getSearchResult on any page's  page-definition file): We are going to use this page def files binding-container to execute AM method. For that purpose you can create a test page and add getSearchResult method on its Page-Def file. Page-Def will look something like

 <bindings>
    <methodAction id="getSearchResult"
                  InstanceName="HRAppModuleDataControl.dataProvider"
                  DataControl="HRAppModuleDataControl"
                  RequiresUpdateModel="true" Action="invokeMethod"
                  MethodName="getSearchResult" IsViewObjectMethod="false"
                  ReturnName="data.HRAppModuleDataControl.methodResults.getSearchResult_HRAppModuleDataControl_dataProvider_getSearchResult_result">
      <NamedData NDName="keyword" NDType="java.lang.String"/>
    </methodAction>
  </bindings>

Open databinding.cpx file and note down page-def entry.


 <pageMap>
        <page path="/oracle/webcenter/portalapp/pages/PortalSearchPg.jspx"
                    usageId="com_san_ui_PortalSearchPgPageDef"/>

</pageMap>
 <pageDefinitionUsages>
         <page id="com_san_ui_PortalSearchPgPageDef"
          path="oracle.webcenter.portalapp.pages.PortalSearchPgPageDef"/>

</pageDefinitionUsages>

usageid com_san_ui_PortalSearchPgPageDef will be used to execute AM method programatically.


4. Register your service:
To register a service in webcenter you need to add following entry in service-definition.xml file. This file is located under <application-folder>/.adf/META-INF
 <?xml version="1.0" encoding="UTF-8" ?>
<definitions xmlns="http://xmlns.oracle.com/webcenter/framework/service">

 <service-definition id="com.san.searchapp.employee" version="11.1.1.0.0">
   <resource-view urlRewriterClass="com.san.portal.search.employee.EmployeeDetailURL">
        <parameters>
            <parameter name="resourceId" value="{0}"/>
        </parameters>
    </resource-view>
        <!--resource-bundle-class>mycompany.myproduct.myapp.mycomponent.resource.ComponentMessageBundle</resource-bundle-class-->
    <name-key>Employee Search</name-key>
    <description-key>Employee Search</description-key>
    <!--icon>/mycompany/myproduct/myapp/mycomponent/component.png</icon-->
    <search-definition xmlns="http://xmlns.oracle.com/webcenter/search"
                       id="com.san.searchapp.employee.query" version="11.1.1.0.0">
      <query-manager-class>com.san.portal.search.employee.EmployeeQueryManager</query-manager-class>
    </search-definition>
  </service-definition>
</definitions> 

NOTE down we have mentioned a query-manager class com.san.portal.search.employee.EmployeeQueryManager. 
We need to implement this class and few other class to make it working. Whenever user performs a search, webcenter will look for all services registered in service-definition.xml file and if any query-manager-class is specified, it will use that class to start search operation for that service. 


5. Implement QueryManager and QueryExecuter classes: 
   a. Write a query manager class: Query manager class is a class which is specified while registering a service. This class should implement oracle.webcenter.search.QueryManager. QueryManager will force you to implement a method createRowQueryHandler. This is the method, which will be executed by webcenter framework when a user performs search.
      Here is the code.

package com.san.portal.search.employee;

import java.util.List;

import oracle.webcenter.search.QName;
import oracle.webcenter.search.Query;
import oracle.webcenter.search.QueryExecutor;
import oracle.webcenter.search.QueryManager;
import oracle.webcenter.search.Row;

public class EmployeeQueryManager  implements QueryManager{
    public EmployeeQueryManager() {
        super();
    }
   
    public QueryExecutor<Row> createRowQueryHandler(Query query,
    List<QName> columns)
    {
        return new EmployeeQueryExecutor(query, columns);
       
    }
}

b.  Query  Executor class:
QueryManager (EmployeeQueryManager) returns a QueryExecutor. We need to create a class EmployeeQueryExecutor which implements QueryExecutor. Class should look as

package com.san.portal.search.employee;
import com.san.model.search.employee.EmployeeResourceRow;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;

import java.util.Map;
import java.util.logging.Level;
import oracle.adf.model.BindingContext;
import oracle.adf.model.binding.DCBindingContainer;

import oracle.adf.share.logging.ADFLogger;
import oracle.jbo.uicli.binding.JUCtrlActionBinding;
import oracle.webcenter.framework.resource.ResourceRow;
import oracle.webcenter.search.ComplexPredicate;
import oracle.webcenter.search.QName;
import oracle.webcenter.search.Query;
import oracle.webcenter.search.QueryExecutor;
import oracle.webcenter.search.QueryHandler;
import oracle.webcenter.search.QueryResult;
import oracle.webcenter.search.Row;
import oracle.webcenter.search.TextPredicate;

public class EmployeeQueryExecutor implements QueryExecutor<Row>{
   private static final int PRESET_ESTIMATED_RESULT_COUNT = 20;
    private static final int PRESET_VALUES_BATCH_SIZE = 5;
    private static final int MAX_RESULT_LIMIT = 100;
    private int m_resultStart;
    private int m_resultLimit;
    private Query m_query;
    private List<QName> m_columns;
    private Map<String, String> m_properties;
    private static ADFLogger _logger =   ADFLogger.createADFLogger(EmployeeQueryExecutor.class);
   
    public EmployeeQueryExecutor(Query query, List<QName> columns)
    {
        m_query = query;
        m_columns = columns;
        m_properties = new HashMap<String, String>();
        m_properties.put(QueryHandler.TITLE, "Sample");
    }
   
   
  
    private void createRows(List<Row> rows, int batchRepeat)
    {
        Map param = new HashMap();        

        ComplexPredicate cp = (ComplexPredicate)m_query.getPredicate();
        TextPredicate tp = (TextPredicate)cp.getChildren().get(0);
        String searchQuery = tp.getTextQueryString();
        if(searchQuery == null)
            return;
        param.put("keyword",searchQuery);       
   
        BindingContext bc = BindingContext.getCurrent();
                DCBindingContainer dc =  bc.findBindingContainer("com_san_ui_PortalSearchPgPageDef");
                JUCtrlActionBinding lBinding =(JUCtrlActionBinding)dc.findCtrlBinding("getSearchResult");
                lBinding.getParamsMap().putAll(param);
                lBinding.invoke();
                List<EmployeeResourceRow> allResources = (List<EmployeeResourceRow>)lBinding.getResult();

               
       
        
        Iterator it = allResources.iterator();
       
        int i = 1;
        while(it.hasNext()){
            if(i > m_resultStart){
                EmployeeResourceRow resRow = (EmployeeResourceRow) it.next();
                String resourceId = (String)resRow.getResourceId();
                String title = (String)resRow.getTitle();
                String creator = (String)resRow.getCreator();
                String lastModifiedOn = (String)resRow.getLastModified();
                rows.add(new EmployeeRow(resourceId,title,creator,lastModifiedOn,null));
              
            }
            else{
                it.next();
            }
           
            i++;
              
           
        }   
    }
   
   
    public QueryResult<Row> execute()    {
        List<Row> rows = new ArrayList<Row>();
        int realLimit = Math.min(MAX_RESULT_LIMIT, m_resultLimit);
        
        createRows(rows, (realLimit / PRESET_VALUES_BATCH_SIZE));
        // Create the QueryResult
        QueryResult<Row> ret = new EmployeeQueryResult(rows.iterator());
        ret.getProperties().put(QueryResult.ESTIMATED_RESULT_COUNT,
                                new Integer(rows.size()).toString());
        return ret;
    }
   
    /**
    * Remember the result start.
    */
    public void setResultStart(int resultStart){
      m_resultStart = resultStart;
    }
    

    public void setResultLimit(int resultLimit){
        m_resultLimit = resultLimit;
    }
   
    public Map<String, String> getProperties(){
        return m_properties;
    }
}

Highlighted text shows how to execute a binding method using java code. I have shown executing a binding because this way we can execute anything for which we can create a binding. It might be AM method, a webservice call, a java method call etc etc.

c. Create EmployeeRow class which should implement Row.

package com.san.portal.search.employee;

import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;

import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.logging.Level;
import static oracle.webcenter.search.AttributeConstants.DCMI_CREATOR;
import static oracle.webcenter.search.AttributeConstants.DCMI_EXTENT;
import static oracle.webcenter.search.AttributeConstants.DCMI_IDENTIFIER;
import static oracle.webcenter.search.AttributeConstants.DCMI_MODIFIED;
import static oracle.webcenter.search.AttributeConstants.DCMI_TITLE;
import static oracle.webcenter.search.AttributeConstants.WPSAPI_ICON_PATH;

import oracle.adf.share.logging.ADFLogger;
import oracle.webcenter.search.QName;
import oracle.webcenter.search.Row;

public class EmployeeRow implements Row{
    private Map<QName, Object> m_storage = new HashMap<QName, Object>();
    private DateFormat m_dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
    private static ADFLogger _logger =   ADFLogger.createADFLogger(EmployeeRow.class);
   
    EmployeeRow(String id, String title,    String creator, String lastModified,    Long size)
    {
               m_storage.put(DCMI_IDENTIFIER, id);
        // Only if you have an external URL to go to
        // m_storage.put(DCMI_URI, "http://www.externalurl.com");
        m_storage.put(DCMI_TITLE, title);
        m_storage.put(DCMI_CREATOR, creator);
        // The below path points to a path accessible in the classpath to an icon
        m_storage.put(WPSAPI_ICON_PATH, "/adf/webcenter/search_qualifier.png"); //we can set human icon here
        m_storage.put(DCMI_EXTENT, size);
        try
        { 
            if(lastModified != null){
                Date date = m_dateFormat.parse(lastModified);
                Calendar cal = Calendar.getInstance();
                cal.setTime(date);
                m_storage.put(DCMI_MODIFIED, cal);
            }
          
        }
        catch (ParseException e)
        {
            e.printStackTrace();
           
        }
       
       
      
    }
    public Object getObject(QName columnName)
    {
            return m_storage.get(columnName);
    }
    public Iterator<QName> getColumns()
    {
            return m_storage.keySet().iterator();
    }
}

d. Create EmployeeQueryResult class which should extend WrapperQueryResult

package com.san.portal.search.employee;

import java.util.Iterator;

import oracle.webcenter.search.Row;
import oracle.webcenter.search.util.WrapperQueryResult;

public class EmployeeQueryResult extends WrapperQueryResult<Row> {
    public EmployeeQueryResult(Iterator<Row> rows)
    {    super(rows);
    }
}





e. Create a class EmployeeDetailURL which should implement ResourceUrlRewriter

package com.san.portal.search.employee;

import oracle.webcenter.framework.resource.ResourceUrlRewriter;

public class EmployeeDetailURL implements ResourceUrlRewriter{
    private String resourceId;
    private String resourceType;
    private String resourceTitle;
    public EmployeeDetailURL() {
        super();
    }

    public String rewriteUrl(String url) {
        return "/faces/oracle/webcenter/portalapp/pages/EmployeeDetailPg.jspx" + "?resourceId=" + url ;
    }

    public void setResourceId(String resourceId) {
        this.resourceId = resourceId;
    }

    public String getResourceId() {
        return resourceId;
    }

    public void setResourceType(String resourceType) {
        this.resourceType = resourceType;
    }

    public String getResourceType() {
        return resourceType;
    }

    public void setResourceTitle(String resourceTitle) {
        this.resourceTitle = resourceTitle;
    }

    public String getResourceTitle() {
        return resourceTitle;
    }
}



6. Create a page EmployeeDetailPg.jspx which should show the detail of an employee:
 When user performs a search, he will see a list of employees. From these employees user can select one to see more details. We have already specified our page EmployeeDetailPg.jspx  in class
EmployeeDetailURL.java. Now we need to create this jspx page. Its just a normal page. To get employee-id which it needs to show you can use expression #{param.resourceId}. There could be multiple ways to implement this page. 
   a. Develop a task-flow which takes employee-id as input and show details of that employee. Drop such task flow on this page and pass it #{param.resourceId} as employee-id
   b. Directly create a form on this page which shows detail of an employee from a VO. Add first binding executable as setCurrentRowWithKeyValue and pass value as #{param.resourceId}



7. User Search task-flow to perform global search
Add webcenter provided search task-flow on your page (or template) where you want to show query box.

Page entry:
<af:region value="#{bindings.searchtoolbar1.regionModel}" id="r1"/>
PageDef entry:
<taskFlow id="searchtoolbar1"
              taskFlowId="/oracle/webcenter/search/controller/taskflows/localToolbarSearch.xml#search-toolbar"
              activation="deferred"
              xmlns="http://xmlns.oracle.com/adf/controller/binding">
      <parameters>
        <parameter id="serviceIds" value="com.san.searchapp.employee"/>
      </parameters>
    </taskFlow>





NOTE: I have not tested above implementation with webcenter secured enterprise search enabled. If your implementation is not usig SES you need to make extra configuration in adf-config.xml file.

Comment out following line

 <!--crawl-properties fullCrawlInterval="P5D" enableWcServicesCrawl="true"
                      enableWcDiscussionsCrawl="true" enableWcUcmCrawl="true"/-->



Other Issues faced:


Error: 
<SearchServiceAPI> <execute> oracle.webcenter.concurrent.TimeoutException occurred during Search result transfer of service with identifier "com.san.searchapp.employee" with message "Execution timedout
            <SearchResultsBean> <collectResults> Execution of the search query did not complete within the time limit for executor com.san.searchapp.employee.

  Solution: 
Increase timeout <execution-properties timeoutMs="300000" prepareTimeoutMs="100000"/> in adf-config.xml



Disclaimer: Any views or opinions presented in this blog are solely those of the author and do not necessarily represent those of the company.