Thursday, October 27, 2011

login required before authorization can proceed

For no apparent reason whatsoever, connections got dropped in our Tomcat clustered environment running Blaze DS. The dreaded “Login required before authorization can proceed” message was popping up everywhere, requiring our users to reload the flex application and login again.
We never discovered why this happened and we might never will. However, we fabricated a solution that will rid our users’ experience of this dreaded popup message.
The solution involves a bit of dynamic method invocation and handling the actual fault string. 
The  solution
Every call to the RemoteObject is delegated to a class, which handles the execution. Included in the call is the actual ResultEvent handler function which you use to handle the results from the RemoteObject method call.
The delegate handles the call, and when the method results in a fault event, the faultString determines whether closing the ChannelSet and re-login is required.
In short the class handles the following:
  • ·         Executes the remote object method call using reflection
  • ·         Listeners via the ItemResponder mechanism for a fault event and a result event
  • ·         If the result event is triggered, the class’ work is done, and the delegated function is then handled
  • ·         However, when a fault is detected, the class faults each fault string other than the Login required message.
  • ·         The fault function disconnects the channels and reconnects again before the method is executed again.
public function getAddressses (contactId:int, handler:Function): void {
var  token:AsyncToken = remoteObject.getAddresses(contactId);
token.addResponder(
new ItemResponder(handler, faultHandler));
}
Follows this pattern now.
public function getAddresses (contactId:int, handler:Function): void {
                var  connector:RemoteConnecter = new RemoteConnecter(remoteObject);
                connector.method = “getAddresses”;
                connector.parameters = [ contactId];
                connection.handler = handler;
                connector.invoke();
}
This is the connector class that handles the call
package scripts
{
                import flash.utils.Proxy;
                import flash.utils.flash_proxy;
               
                import mx.collections.ItemResponder;
                import mx.messaging.ChannelSet;
                import mx.messaging.config.ServerConfig;
                import mx.rpc.AsyncToken;
                import mx.rpc.events.FaultEvent;
                import mx.rpc.events.ResultEvent;
                import mx.rpc.remoting.RemoteObject;
               

                public class RemoteConnecter
                {
                               
                                private var _remote:RemoteObject;
                                public var  method:String;
                                public  var parameters:Array;
                                public  var handler:Function;
                                private var _numberTries:int = 0;
                               
                                public function RemoteConnecter ( remote:RemoteObject)
                                {
                                                this._remote = remote;                                     
                               
                                }
                               
                                public  function invoke(): void {
                                                _numberTries++;

                                                var args:Array = [_method];
                                                if (_params != null && _params.length != 0) {
                                                                args["push"].apply(args, parameters);
                                                }
                                               
                                                var token:* =  Proxy(_remote).flash_proxy::callProperty.apply(_remote, args);
                                                AsyncToken(token).addResponder(new ItemResponder(handler, faultFunction));
                                }
                               
                                private function faultFunction(event:FaultEvent, token:Object = null): void {
                                                if (event.fault.faultString.indexOf("Login required before ") != -1 &&
                                                                _numberTries < 3 ) {
                                                               
                                                                var t:AsyncToken;
                                                               
                                                                var cs:ChannelSet = ServerConfig.getChannelSet( _remote.destination);
                                                                if (cs.connected) {
                                                                                t = cs.logout();
                                                                                t.addResponder(new ItemResponder(logoutHandler, faultFunction));
                                                                                return;
                                                                }
                                                               
                                                                t = cs.login(“loginname”,"retry");
                                                                t.addResponder(new ItemResponder(reloadHandler, faultFunction));
                                                               
                                                } else {
                                                                BBWAlert.error(event.fault.faultString);        
                                                }
                                               
                                }
                                private function logoutHandler(event:ResultEvent, token:Object = null) : void {
                                               
                                                var cs:ChannelSet = ServerConfig.getChannelSet( _remote.destination);
                                                               
                                                var t:AsyncToken = cs.login(“loginname”,"retry");
                                                t.addResponder(new ItemResponder(reloadHandler, faultFunction));
                                }
                               
                                private function reloadHandler(event:ResultEvent, token:Object = null) : void {
                                                invoke();
                                }
                               
}
}

Thursday, December 10, 2009

Date Sorting in DataGrid on Flex 1.5

Everybody who ever had data columns in a DataGrid in Flex 1.5 came across the combination of interesting sorting behavior when the column has a label function which formats the date to something like MM/DD/YYYY. I have tried various work arounds but the sortCompareFunction documentation is very clear:

“sortCompareFunction

sortCompareFunction: Function

