Tuesday, January 19, 2010

Retrieving the current user from Acegi

The Grails Acegi plugin automatically puts the AuthenticateService into the Spring beans. This can be accessed in any controller simply by adding the member variable "authenticateService"

class MyController {
def authenticateService
...
}

The current user is retrieved with:
authenticateService.principal()

This returns an instance of:
org.codehaus.groovy.grails.plugins.springsecurity.GrailsUserImpl

but of course we handle it dynamically:
def principal = authenticateService.principal()


The username can be retrieved by:
authenticateService.principal()?.getUsername()


Note, the "?" operator to prevent NullPointerException. The principal() is null when no one is logged in. You can always secure the page to prevent that but I usually develop unsecured and then add authentication before testing.

-Ben Hidalgo

Thursday, January 14, 2010

Grails Multiple Select Tag

Though it would seem like a feature available right out of the box, having a tag that allows a user to select multiple options in a select box is in fact, a custom tag.

The src code for the tag can be found here. just copy the src into a file named MultiselectTagLib.groovy in the taglib dir.

Directions for use, can be found here

As an example, suppose we had a Statement class which contains multiple payments

class Statement {
String name
Date stmtDate

static hasMany = [payments:Payment]

static constraints = {
payments(nullable:false)
}
}

class Payment {
Double amt
Date txnDate

static constraints = {
amt(nullable:false)
txnDate(nullable:false)
}
}


then the gsp tag to select payments for a statement would look like

<g:select optionKey="id" optionValue="amt" from="${Payment.list()}" name="payments"
value="${statementInstance.payments}" multiple="multiple" />

Wednesday, January 13, 2010

Using Acegi tags in GSP

Using the Acegi security tags is very simple and described nicely here

The tags are basically:
<g:isNotLoggedIn>
...
</g:isNotLoggedIn>

<g:isLoggedIn>
...
</g:isLoggedIn>

<g:ifAnyGranted role="ROLE_ADMINISTRATOR,ROLE_VIEWER">
...
</g:ifAnyGranted>


To place a login link use:
<g:isNotLoggedIn>
<div style="margin-left:20px;">
<g:link controller="login" action="auth">Click Here to Login</g:link>
</div>
</g:isNotLoggedIn>
Here's a trick, if you're using views instead of tables to define your roles (for tables, use Bootstrap.groovy) you can fake results from a remote system like this:

create view role_vw as
select 1111 as ID,
'ROLE_ADMINISTRATOR' AS AUTHORITY,
0 as VERSION,
'Admin' as DESCRIPTION
from dual
union
select 2222 as ID,
'ROLE_VIEWER' AS AUTHORITY,
0 as VERSION,
'Viewer' as DESCRIPTION
from dual
And follow a similar trick for the Person and PersonRole views.

I found an annoying quirk when attempting to use constants in the @Secured annotation.
@Secured(['ROLE_ADMINISTRATOR']) works fine but @Secured([RoleNames.ADMIN]) where RoleNames.ADMIN is a public static final constant in a class or interface doesn't work. This is tedious because I'll have to search and replace the role names if the product owner changes them. The GSPs also have the roles hardcoded, so not a big deal.

-Ben Hidalgo

Tuesday, January 12, 2010

Apache JAMES

Today I installed Apache JAMES to use as our development mail server. I've found this to be easy to use and setup whenever I need a quick mail server. When using e-mail that requires proper formatting, the default "localhost" servername will not work, so to solve this problem, I added
localhost.com
to \apache-james-2.3.2\apps\james\SAR-INF\config.xml

<servernames autodetect="true" autodetectip="true">
<servername>localhost</servername>
<servername>localhost.com</servername>
</servernames>

An easy fix for what can sometimes be an annoying problem.


-Brandy Brewster

Piggyback Acegi on external user/role management

