Friday 23 December 2016

How to integrate CKEditor into an Oracle JET page

First of all you need to setup up your main.js file, it should look like below.
It is assumed, that ckeditor is available in js/libs/ckeditor folder.

requirejs.config, paths:

'ckeditor': 'libs/ckeditor/ckeditor',
'ckeditor-jquery': 'libs/ckeditor/adapters/jquery'

shim:
                 'ckeditor-jquery': {
                                    deps: ['jquery', 'ckeditor']
                                }
in the require block add 'ckeditor-jquery';

in your html file add, if you add the same construct into a div element, it just does not show anything.
within textarea tag add

id="id1" data-bind="jqueryUI:{component: 'ckeditor', value: value, skin: 'moono-lisa', toolbar: 'basic', uiColor : '#9AB8F3'}"

To change the toolbar add the following within the curly brackets

To change the toolbars add the fo

toolbarGroups:  [
                                {name: 'basicstyles', groups: ['basicstyles', 'cleanup']},
                               
                            ]

And you should see






Friday 16 December 2016

How to format JSON date in Oracle JET

Let's say that you have a JSON date in the following format
"createdDate" : "2016-03-01T13:00:00-08:00"

and you need to show on your web page created with Oracle JET.
To format the above date to a human readable format use the following javascript function:

self.getFormattedDate = function (requestedDate) {
                 
                    var dateOptions = {formatStyle: 'date', dateFormat: 'long'};  
                    var dateConverter = oj.Validation.converterFactory("datetime").createConverter(dateOptions);
                    var convertedDate = oj.IntlConverterUtils.dateToLocalIso(new Date(requestedDate));

                    return dateConverter.format(convertedDate);
                };

On UI side call it as follows.

data-bind="text: getFormattedDate(createdDate)"





Sunday 6 November 2016

Fixing ORABPEL-02117 Variable already declared. A variable with key "XXXX" is already declared in the current scope.

A surprise error message occurs after the successful deploy of the BPM project. To resolve it look for duplicate definitions in the BPMN process files. In my case I had:

<ns5:Conversation id="CONVERSATION155142191537" type="service_call">
          <ns5:ServiceCallConversationDefinition service="Services.Externals.GetApplicationData"/>
         </ns5:Conversation>
         <ns5:Conversation id="CONVERSATION176785361049" type="service_call">
          <ns5:ServiceCallConversationDefinition service="Services.Externals.GetApplicationData"/>
         </ns5:Conversation>

Removed the first occurrence and updated Conversation id to the value of the second occurrence and redeployed. The error has been resolved!

Tuesday 31 May 2016

QuickTips: MultiSelect LOV and limited number of list members

If your multiselect list refuses to show all the values of your list, then you can go to the page bindings, click on the iterator and change "RangeSize" to a higher value (by default it is 25).

Saturday 21 May 2016

Button actionListener with an actionEvent.queue() issue

The other day, I faced an issue with the programmatic taskflow navigation. At first, it seemed to be a simple task. The purpose was to click a button on a page and navigate to the next step in the bounded taskflow.

The environment is MS Windows 10, JDeveloper is 12.2.1 (Build JDEVADF_MAIN_GENERIC_151011.0031.S).

The code for the button is:
af:button text="button 1" id="b1" action="save" actionListener="#{viewScope.bean.validate}"


The code for the actionListener is:
public void validate(ActionEvent actionEvent) {
        RichButton saveButton = (RichButton) actionEvent.getComponent();
        ActionEvent action = new ActionEvent(saveButton);
        System.out.println("The listener has fired ########### ");
        action.queue();
}

When the button is clicked, the actionListener fires continuously, so the user sees that the application is hang. The Oracle Support has opened a bug 23325363 : TASKFLOW NAVIGATION USING ACTIONLISTENER IS FIRED CONTINUOUSLY, which is being worked on a priority.


Update, which works OK, but the question still remains. "save" is the name of action.

public void validate(ActionEvent actionEvent) {
//RichButton saveButton = (RichButton) actionEvent.getComponent();
//ActionEvent action = new ActionEvent(saveButton);
//action.queue();

NavigationHandler nvHndlr =
FacesContext.getCurrentInstance().getApplication().getNavigationHandler();
nvHndlr.handleNavigation(FacesContext.getCurrentInstance(),
null,"save");
}

Monday 9 May 2016

Harry Potter and how to create MySQL fields which are auto incremented to use with ADF BC

MySQL has an auto_increment field, which can be used as a primary key. A sequence object when you think in Oracle database terms. But in the case of Oracle you can access to sequence values directly, but in the case of MySQL you don't have such a possibility.
If you want to have a field, which is auto incremented and still have the flexibility you can follow the steps below.