Pointer to the callback function that gets called when the user click on a column header to sort the contents of the DataGrid. The underlying dataProvider will call this function multiple times in order to sort the elements of the dataProvider. If a labelFunction is defined, then the objects to be sorted are the return values of the labelFunction. The function signature of the callback function takes three parameters and should look like this:

mySortCompareFunction(obj1 : Object, obj2 : Object, columnIndex : Number) : Number

obj1 - a data element to compare
obj2 - another data element to compare with obj1
columnIndex - zero-based index of the column number that is being sorted

The function should return a value based on the comparison of the objects:

-1 if obj1 should appear before obj2 in the sorted sequence
0 if obj1 = obj2
1 if obj1 should appear after obj2 in the sorted sequence
The default value is undefined.”

There is no way around that when using a labelFunction.

Take for example this mxml application:

<?xml version="1.0" encoding="utf-8"?>

<mx:Application xmlns:mx="http://www.macromedia.com/2003/mxml" creationComplete="initApp()">

<mx:DateFormatter id="dateformat" formatString="MM/DD/YYYY"/>

<mx:Array id="items"/>

<mx:Script>

<![CDATA[

function initApp() {

items.push( {lbl:"First Item", dte:new Date(2009, 11, 6)});

items.push( {lbl:"Second Item", dte:new Date(2007, 5, 14)});

items.push( {lbl:"Third Item", dte:new Date(2008, 0, 9)});

items.push( {lbl:"Fourth Item", dte:new Date(2008, 8, 1)});

dgItems.dataProvider = items;

}

function displaylabel(item, col) {

return dateformat.format(item[col]);

}

]]>

</mx:Script>

<mx:DataGrid dataProvider="{items}" id="dgItems">

<mx:columns>

<mx:Array>

<mx:DataGridColumn columnName="lbl" headerText="Label"/>

<mx:DataGridColumn columnName="dte" headerText="Date"

labelFunction="displaylabel"/>

</mx:Array>

</mx:columns>

</mx:DataGrid>

</mx:Application>

If you try to sort this data grid by clicking on the Date header, the sorting is not performed correctly.

In order to circumvent this, I developed a little workaround. Basically the display in the DataGrid is handled by a cell renderer, and now the sorting can be done on the underlying Date objects.

Here is the cell renderer:

datesort.mxml

<?xml version="1.0" encoding="utf-8"?>

<mx:VBox xmlns:mx="http://www.macromedia.com/2003/mxml">

<mx:DateFormatter formatString="MM/DD/YYYY" id="dateformat"/>

<mx:Script>

<![CDATA[

import mx.controls.DataGrid;

var getCellIndex:Function;

var listOwner:DataGrid;

function setValue(str:String, item:Object, sel:String):Void {

if (item == undefined || item == null) return;

var col = listOwner.getColumnAt(getCellIndex().columnIndex).columnName;

datelabel.text = dateformat.format(item[col]);

}

]]>

</mx:Script>

<mx:Label id="datelabel" textAlign="center" width="100%"/>

</mx:VBox>

This cell renderer just takes a Date and displays this as a label.

Now we can build an application:

<?xml version="1.0" encoding="utf-8"?>

<mx:Application xmlns:mx="http://www.macromedia.com/2003/mxml"

creationComplete="initApp()"

>

<mx:Array id="items"/>

<mx:Script>

<![CDATA[

function initApp() {

items.push( {lbl:"First Item", dte:new Date(2009, 11, 6)});

items.push( {lbl:"Second Item", dte:new Date(2007, 5, 14)});

items.push( {lbl:"Third Item", dte:new Date(2008, 0, 9)});

items.push( {lbl:"Fourth Item", dte:new Date(2008, 8, 1)});

dgItems.dataProvider = items;

}

function sortdate(obj1:Object, obj2:Object, columnIndex:Number) : Number {

if (obj1.getTime() < obj2.getTime()) return -1;

if (obj1.getTime() == obj2.getTime() ) return 0;

if (obj1.getTime() > obj2.getTime() ) return 1;

return undefined;

}

]]>

</mx:Script>

<mx:DataGrid dataProvider="{items}" id="dgItems">

<mx:columns>

<mx:Array>

<mx:DataGridColumn

columnName="lbl"

headerText="Label"

/>

<mx:DataGridColumn

columnName="dte"

headerText="Date"

cellRenderer="datesort"

sortCompareFunction="sortdate"

/>

</mx:Array>

</mx:columns>

</mx:DataGrid>

</mx:Application>

Now the sorting on the date field is correct.

Friday, June 19, 2009

Loading Library without library path

I have been struggling lately with loading dll's which I need to make an application work. Setting the library path -Djava.library.path=some/path/to/dll was not really an option as our application is distributed via Webstart where all the resources need to be needly packaged in a couple of jars.