For a variety of reasons, we need to store our users and roles in a different database schema and structure than comes with Grails... but we definitely want to use the Acegi plugin. Also, there is no LDAP or SSO so the goal was to make Role and Person use tables in another schema in the same database (managed by a different web app.)

The approach is to map the Role, Person and RolePerson (implicit) entities to views. Using a view to select from the other tables, makes one-way synchronization trivial.

1. Create a view called ROLE_VW which selects from the roles table and maps the columns to what Role.groovy expects. I appended 'ROLE_' to the Authority so that Acegi will respect it as a role. The actual role names in the other schema don't include it and are named like: "Administrator." I also did an "upper" just to follow an all-caps convention in the annotation based @Secured in the controllers. "Participant" is the table name in the other schema (from the existing app with user mgmt capability).

create view role_vw as
select OID as ID,
'ROLE_'||upper(id) AS AUTHORITY,
0 as VERSION,
id as DESCRIPTION
from otherapp.participant
/

create view people_vw as
select
oid as id,
account as USERNAME,
firstname||' '||lastname as USER_REAL_NAME,
password as PASSWD,
email as EMAIL,
1 as EMAIL_SHOW,
1 as ENABLED,
0 as VERSION,
description as DESCRIPTION
from otherapp.workflowuser
/

create view people_to_role_vw as
select ID as ROLE_ID,
up.WORKFLOWUSER AS PERSON_ID
from otherapp.user_participant up, otherapp.participant p
where up.participant = p.oid
/

Then update the groovy classes to use these:

Role.groovy:
static mapping = {
// mapping to a view because info is maintained in a separate schema
table 'role_vw'
people joinTable:[name:'PEOPLE_TO_ROLE_VW']
}
Person.groovy
static mapping = {
// mapping to a view because info is maintained in a separate schema
table 'people_vw'
authorities joinTable:[name:'PEOPLE_TO_ROLE_VW']
}

Lastly, the passwords stored by the other app are unencrypted. Therefore, to disable encryption place this snippet in the ./conf/spring/resources.groovy:

beans = {
passwordEncoder (org.springframework.security.providers.encoding.PlaintextPasswordEncoder)
}
-Ben Hidalgo

GrailsUI Tab Layout

Using the tab view layout in grails is pretty simple

First, install the GrailsUI plugin

grails install-plugin grails-ui


Second, include the gui resource in your gsp file by adding the following within the head of your document. There are several components available in the plug-in, we're just including one here.

<gui:resources components="['tabView']"/> 


Finally, use the tags in your gsp. Note that the tags need to be used with a particular css style - something that, bafflingly, needs to be done manually.

<div class="yui-skin-sam">
<gui:tabView>
<gui:tab label="Default Tab" active="true">
<g:render template="myTemplate" bean="${myBeanInstance}" />
</gui:tab>
<gui:tab label="Tab 2" controller="myController" action="foo">
</gui:tab>
</gui:tabView>
</div>



The first tab is the default tab, as indicated by the active attribute. The contents for the tab are contained within the template myTemplate.

The second tab is making a server call, and will display the template returned by the foo action on the controller myController.

Monday, January 11, 2010

Logout from Acegi

To logout from Acegi, simply GET the following url: my-context-root/logout

This link gets it done:
<g:link action="logout" controller=".">Logout</g:link>


I added some inline style to move it to the right side of the screen:
<div style="text-align: right; padding: 1px 20px 0px 0px;">
<g:link action="logout" controller=".">Logout</g:link>
</div>


Placing the snippet into .\views\layouts\main.gsp will make the Logout link appear on every page.

-Ben Hidalgo

Friday, January 8, 2010

Unit tests for processes

I added a Helper class containing IPP query methods that are helpful for automated process testing. It provides methods to search for process and activity instances, complete activities, check the state of processes or activities...
For example:
import static ag.carnot.workflow.query.ProcessInstanceQuery.ROOT_PROCESS_INSTANCE_OID;

