Thursday, February 27, 2014

Prevent copy/cut/paste in input fields

one of the requirements  we had for a certain application I've been working on was to prevent users from copying/pasting values from/into an input text field.
In this blog entry, I will show how we achieved the requested behavior.

basically, you can achieve the requested behavior using JavaScript.
include the following code snippet in your page:

<af:resource type="javascript">
          function eventCancel(evt) {
             AdfPage.PAGE.clearMessages(evt.getSource()._clientId);
              AdfPage.PAGE.addMessage(evt.getSource()._clientId, new AdfFacesMessage(AdfFacesMessage.TYPE_ERROR, "Error" , "context menu is disabled for this field" )) ;
          }

          function cancelCopy(evt) {
              if (((evt.getKeyCode() == AdfKeyStroke.C_KEY)||(evt.getKeyCode() == AdfKeyStroke.X_KEY))&&(evt.getKeyModifiers() & AdfKeyStroke.CTRL_MASK) > 0) {
                 AdfPage.PAGE.clearMessages(evt.getSource()._clientId);
                  AdfPage.PAGE.addMessage(evt.getSource()._clientId, new AdfFacesMessage(AdfFacesMessage.TYPE_ERROR, "Error" , "sorry cannot cut/copy" ));
                  evt.cancel();
              }
              if ((evt.getKeyCode() == AdfKeyStroke.V_KEY)&& (evt.getKeyModifiers() & AdfKeyStroke.CTRL_MASK) > 0) {
                    AdfPage.PAGE.clearMessages(evt.getSource()._clientId);
                  AdfPage.PAGE.addMessage(evt.getSource()._clientId, new AdfFacesMessage(AdfFacesMessage.TYPE_ERROR, "Error" , "sorry cannot paste" ));
                  evt.cancel();
              }
          }
        </af:resource>

then modify your input text field as shown below:
<af:inputText label="Label 1" id="it1" clientComponent="true">
          <af:clientListener type="keyDown" method="cancelCopy"/>
          <af:clientListener type="contextMenu" method="eventCancel"/>
        </af:inputText>

the above shows two client listeners:
1- is called on key down: which calls a function that test to see what the pressed key is and if the pressed key pressed is (X,C,V) and the control key is pressed, then  the edit operation is cancelled and an alert message is shown.
2- when the user right click on the field, the context menu event is cancelled, so no context menu will be shown, thus the user won't be able to use the edit operations.

Edited on 03/03/2014:
sorry, but it seems that the property ._clientId is an internal property of the javascript variable that should not be used by the developers. so, please use: evt.getSource().getId() instead.

Friday, February 14, 2014

Logging DML statements

For a while now, I've been wondering is it possible to know the generated DML statements and their parameter values; since this might be helpful sometimes. And finally I have managed to do this, I will list the steps below.

steps:
1) generate a java class for the targeted entities or use a base entity class.
2) override the buildDMLStatement method and your custom logic, similar to:
protected StringBuffer buildDMLStatement(int i , AttributeDefImpl[] attributeDefImpl ,
                                             AttributeDefImpl[] attributeDefImpl2 ,
                                             AttributeDefImpl[] attributeDefImpl3 , boolean b) {
        StringBuffer buffer =
            super.buildDMLStatement(i , attributeDefImpl , attributeDefImpl2 , attributeDefImpl3 , b) ;
        logger.info("created dml statement  (for object:" + getEntityDef().getName() + getKey() + "):" +
                    buffer.toString()) ;

        return buffer ;
    }

3)override the bindDMLStatement method and add your custom logic, similar to:
protected int bindDMLStatement(int i , PreparedStatement preparedStatement , AttributeDefImpl[] attributeDefImpl ,
                                   AttributeDefImpl[] attributeDefImpl2 , AttributeDefImpl[] attributeDefImpl3 ,
                                   HashMap hashMap , boolean b) throws SQLException {
        String[] attrs = getAttributeNames() ;
        for (String attrName : attrs) {
            if (isAttributeChanged(getAttributeIndexOf(attrName))) {
                logger.info("attribute[" + attrName + "] changed, " + "postedValue:" +
                            getAttribute(getAttributeIndexOf(attrName) , ORIGINAL_VERSION) + ", newValue:" +
                            getAttribute(attrName)) ;
            }
        }
        return super.bindDMLStatement(i , preparedStatement , attributeDefImpl , attributeDefImpl2 ,
                                      attributeDefImpl3 , hashMap , b) ;
    }


You are done now. using the above code- in the base entity class- will log all generated DML statements.

Full class looks like:
package com.ahmad.model ;

import java.sql.PreparedStatement ;
import java.sql.SQLException ;

import java.util.HashMap ;

import java.util.logging.Logger ;

import oracle.jbo.server.AttributeDefImpl ;
import oracle.jbo.server.EntityImpl ;



public class BaseEntity extends EntityImpl {
    public BaseEntity() {
        super() ;
    }
    Logger logger = Logger.getLogger(BaseEntity.class.getName()) ;

    protected int bindDMLStatement(int i , PreparedStatement preparedStatement , AttributeDefImpl[] attributeDefImpl ,
                                   AttributeDefImpl[] attributeDefImpl2 , AttributeDefImpl[] attributeDefImpl3 ,
                                   HashMap hashMap , boolean b) throws SQLException {
        String[] attrs = getAttributeNames() ;
        for (String attrName : attrs) {
            if (isAttributeChanged(getAttributeIndexOf(attrName))) {
                logger.info("attribute[" + attrName + "] changed, " + "postedValue:" +
                            getAttribute(getAttributeIndexOf(attrName) , ORIGINAL_VERSION) + ", newValue:" +
                            getAttribute(attrName)) ;
            }
        }
        return super.bindDMLStatement(i , preparedStatement , attributeDefImpl , attributeDefImpl2 ,
                                      attributeDefImpl3 , hashMap , b) ;
    }

    protected StringBuffer buildDMLStatement(int i , AttributeDefImpl[] attributeDefImpl ,
                                             AttributeDefImpl[] attributeDefImpl2 ,
                                             AttributeDefImpl[] attributeDefImpl3 , boolean b) {
        StringBuffer buffer =
            super.buildDMLStatement(i , attributeDefImpl , attributeDefImpl2 , attributeDefImpl3 , b) ;
        logger.info("created dml statement  (for object:" + getEntityDef().getName() + getKey() + "):" +
                    buffer.toString()) ;

        return buffer ;
    }

    protected boolean isAttributeChanged(int i) { //it seems this method is used to determine if the attribute is chnaged or not, so that it will be included in the generated dml
        return super.isAttributeChanged(i) ;
    }

}



Update on: 17 February,2014:
found out that you can achieve a similar result by using your own class that extends DBTransactionImpl2 class and overriding the createPreparedStatement method to log the passed string. But if you want to have more control about which Entities' DMLs you want to log, then use the first approach.

Thursday, February 13, 2014

Backing Bean - auto bind

in JSF you can choose to bind the JSF page components to java objects in a backing bean, so that you can access the components programatically.
in JDeveloper 11g Release 2, you can choose to set a backing bean that will be used as the backing bean for a JSF page and any components added to the page will be added to the bean automatically.

steps:
  1. open the page.
  2. from the toolbar menu open the design menu.
  3. click page properties.
  4. in the page properties dialog window, switch to the component binding tab.
in the opened tab you can:
  1. switch on/off auto binding.
  2. choose the bean to be used as the component binding.