I decided to take a hard look at the System.load() command where you pass a string to the dll location. With a little bit of help from class loaders, this can work.

I stored a DLL in the package run.dll with the name of test.dll

The following code snippet will find the location of the resource:

private static final String SUFFIX = ".dll";
public static String loadDLL(String name) throws Exception {
//result variable
String result = null;

//package name can be loaded with '/' or '.'
if (name.startsWith( "/" ) ) { name = name.substring(1); }

//add the .dll to the end of the name
if (!name.endsWith(SUFFIX) { name = name.concat(SUFFIX); }

//replace '.' with '/'
name = name.replace('.', '/');

//use the default class loader
ClassLoader loader = ClassLoader.getSystemClassLoader();

//get the resource
java.net.URL = loader.getResource(name);

//the URl file format is /drive:/path/to/dll/dll.dll
//therefore, strip the first '/' and replace the remaining
//'/' with whatever file separator is applicable for
//your OS
result = url.getFile().substring(1).replace("/", File.separator);

//path to resource
return result;
}

This can now be launched as:
public static void main (String args[]) {
try {
//now we can load the dll using system.load();
system.load ( loadDLL("some.pkg.test.dll") ) ;
} catch (Exception e) {
e.printStackTrace();
}
}

Wednesday, April 9, 2008

Casting ArrayCollection items when using DataServices

Here's another thing I discovered along the way. When casting from an ArrayCollection in Flex 2.0 filled by the Live Cycle DataServices you cannot use the var item:Contact = Contact(collection[i]) syntax. You have to use var item:Contact = collection[i] as Contact in order to get it to cast the right object.

Here a limited example:

Flex object:
============
package object
{
[Managed]
[RemoteClass (alias="com.ds.object.Contact")]
public class Contact
{
public var id:Number;
public var firstName:String;
public var lastName:String;

public function Contact() {
super();
}
}

Java object:
============
package com.ds.object;

import java.io.Serializable;

public class Contact implements Serializable{

private long id;
private String firstName;
private String lastName;

public FlexContact() {
}

public long getId() {
return id;
}

public void setId(long id) {
this.id = id;
}

public String getFirstName() {
return firstName;
}

public void setFirstName(String firstName) {
this.firstName = firstName;
}

public String getLastName() {
return lastName;
}

public void setLastName(String lastName) {
this.lastName = lastName;
}

}

Assembler:
==========
public Collection<Contact> fill(List fillParameters) {
Collection<Contact> retValue = new ArrayList<Contact>();
//...fill list with Contact Objects

return retValue;
}


Mxml file:
==========
<?xml version="1.0" encoding="utf-8"?>
<mx:Application
xmlns:comp="global.mxml.CalendarItems.*"
xmlns:mx="
http://www.adobe.com/2006/mxml"
layout="vertical"
creationComplete="initApp()">

<mx:ArrayCollection id="collection"/>

<mx:Script>
<![CDATA[
import mx.event.ListEvent;
import mx.controls.DataGrid;

private function initApp(): void{
//dataservice object ds
ds.fill(collection);
}
private function doChange(event:ListEvent): void {
var dg:DataGrid = DataGrid(event.target);
/* do not cast this using Contact(dg.selectedItem)!!*/
var contact:Contact = dg.selectedItem as Contact;
}
]]>
</mx:Script>

<mx:DataGrid dataProvider="{collection}" change="doChange(event)">
<mx:columns>
<mx:DataGridColumn headerText="Contact Id" dataField="id"/>
<mx:DataGridColumn headerText="First Name" dataField="firstName"/>
<mx:DataGridColumn headerText="Last Name" dataField="lastName"/>
</mx:columns>
</mx:DataGrid>
</mx:Application>

Monday, April 7, 2008

ANY_HOST_CONFIGURATION error in booting

I spend a whole day trying to figure out why the Abobe LiveCycle
DataServices did not run on our Jboss 4.0.4.GA. I kept getting "Field
ANY_HOST_CONFIGURATION not found" as a ClassLoader error. It turned out
that the version of commons-httpclient.jar that ships with Jboss is an
older version that what Flex requires. As the older jar is located in
the class path of Jboss ([jboss]/server/default/lib), this older version
is used and not the jar that comes with Flex.

These steps makes the flex applications deploy:
1. Unpack flex.war from the LiveCycle DataServices
2. Stop Jboss
3. Delete commons-httpclient.jar from Jboss classpath
([jboss]/server/default/lib)
4. Copy commons-httpclient-3.0.1.jar to Jboss classpath
5. Restart Jboss
6. Deploy flex.war

Sunday, December 2, 2007

Installating JBoss with the Tanuki wrapper

We use Windows 2003 servers in our architecture so we make extensive use of the Tanuki wrapper. However, with all the options involved and with the all the quirk in running JBoss, it is always a challenge to configure the wrapper correctly.

Here are the steps we took to get JBoss running as a service in our Windows 2003 server:

  1. Create environment variable JBOSS_HOME and point this to the installation directory of JBoss
  2. Copy wrapper.exe from {wrapper_installer}\bin to {JBoss}\bin directory
  3. Copy wrapper.dll and wrapper.jar fro {wrapper_installer}\lib to {JBoss}\lib
  4. Copy App.bat.in, InstallApp-NT.bat.in and UninstallApp-NT.bat.in from {wrapper_installer}\src\bin to {JBOSS}\bin directory and remove the .in extensions
  5. Create directory {JBoss}\conf and copy wrapper.conf.in from {wrapper_installer}\src\conf to {JBoss}\conf; remove the .in extension.
  6. Make the wrapper.conf file look like this:

wrapper.java.command=%JAVA_HOME%/bin/java

wrapper.java.mainclass=org.tanukisoftware.wrapper.WrapperSimpleApp

wrapper.java.classpath.1=%JBOSS_HOME%/lib/wrapper.jar

wrapper.java.classpath.2=%JAVA_HOME%/lib/tools.jar

wrapper.java.classpath.3=./run.jar

wrapper.java.library.path.1=%JBOSS_HOME%/lib

wrapper.java.additional.1=-server

#parameters to start Jboss

wrapper.app.parameter.1=org.jboss.Main

#parameter to start default server

wrapper.app.parameter.2=-c default

# Initial Java Heap Size (in MB)

wrapper.java.initmemory=256

# Maximum Java Heap Size (in MB)

wrapper.java.maxmemory=1024

wrapper.logfile=%JBOSS_HOME%/logs/wrapper.log

# name of service as in net startstop JBoss

wrapper.ntservice.name=JBoss

# Name of server as it is displayed

wrapper.ntservice.displayname=JBoss Server

# Description of the service

wrapper.ntservice.description=JBoss 4.0.3 Server

# Mode in which the service is installed. AUTO_START or DEMAND_START

wrapper.ntservice.starttype=DEMAND_START

# Priority at which the service is run. NORMAL, LOW, HIGH, or REALTIME

wrapper.ntservice.process_priority=HIGH

# Allow the service to interact with the desktop.

wrapper.ntservice.interactive=false

Default the wrapper starts the default server in Jboss; we need to the the all server, because we are depending on the extra services from the all server. The wrapper.app.parameter.2 and ..3 accomplish this. This is equivalent to starting jboss with run –c all

  1. Install and start the server using wrapper.exe -i ../conf/wrapper.conf and wrapper.exe -t../conf/wrapper.conf

Monday, November 12, 2007

Apache James Mailet with JDBC Database connection

James is a wonderful mailserver. It works very well for what we are doing. But as with all open source java applications, it is not always very well documented. We needed some mailets which all use the JDBC connection pool as configured in the SAR-INF/config.xml.

I had to use the James source code to figure out how to use the connection pool as I am not very familiar with the Avalon framework. As always, it turned out to be not too difficult, but how to get there took some time.


import javax.mail.MessagingException;
import java.sql.*;
import org.apache.mailet.*;
import org.apache.avalon.cornerstone.services.datasources.DataSourceSelector;
import org.apache.avalon.excalibur.datasource.DataSourceComponent;
import org.apache.avalon.framework.service.ServiceManager;
import org.apache.james.Constants;
import org.apache.james.util.JDBCUtil;

public class ourMailet extends GenericMailet {
private static final String DATASOURCE = "maildb";
protected DataSourceComponent datasource = null;

protected final JDBCUtil jdbcUtil = new JDBCUtil() {
protected void delegateLog(String logString) {
log("JDBCourMailet: " + logString);
}
};

public ourMailet() { }
public String getMailetInfo() {
return "OurMailet version 1.0";
}

public void init() throws MessagingException {
if (datasource != null) return;

try {
ServiceManager componentManager =
(ServiceManager) getMailetContext()
.getAttribute(Constants.AVALON_COMPONENT_MANAGER);

DataSourceSelector datasources =
(DataSourceSelector) componentManager.lookup(DataSourceSelector.ROLE);

datasource = (DataSourceComponent) datasources.select(DATASOURCE);
}
catch (Exception e) {
throw new MessagingException("Error initializing OurMailet", e);
}
}
public void service(Mail mail) throws MessagingException {

Connection conn = null;
try {

conn = datasource.getConnection();
// do processing
}
catch (Exception e){
throw new MessagingException("Error initializing OurMailet", e);
}
finally {
jdbcUtil.closeJDBCConnection(conn);
}
}

}