...
    /**
* Gets all process instances for ProcessOID of a process
*
* @param processId
* in model
* @param rootPoid
* root process instance oid
* @return all process instances matching process ID and root process OID
*/

@SuppressWarnings("unchecked")
public static final List<ProcessInstance> findAllProcessInstancesForProcessIdAndRootPoid(
String processId, long rootPoid) {
ProcessInstanceQuery query = ProcessInstanceQuery.findForProcess(processId);
query.where(ROOT_PROCESS_INSTANCE_OID.isEqual(rootPoid));
List<ProcessInstance> allProcessInstances = queryService.getAllProcessInstances(query);
return allProcessInstances;
}
I also added a sample test, which is at the moment only starting the Emergency Voting process and retrieves all started "Vote" subprocesses. To get the test running, you have to make sure, that the $JBOSS_HOME\client\jbossall-client.jar is on the classpath.
 public static void testEmergencyVoting() throws InterruptedException {
long votingProcessOID = startEmergencyVotingProcess();
//Wait for Subprocesses to be started
Thread.sleep(ProcessHelper.PROCESS_CHECK_RETRY_DELAY);
List<ProcessInstance> processInstances = ProcessHelper
.findAllProcessInstancesForProcessIdAndRootPoid(VOTING_PID, votingProcessOID);
LOG.info("Found "+ processInstances.size()+" Instances of Vote Subprocesses");
//TODO: real testcase to be implemented
}

-Larissa Werlein-

Custom Constraints with Grails

While Grails provides a very robust validation mechanism for domain objects, we encountered an issue where the required-ness of certain fields are dependent on the value of another field in the same object.

Enter the grails constraints plugin -- this allows you to create a custom constraint that is reusable in your application.

First install the plugin by executing the command
grails install-plugin constraints


Second, create a groovy file in the grails-app\utils folder with a name ending with 'Constraint.' This file should contain a closure named validate that returns a boolean value.

The closure has three parameters passed to it:
1. the value of the field on which the constraint was placed (val)
2. the instance of the object being validated (target)
3. a collection of validation errors (not used in example)

In addition, it implicitly has access to parameters passed to the constraint via the params property.

class CreditCardNumberRequiredConstraint {
static name = "ccReq"

def validate = { val, target ->
// find the payment method type value on the object
def paymentMethod
if (target.metaClass.hasMetaProperty("paymentMethod")) {
paymentMethod = target.paymentMethod
}

// cc number required, check if the value is populated
if (params.vals.contains(paymentMethod)) {
return val
}
// payment method type does not require cc number
else {
return true
}
}
}


Third, use the new constraint in your domain class. The default name is the camel-cased name of your groovy file, minus Constraint (i.e. - creditCardNumberRequired). This example has over-ridden the default by adding the static name line.
class Payment {
String name
String paymentMethod
String creditCardNumber

static constraints = {
name(nullable:false)
paymentMethod(nullable:false, inList:['AMEX', 'MasterCard', 'Check'])
creditCardNumber(ccReq:[vals:['AMEX', 'Mastercard']])
}
}

Finally, add an entry to the properties files under grails-app\i18n. The key for the entry should be of the format:

default.invalid.% camel-coded-constraint-name %.message

Using the sysconsole tool with ant

You can easily call all IPP-sysconsole commands as an ant script. For example drop/create the IPP schema:

<path id="infinity.tools.sysconsole.path">
<fileset dir="${local.dir.lib}" includes="*" />
<pathelement location="${jboss.home}/client/jbossall-client.jar" />
<pathelement location="${jboss.home}/client/log4j.jar" />
</path>
<target name="infinity-drop-schema" description="Drop Schema">
<java classname="ag.carnot.workflow.tools.sysconsole.Main"
classpathref="infinity.tools.sysconsole.path" fork="true">
<arg line="-f -dbtype ORACLE9i -dbdriver oracle.jdbc.driver.OracleDriver
-dburl ${audittrail.database.url} -dbuser ${audittrail.database.username}
-dbpassword ${audittrail.database.password} -password sysop dropschema" />
</java>
</target>
<target name="infinity-create-schema" description="Create Schema">
<java classname="ag.carnot.workflow.tools.sysconsole.Main"
classpathref="infinity.tools.sysconsole.path" fork="true">
<arg line="-f -dbtype ORACLE9i -dbdriver oracle.jdbc.driver.OracleDriver
-dburl ${audittrail.database.url} -dbuser ${audittrail.database.username}
-dbpassword ${audittrail.database.password} createschema" />
</java>
</target>

