Saturday, 16 January 2010

Using a NavigiationHandler to do HTTP/HTTPS switching

In a project I was working on, we wanted the log in page and anything to do with payment to be secure using HTTPS, whilst the majority of the site would be unsecure using HTTP. JSF 1.2 doesn't really give you anyway of doing this other than manually specifying the full url for every single link and button on the site. This removes a lot of the flexibility and simplicity of JSF. I came up with a pretty good solution. For any link or action that you know will switch between secure and unsecure, which isn't that hard to know (if you design the site with that in mind), then you configure it to be a redirect. Then you create your own navigation handler which applies rules you create yourself, to determine which parts should be secure or not.

This is a very simple solution that when a redirect occurs, it checks the path of the destination page (its viewId). If it starts with "/secure" it gives a full HTTPS url and for all others, it gives a full HTTP url. To activate it, have a navigation rule with a redirect. e.g. a login button with a result "userLogin":
<navigation-case>
 <from-outcome>userLogin</from-outcome>
 <to-view-id>/secure/login.xhtml</to-view-id>
 <redirect/>
</navigation-case>
<navigation-case>
 <from-outcome>home</from-outcome>
 <to-view-id>/index.xhtml</to-view-id>
 <redirect/>
</navigation-case>
Then on successful login return result "home" to redirect back to HTTP.