Create a table (counters) which stores a sequence name and last value.

CREATE TABLE `counters` (
  `counter_code` varchar(40) NOT NULL,
  `last_id` bigint(20) unsigned NOT NULL,
  `created_by` varchar(40) DEFAULT NULL,
  `creation_date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `last_updated_by` varchar(40) DEFAULT NULL,
  `last_updated_date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`counter_code`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

Create a procedure, which increases the sequence value and returns the value as an out parameter.
Note, that the procedure updates the counters table, this means that it has to issue a lock, which maybe the source of a bottleneck.

DELIMITER $$
CREATE DEFINER=`root`@`localhost` PROCEDURE `get_next_sequence_id`(in counterCode varchar(128), out lastId  bigint(20) unsigned)
BEGIN
DECLARE rowCount integer;

DECLARE CONTINUE HANDLER FOR NOT FOUND SET rowCount=0;
DECLARE EXIT HANDLER FOR SQLEXCEPTION ROLLBACK;
DECLARE EXIT HANDLER FOR SQLWARNING ROLLBACK;

SELECT
    COUNT(*)
INTO rowCount FROM
    counters
WHERE
    counter_code = counterCode;

if rowCount=0 then
START TRANSACTION;
INSERT INTO counters
(counter_code,last_id,created_by) VALUES(counterCode,1,'system');
COMMIT;
SET lastId =1;
else
START TRANSACTION;

UPDATE counters
SET
    last_id = (@cur_value:=last_id) + 1,
    last_updated_by = 'system'
WHERE
    counter_code = counterCode;
 
SELECT @cur_value + 1 INTO lastId;
   
   COMMIT;
end if;
END$$
DELIMITER ;

In your ADF Model Entity Object class create the following.

    @Override
    protected void create(AttributeList attributeList) {
        int nextVal = 0;
        super.create(attributeList);
        nextVal = getNextSequenceValue("NEWSEQ");
        setAttribute("ReportId", new BigDecimal(nextVal));
    }

    public int getNextSequenceValue(String sequenceNumber) {
        int nextVal = 0;
        DBTransactionImpl transaction = (DBTransactionImpl) getDBTransaction();
        CallableStatement statement = transaction.createCallableStatement("call get_next_sequence_id(?,?);", 0);
        try {
            statement.setString(1, sequenceNumber);
            statement.registerOutParameter(2, Types.INTEGER);
            statement.execute();
            nextVal = statement.getInt(2);
            statement.close();

        } catch (SQLException e) {
            throw new JboException(e);
        } finally {
            try {
                if (statement != null) {
                    statement.close();
                }
            } catch (SQLException e) {
                throw new JboException(e);
            }
        }
        return nextVal;
    }

Or you can create the same method in your AM and wire it to your attribute default value (choose "Expression") by the help of a groovy expression:adf.object.getDBTransaction().getRootApplicationModule().getNextSequenceValue("NEWSEQ")

Friday 6 May 2016

Troubleshooting read only selectOneChoice content alignment issue

While using an ALTA UI with JDeveloper 12c (12.2.1) I have noticed the following misbehavior.





As you can see, when selectOneChoice component is set to read only, the label and the content are misaligned.

This can be resolved by surrounding the component with panelLabelAndMessage component (set the label of panelLabelAndMessage to the label selectOneChoice and set simple the attribute of selectOneChoice to true (simple=true). But this approach is much time consuming, so this has to be addressed by skinning the component.

Add new skin into your project, to make troubleshooting easier add

   
        If this parameter is true, there will be an automatic check of the modification date of your JSPs, and saved state will be discarded when JSP's change. It will also automatically check if your skinning css files have changed without you having to restart the server. This makes development easier, but adds overhead. For this reason this parameter should be set to false when your application is deployed.
        org.apache.myfaces.trinidad.CHECK_FILE_MODIFICATION
        true
   
   
        No obfuscation of CSS.
        org.apache.myfaces.trinidad.DISABLE_CONTENT_COMPRESSION
        true
   

into your web.xml file. The description is here http://docs.oracle.com/cd/E21764_01/user.1111/e21420/adfsg_apply.htm#ADFSG312

The second parameter DISABLE_CONTENT_COMPRESSION is essential as if you do not set it, you won't see "human" readable tags while you troubleshoot the issue with FireBug or similar skin inspecting tools.

So when the page is rendered, you inspect the component under the question and you shall see the similar picture.








If you make changes to

.af_selectOneChoice.p_AFReadOnly .AFPanelFormLayoutContentCell {
    padding-left: 12px;
}

and add padding-top: 10px;

This will fix the issue, to make it a permanent fix add the following into your skin file.

af|selectOneChoice:read-only .AFPanelFormLayoutContentCell {
    padding-top: 8px;
}

And here is the result





P.S. Reset back CHECK_FILE_MODIFICATION and DISABLE_CONTENT_COMPRESSION to their default values when you deploy your application to production.




Sunday 24 April 2016

End BPM process instance on a given day of month

Use case:
We need to end (close) a process instance on a given day of month. To further complicate things let's assume that a day is not fixed. An admin should be able to change the end day without re-deploying the BPM project.

With 11gR1 (11.1.1.7) you can achieve the purpose by adding timer boundary event (make it the "Interrupting Event"), choosing a "Time Cycle" type and setting the boundary timer event to kick in, let's say every 12 hours. This does exactly what you want, but it kicks in every 12 hours and creates a task every 12 hours and closes it if the down the flow condition is not met.

Instead we can try another approach.

Set the timer type to "Time Cycle" and check the "Use Expression" attribute.

Write an expression, in my case it is:

oraext:query-database(concat('SELECT GET_CLOSURE_DURATION(', "'", bpmn:getDataObject('applicationDataDO')/ns:applicationId, "'" ,') NEXTDAY FROM dual'),false(),false(),'jdbc/DATASOURCE')

where get_closure_duration(applicationid in varchar2) is a function, which returns duration in the P1Y1DT1H1S - One year, one day, one hour and one second form. The function parameter applicationid is used to further fine tune the result.

Now we write the get_closure_duration function in a such a way, that it returns the duration. For demonstration purposes, I set the end date to be 7th day of the next month at 10 PM:

FUNCTION GET_CLOSURE_DURATION(
    APPLICATIONID VARCHAR2)
  RETURN VARCHAR2
IS
  NEXTDAY   NUMBER;
  NEXTDATE  DATE;
BEGIN
NEXTDAY:=7;
NEXTDATE:=TRUNC(LAST_DAY(SYSDATE) +NEXTDAY) + 22/24;

RETURN 'P'|| TRUNC(months_between(NEXTDATE, SYSDATE) /12) ||'Y'|| mod(TRUNC(months_between(NEXTDATE, SYSDATE)),
  12) ||'M'|| TRUNC(NEXTDATE-SYSDATE) || 'DT'|| TRUNC(mod((NEXTDATE-SYSDATE) *24, 24)) ||'H'|| TRUNC(mod((NEXTDATE  -  SYSDATE) *24*60, 60)) ||'M';

END GET_CLOSURE_DURATION;

E.g, to close the application on 07-MAY-16 at 22:00 the GET_CLOSURE_DURATION function gave P0Y0M6DT7H14M when run at 24-APR-16 14:45.


Friday 4 March 2016

JDeveloper is slow on Windows 10

Yeah, it is slow! The version of JDeveloper is Studio Edition Version 12.2.1.0.0, OS is Windows 10 Pro with 16 GB RAM, core i7 CPUs and still it is slow.
I have done all the possible tweaks, none helped so far.

The same works with no delays on Windows Server Standard (although not certified), no issues with the performance....

An update.
Finally, after I have changed some BIOS settings, namely disabled "Intel Speed Step" as well as "C-States Control" settings the JDeveloper now works at acceptable performance levels. I have to admit, that there still some random freezes, but this may be related to  the project itself.

Because of lack of time, I have not checked which setting out of two has made such a significant change.

Thursday 3 March 2016

JDeveloper 12c (12.2.1) and MySQL database

I have started a project where I plan to use MySQL (
5.7.11, community edition) as a data backend. I use JDeveloper 12c (12.2.1.0) to develop model, UI etc.
The first problem I faced is that JDeveloper does not correctly add associations (therefore, create viewlinks) between tables. This is described in this entry https://java.net/jira/browse/ADFEMG-142 but for earlier versions of JDeveloper and MySQL, but the issue is reproduced in JDeveloper 12c as well.
Unfortunately, the reported bug has no resolution. To resolve the issue, I had to manually create or adjust the associations and viewlinks.

Wednesday 20 January 2016

How to set maximum file upload size in BPM Human Task

To change the maximum upload size of a file, you need to make changes to web.xml file.
Here are the changes which need to be introduced.

.

Friday 15 January 2016

How to set current user in BPM tasks

To obtain current user in BPM tasks (especially when page fragments are being used) you can follow below approach.

1. First create the following java class, the current user's login is stored in HttpSession (this survives AM activation/passivation events).

package home.common.ext;

import javax.faces.context.ExternalContext;
import javax.faces.context.FacesContext;

import javax.servlet.http.HttpSession;

import oracle.bpel.services.workflow.WorkflowException;
import oracle.bpel.services.workflow.client.IWorkflowServiceClient;
import oracle.bpel.services.workflow.query.ITaskQueryService;
import oracle.bpel.services.workflow.verification.IWorkflowContext;
import oracle.bpel.services.workflow.worklist.adf.ADFWorklistBeanUtil;

import oracle.jbo.client.Configuration;


public class ContextUtils {
    public ContextUtils() {
        super();
    }

    public void initializeUserContextData(String applicationId) {
        FacesContext context = FacesContext.getCurrentInstance();
        ExternalContext ectx = context.getExternalContext();
        HttpSession userSession = (HttpSession)ectx.getSession(true);
        try {
            String ctx =
                (String)context.getApplication().evaluateExpressionGet(context,
                                                                       "#{pageFlowScope.bpmWorklistContext}",
                                                                       String.class);
            IWorkflowServiceClient workflowSvcClient =
                ADFWorklistBeanUtil.getWorkflowServiceClient();
            ITaskQueryService wfQueryService =
                workflowSvcClient.getTaskQueryService();
            IWorkflowContext wfContext;
            String userName = "";
            try {
                wfContext = wfQueryService.getWorkflowContext(ctx);
                userName = wfContext.getUser();
            } catch (WorkflowException e) {
                e.printStackTrace();
            }
            if (userName != null) {
                userSession.setAttribute("currentUserLogin", userName);
                System.out.println("Current user's login from workspace is: " +
                                   userName);
            }

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

2. Create data control from this class (Just right click on this class and choose "Create Data Control" from the context menu);

3. Add this method as a default activity into the Task UI project.

4. The value is also available in Model project, so you can populate your WHO columns with current user login.



How to update programmatically a BPM task acquired by field

To update a BPM task programmatically one can leverage BPM APIs. Here is a short example to update an acquired by field.
Find tasknumber, for this you need to issue a select query against WFTASK table.

E.g, let's find latest task for a given instance Id:

select taskNumber, acquiredBy from wftask
where compositeInstanceId=(instance Id)
order by taskNumber desc;

The java code itself. To have this working to you have to complete the prerequisite steps.
I forgot to mention, that this is tested with BPM 11.1.1.7.8.

package home.generic;

import java.util.HashMap;
import java.util.Map;

import oracle.bpel.services.workflow.StaleObjectException;
import oracle.bpel.services.workflow.WorkflowException;
import oracle.bpel.services.workflow.client.IWorkflowServiceClient;
import oracle.bpel.services.workflow.client.IWorkflowServiceClientConstants;
import oracle.bpel.services.workflow.client.WorkflowServiceClientFactory;
import oracle.bpel.services.workflow.query.ITaskQueryService;
import oracle.bpel.services.workflow.task.ITaskService;
import oracle.bpel.services.workflow.task.model.SystemAttributesType;
import oracle.bpel.services.workflow.task.model.Task;
import oracle.bpel.services.workflow.verification.IWorkflowContext;


public class UpdateTaskAssignees {
    public static void main(String[] args) throws WorkflowException,
                                                  StaleObjectException {
        String userid = "username";
        String password = "password";
        String serverUrl =
            "t3://localhost:8001"; // host:port of the soa server
        Map connProperties =
            new HashMap();
        connProperties.put(IWorkflowServiceClientConstants.CONNECTION_PROPERTY.CLIENT_TYPE,
                           WorkflowServiceClientFactory.REMOTE_CLIENT);
        connProperties.put(IWorkflowServiceClientConstants.CONNECTION_PROPERTY.EJB_PROVIDER_URL,
                           serverUrl);
        connProperties.put(IWorkflowServiceClientConstants.CONNECTION_PROPERTY.EJB_INITIAL_CONTEXT_FACTORY,
                           "weblogic.jndi.WLInitialContextFactory");
        IWorkflowServiceClient wfSvcClient =
            WorkflowServiceClientFactory.getWorkflowServiceClient(connProperties,
                                                                  null, null);
        IWorkflowContext ctx =
            wfSvcClient.getTaskQueryService().authenticate(userid,
                                                           password.toCharArray(),
                                                           "jazn.com");
        ITaskQueryService querySvc = wfSvcClient.getTaskQueryService();
        ITaskService taskSvc = wfSvcClient.getTaskService();
        // Get task by its tasknumber
        Task currentTask = querySvc.getTaskDetailsByNumber(ctx, 239120);
        SystemAttributesType types = currentTask.getSystemAttributes();
        System.out.println("Currently task is acquired by: " +
                           types.getAcquiredBy());
        types.setAcquiredBy("new_login");
        currentTask.setSystemAttributes(types);
        taskSvc.updateTask(ctx, currentTask);
    }
}