How to get the new project running

  • Checkout all 4 projects under ...stallion/apps/seattle in your workspace (as single projects)
  • Set JBOSS_HOME to your JBoss installation
  • Make sure JAVA_HOME is set
  • Create oracle user infinity/infinity and grant all rights (via web portal)
  • run build.xml target: installApp
  • This script
  1. -copies database and jms config to JBoss dir
  2. -copies db driver to JBoss dir
  3. -creates jackrabbit repository folder C:\umbroot\tools\jackrabbit\repository
  4. -copy jackrabbit files to JBoss
  5. -changes JBoss port to 8081
  6. -creates the audittrail
  7. -builds and deploys seattle ear
  8. -starts JBoss
  • Wait till JBoss is started
  • Afterwards the application should be accessible via http://localhost:8081/seattle/processPortal
  • Now run target "initializeApp"
  • This script
  1. -deploys the model
  2. -Creates users with role "Voter"
  3. -starts event and timer daemon

-Larissa Werlein-

Thursday, January 7, 2010

Universal IM Clients

To ease communication between developers while traveling, we are all using Yahoo! Messenger. While some people on our project already had accounts, I did not, thus when I created a new account I had a list of 3 contacts. Many of my other fellow work colleges use Google Talk, so if I wanted to communicate with them about best practices, or any other work related jabber, it was necessary for me to have to clients open. For some people this wouldn't be a problem, but for me it was a huge distraction. Enter Universal IM Clients. Old news to some, for me this revolutionary technology negated the need for me to have numerous blinking windows on my already busy monitor. I currently am running Pidgin, which I discovered today is rated the 2nd Best Universal Chat Client.

I know this is not the only solution available, and I'd be welcome in hearing about more technologies that help serve this purpose. Always looking for the best technology to solve problems! (Even though I'm using the 2nd best. )

-Brandy Brewster

Wednesday, January 6, 2010

Installing the Grails Security Plugin

To setup controller annotation based security, I'm following the plugin installation instructions detailed here.

Starting with:
grails install-plugin acegi

I used "InfinityUser" as my "User" class as User is a reserved word in Oracle. There are still places in the generated code where people/person is used instead of the name you pass.
# grails create-auth-domains InfinityUser Role Requestmap

Then, I generated Person/Role management support:
# grails generate-manager

I modified the SecurityConfig.groovy and deleted the ResourceMap domain, controller and views as directed in the tutorial.

I tested the app by starting it up and modified BootStrap.groovy to avoid retyping Roles and Persons in the GUI. It is very important to make your Roles begin with the word "ROLE"

new Role(authority:"ROLE_LOV_ADMIN",description:"Allows a user to modify the available List Of Values definitions.").addToPeople(new InfinityUser(username:"jdoe", userRealName:"John Doe",passwd:"81fe8bfe87576c3ecb22426f8e57847382917acf",enabled:true,email:"jdoe@gmail.com",emailShow:true,description:"An LOV Admin")).save()
new Role(authority:"ROLE_PAF_EDITOR",description:"Allows a user to modify draft Pre-Acceptance Forms.").addToPeople(new InfinityUser(username:"jsmith", userRealName:"Jane Smith",passwd:"81fe8bfe87576c3ecb22426f8e57847382917acf",enabled:true,email:"jsmith@gmail.com",emailShow:true,description:"A PAF Editor")).save()