Next you'll need to create and configure your own navigation handler. The easiest way is to copy the entire contents of the mojarra NavigationHandlerImpl and change one line. In the handleNavigation function the line String newPath = viewHandler.getActionURL(context, caseStruct.viewId); is replaced as shown below:
public void handleNavigation(FacesContext context, String fromAction, String outcome) {

   // ...
   
   if (caseStruct != null) {
      ViewHandler viewHandler = Util.getViewHandler(context);
      assert (null != viewHandler);

      if (caseStruct.navCase.hasRedirect()) {
         // perform a 302 redirect.
         // ---- start change ---
         String newPath = prependSecure(context, viewHandler, caseStruct.viewId);
         // ---- end change -----
         
   //....
}
Download the full source here.

Below is the additional code to support to prependSecure function, that builds up a full url with scheme for the page being redirected to.
private static final String SCHEME_HTTP = "http";
private static final String SCHEME_HTTPS = "https";
private static final int HTTPS_DEFAULT_PORT = 443;
private static final int HTTP_DEFAULT_PORT = 80;
private static final String PREFIX_SECURE = "/secure/";

private String prependSecure(FacesContext context, ViewHandler viewHandler,
      String viewId) {
   boolean isSecure = (viewId != null && (viewId.startsWith(PREFIX_SECURE)));

   String prefix = getServerUrlForceScheme(context, isSecure);

   return prefix + viewHandler.getActionURL(context, viewId);
}

public static String getServerUrlForceScheme(FacesContext context,
      boolean secure) {
   String scheme;
   int defaultPort;
   if (secure) {
      scheme = SCHEME_HTTPS;
      defaultPort = HTTPS_DEFAULT_PORT;
   } else {
      scheme = SCHEME_HTTP;
      defaultPort = HTTP_DEFAULT_PORT;
   }

   HttpServletRequest request = (HttpServletRequest) context
         .getExternalContext().getRequest();

   int port;
   //use current port if known (ideally make this configuration driven)
   if ((!SCHEME_HTTPS.equalsIgnoreCase(request.getScheme()) && !secure)
         || (!SCHEME_HTTP.equalsIgnoreCase(request.getScheme()) && secure))
      port = request.getServerPort();
   else
      port = defaultPort;

   String basePath = scheme + "://" + request.getServerName()
         + (defaultPort == port ? "" : ":" + port);

   return basePath;
}

Tuesday, 12 January 2010

Updating the nextval of all sequences in PostgreSQL

If you've been doing SQL level inserts for testing purposes, then when you go to use the sequences then they will have a nextval that already exists in your table.

So below is a script which updates the nextval for all fields that were created using bigserial/serial:
/* Updates all the sequences to have a next value of max+1 excluding the list passed */
CREATE OR REPLACE FUNCTION fn_fixsequences(excludes text) RETURNS integer AS
$BODY$
DECLARE
themax BIGINT;
mytables RECORD;
num integer;
BEGIN
 num := 0;
 FOR mytables IN 
  select relname, ns.nspname, a.attname, pg_get_serial_sequence(c.relname, a.attname) as seq
  FROM pg_catalog.pg_attribute a INNER JOIN 
   pg_catalog.pg_class c ON c.oid=a.attrelid
    inner join pg_catalog.pg_attrdef d
    on d.adrelid = a.attrelid AND d.adnum = a.attnum AND a.atthasdef
       LEFT JOIN pg_catalog.pg_namespace ns ON ns.oid = c.relnamespace
  WHERE 
    pg_catalog.pg_table_is_visible(c.oid)
   and a.attnum > 0 AND NOT a.attisdropped  and atttypid=20
   and relname not like 'pg_%' and relname not like excludes
   and ns.nspname not in ('information_schema', 'pg_catalog')
   and c.relkind='r'
   and not pg_get_serial_sequence(c.relname, a.attname) is null
 LOOP
      EXECUTE 'SELECT MAX('||mytables.attname||') FROM '||mytables.nspname||'.'||mytables.relname||';' INTO themax;
      IF (themax is null OR themax < 0) THEN
       themax := 0;
      END IF;
      themax := themax +1;
      EXECUTE 'ALTER SEQUENCE ' || mytables.seq || ' RESTART WITH '||themax;
      num := num + 1;
  END LOOP;
 
  RETURN num;
 
END;
$BODY$
LANGUAGE 'plpgsql' VOLATILE;
COMMENT ON FUNCTION fn_fixsequences() IS 'Updates all the sequences to have a next value of max+1 excluding the list passed';

Saturday, 9 January 2010

Getting JBoss Scheduler working

When using the @Service annotation together with the scheduler has given me no end of trouble so I'd like to share my working configuration.

It took me a while to get the objectName right. If your not using @Service(objectName='name') but instead using @Service(name='name') then the final object name is not the same as what they imply at EJB Extensions Help.
Instead I'd recommend going into the JMX console of the running server and searching for it manually, listed under 'jboss.j2ee' for the name you've specified AND type=ManagementInterface.

As well as defining the @Service, you'll also need to define the @Management.

@Management(SampleServiceMBean.class)
@Service(name = SampleServiceMBean.SERVICE_NAME)
public class SampleServiceMBean implements SampleService
{
 public final static String SERVICE_NAME = "SampleServiceMBean";
 
 public void process(ObjectName name, Date date)
 {
  log.info("process {} {}", name, date);
 }

Then create a file the appropriate place
e.g. MyEar.ear/MyEjb.jar/META-INF/myscheduler-service.xml:

<?xml version="1.0" encoding="UTF-8"?>
<server>
 <mbean code="org.jboss.varia.scheduler.Scheduler" name=":service=MyScheduler">
  <attribute name="StartAtStartup">true</attribute>
  <attribute name="SchedulableMBean">jboss.j2ee:ear=MyEar.ear,jar=MyEjb.jar,name=SampleServiceMBean,service=EJB3,type=ManagementInterface</attribute>
  <attribute name="SchedulableMBeanMethod">process( SCHEDULER_NAME, DATE )</attribute>
  <attribute name="InitialStartDate">NOW</attribute>
  <attribute name="SchedulePeriod">60000</attribute>
  <attribute name="InitialRepetitions">10</attribute>
  <attribute name="FixedRate">true</attribute>
    <depends>
      <mbean code="javax.management.timer.Timer" name="jboss:service=Timer"/>
    </depends>
 </mbean>
</server> 
The parameter list threw me off for a while. If you get errors like java.lang.IllegalArgumentException: Unable to find operation processPending(javax.management.ObjectName,java.util.Date), the the key is the argument list (obviously).

Good luck.

Thursday, 7 January 2010

Disabling RichFaces FacesBeanValidator

If you use the rich:ajaxValidator, for example:
<rich:ajaxValidator event="onblur" />
It automatically adds a FacesBeanValidator to the components list of validators. If you don't have javax.validation or Hibernate Validator setup then you'll get the following exception:
2010-01-07 00:29:19,889 [http-8443-1] WARN  org.richfaces.validator.ObjectValidator - Hibernate Validator could not be instantiated, use stub instead
javax.validation.ValidationException: Unable to find a default provider
 at javax.validation.Validation$GenericBootstrapImpl.configure(Validation.java:248)
 at javax.validation.Validation.buildDefaultValidatorFactory(Validation.java:115)
 at org.richfaces.validator.BeanValidator.(BeanValidator.java:34)
 at org.richfaces.validator.ObjectValidator.createInstance(ObjectValidator.java:54)
 at org.richfaces.validator.ObjectValidator.getInstance(ObjectValidator.java:85)
 at org.richfaces.validator.FacesBeanValidator.validate(FacesBeanValidator.java:103)
 at org.richfaces.component.html.HtmlInputText.validateValue(HtmlInputText.java:52)

The way I'd like to be able to do it is:
<managed-bean>
  <description>
   Override RichFaces dependance on hibernate validator and validation api. This stops boot up exceptions (errors once).
  </description>
  <managed-bean-name>org.richfaces.validator.HibernateValidator</managed-bean-name>
  <managed-bean-class>org.richfaces.validator.NullValidator</managed-bean-class>
  <managed-bean-scope>application</managed-bean-scope>
 </managed-bean>
However, unless the bean is accessed by an EL expression like #{applicationScope['org.richfaces.validator.HibernateValidator']}, then it won't be instantiated and added to the application map.

So the only other solution is to set it with code in a ContextListener:
public void contextInitialized(ServletContextEvent event) {
  event.getServletContext().setAttribute(
    org.richfaces.validator.ObjectValidator.VALIDATOR_PARAM, 
    new org.richfaces.validator.NullValidator());
}
A bit of work just to hide an exception... And all this because I was chasing a red herring.