Note the 'passwd' is "81fe8bfe87576c3ecb22426f8e57847382917acf". The password is "abcd" encoded by authenticateService.encodePassword(params.passwd) in PersonController.save()

Here is one of the places where the plugin fails if the Role.authority doesn't include 'ROLE'. When I tested the app I noticed there was a bug where all Roles would be removed when saving an InfinityUser. I tracked it down to InfinityUserController.update(). There is a line that removes all Roles:
Role.findAll().each { it.removeFromPeople(person) }
However, they weren't being added back in. This was a bug in addRoles(person)Adding the word 'ROLE' to your role names fixes it. However, there are other places in the plugin that rely on this convention.
    private void addRoles(person) {
for (String key in params.keySet()) {
//println "key="+key+" params.get(key)="+params.get(key)
// modified this line because it causes roles not to be added if they don't contain the word role
//if (key.contains('ROLE') && 'on' == params.get(key)) {
if ('on' == params.get(key)) {
// use Elvis '?' operator to avoid keys with "on" values that aren't Roles
Role.findByAuthority(key)?.addToPeople(person)
}
}
}
-Ben Hidalgo

JUnit Tests for IPP

I have started to write JUnit tests cases for our voting process. The current voting process has been implemented with our BPM tool, IPP. In the past to test code or model changes, I would need to redeploy the model and walk through the changes via the on-line portal. Although there are tools (Selenium for one) that can be used to test web applications by providing button clicking and web page display validation, they are limited in the fact that the range of what is tested is defined by the current logged in user. By using the api I can test my processes, activities, and the states of both simply by running a unit test. So instead of manually logging on, starting a voting process, and then validating that each user that should have a voting activity has one, a test is started that walks through this entire process via the IPP api and uses JUnit to assert the correct results.

1. Start the process:
ProcessInstance processInstance = workflowService.startProcess("VotingSystem",votingInformation,false);

2. Verify that activites are created for the correct number of voters:
ActivityInstanceQuery aiQuery = ActivityInstanceQuery.findAlive(processInstance.getProcessId(), "vote");
assertEquals("The number of activity instances in vote does not match",
voters.size(), qService.getActivityInstancesCount(aiQuery));

-Brandy Brewster

Issues with Grails and Ajax

Currently encountering an issue with the Grails gsp tag submitToRemote. The attributes defining javascript functions called at different points in the ajax call lifecycle, do not appear to invoke user defined javascript methods.

for example:

foo() isn't being invoked here
< onsuccess="foo();" url="[action:'fooAction']" value="Run Foo">

but the following alert will occur
< onsuccess="javascript:alert('hi');" url="[action:'fooAction']" value="Run Foo">

Adding to the confusion is the fact that Grails In Action states that the javascript method signature should define a parameter which contains the HttpXmlResponse object, something the documentation doesn't mention.

Strangely, the issue does not appear with other ajax tags like remoteLink and remoteFunction.

Switched Grails DataSource from hsqldb to Oracle

Yesterday, I switched the default Grails datasource that grails create-app generates into an Oracle datasource in the following steps:

1. Modify grails-app/conf/DataSource.groovy
2. Add the ojdbc14.jar to /lib
3. Install OracleXE
4. Create a database user and grant privileges

1: DataSource.groovy

dataSource {
pooled = true
driverClassName = "oracle.jdbc.OracleDriver"
}
...
development {
dataSource {
dbCreate = "create-drop" // one of 'create', 'create-drop','update'
url = "jdbc:oracle:thin:@localhost:1521:XE"
username = "scott"
password = "tiger"
}
}

4: User create script:
conn system/system@xe
drop user scott;
create user scott identified by tiger default tablespace users quota unlimited on users;
grant connect to scott;
grant create any table to scott;
grant create any view to scott;
grant create any sequence to scott;
grant create any index to scott;

-Ben Hidalgo