Monday, March 5, 2007

Blending and rotating images using Javascript

I recently can across this interesting website that shows how you can create cross browser fading effects for images. However, I needed to rotate a number of images displayed in addition to the blending effects. Furthermore, I wanted to have 2 or more sets of rotating images in the same page if possible. So I decided to enhance the javascript provided in the above website. There are 3 steps. Firstly, create a html page and include the js.file called, "rotatingImages.js" as below.


<HEAD>
<SCRIPT language="JavaScript" src="rotatingImages.js" type="text/javascript"></SCRIPT>
<TITLE>Test page</TITLE>
</HEAD>


Then include the following DIV tag, A tag and IMG tag in the body of the html file. Although not very elegant, you will have to declare two javascript arrays to store the hyperlinks (when users click on the image) and the tooltips (when users moveover the image). This is necessary if we want to display more than one set of rotating images in a html page.


<div id="rotatingImagesDiv" style="background-repeat: no-repeat;" >
  <a id="linkbanner">
    <img id="rotatingImages" src="http://www.abc.com/images/0.jpg"
    border="0" hspace="0"
    onload="var urls = new Array();
    urls[0]='http://www.0.com';
    urls[1]='http://www.1.com';
    urls[2]='http://www.2.com';
    urls[3]='http://www.3.com';
    urls[4]='http://www.4.com';
    urls[5]='http://www.5.com';
    urls[6]='http://www.6.com';

    var toolTips = new Array();
    toolTips[0]='0 image';
    toolTips[1]='1 image';
    toolTips[2]='2 image';
    toolTips[3]='3 image';
    toolTips[4]='4 image';
    toolTips[5]='5 image';
    toolTips[6]='6 image';

    initImagesRotation('http','www.abc.com/images/',7,urls,toolTips,'rotatingImagesDiv','linkbanner','rotatingImages',10000,2000)" />
  </a>
</div>

<div id="rotatingImagesDiv1" style="background-repeat: no-repeat;">
  <a id="linkbanner1">
    <img id="rotatingImages1" src="http://www.abc.com/images/0.jpg"
    border="0" hspace="0"      
    onload="urls = new Array();
    urls[0]='http://www.one.com';
    urls[1]='http://www.two.com';
    urls[2]='http://www.three.com';
    urls[3]='http://www.four.com';
    urls[4]='http://www.five.com';
    urls[5]='http://www.six.com';
    urls[6]='http://www.seven.com';

    toolTips= new Array();
    toolTips[0]='zero image';
    toolTips[1]='one image';
    toolTips[2]='two image';
    toolTips[3]='three image';
    toolTips[4]='four image';
    toolTips[5]='five image';
    toolTips[6]='six image';

    initImagesRotation('http','www.abc.com/images/',7,urls,toolTips,'rotatingImagesDiv1','linkbanner1','rotatingImages1',5000,1000)" />
  </a>
</div>


Lastly, create the javascript file, "rotatingImages.js" to make it all work. Then enjoy!


function changeOpac(opacity, id) {

  var object = document.getElementById(id).style;
  object.opacity = (opacity / 100);
  object.MozOpacity = (opacity / 100);
  object.KhtmlOpacity = (opacity / 100);
  object.filter = "alpha(opacity=" + opacity + ")";

}

function RotateImageClass(){

  var counter = 1;
  var toggleFlag = false;
  var imageFiles = new Array();
  var timeoutHandler, timer, speed;

  var self = this;

  function fade(clsid, id, speed, opacStart, opacEnd) {

    var percentChange = Math.abs( (opacStart - opacEnd) );
    speed = Math.round( (speed / 100) );

    //determine the direction for the blending, if start and end are the same nothing happens

    var timer = 0, ct;
    if(opacStart > opacEnd) {

      opacEnd -= 1;
      for(ct = opacStart; ct> opacEnd; ct--)
      {
        setTimeout("changeOpac(" + ct + ",'" + id + "')",(speed*timer));
        timer++;
      }

    } else if(opacStart < opacEnd) {

      opacEnd += 1;
      for(ct = opacStart; ct < opacEnd; ct++)
      {
        setTimeout("changeOpac(" + ct + ",'" + id + "')",(speed*timer));
        timer++;
      }
    }
  }

  this.Init = function() {

    timer = parseInt(this.Duration);
    speed = parseInt(this.FadingSpeed);
    if( speed < 100 )
      speed = 100; //Pointless if set too low.

    // Need to do this because IE 7 choke when function parameters contains 'http://'!
    var prefix = "";

    if( this.ProtocolType.indexOf("http",0) > -1 ){
      prefix = 'http://';
    }
    else if( this.ProtocolType.indexOf("file",0) > -1 ){
      prefix = 'file:///';
    }

    for( var i=0; i<this.ImageQuantity; i++ ){
      imageFiles[i] = prefix + this.ImageLocation + i + '.jpg';
    }
  }

  this.GetImage = function(index) { return imageFiles[index]; }

  this.Rotate = function(){

    if( counter < this.ImageQuantity ){
      // do nothing
    }
    else{
      counter = 0;
    }

    // Set tool tips
    setTimeout("document.getElementById('" + this.ImageId + "').title='"
      + this.ToolTipArray[counter] + "'",this.Duration);

    // Set the hyperlink
    setTimeout("document.getElementById('" + this.LinkId + "').href='"
      + this.UrlArray[counter] + "'",this.Duration);

    if( toggleFlag ) {

      fade(this.ImageId,this.ImageId,speed,0,100);
      // Set the DIV background image when it is totally faded
      setTimeout("document.getElementById('" + this.DivId + "').style.backgroundImage='url("
      + imageFiles[counter] + ")'",this.Duration);
    }
    else{
      fade(this.ImageId,this.ImageId,speed,100,0);
      // Set image source when it is totally faded
      setTimeout("document.getElementById('" + this.ImageId + "').src='"
        + imageFiles[counter] + "'",this.Duration);
    }

    timeoutHandler = setTimeout("rotateImageInstances['" + this.ImageId + "'].Rotate()",timer);
    counter++;

    toggleFlag = !toggleFlag;
  }
}

var rotateImageInstances = new Array();

function initImagesRotation(protocolType, imageLocation, numberOfImages, urlArray, toolTipArray, divId, linkId, imageId, duration, fadingSpeed)
{
  // Sanity check
  if( fadingSpeed > duration )
  {
    // Huh?
  }
  else if( numberOfImages<2 ){
    // Huh?
  }
  else if( urlArray.length!=numberOfImages ){
    alert("The list of image URL do not match the number of images given.");
  }
  else if( toolTipArray.length!=numberOfImages){
    alert("The number of tooltips do not match the number of images given.");
  }
  else
  {
    if( typeof(rotateImageInstances[imageId])=="undefined" )
    {
      rotateImageInstances[imageId] = new RotateImageClass();
      rotateImageInstances[imageId].ProtocolType = protocolType;
      rotateImageInstances[imageId].ImageLocation = imageLocation;
      rotateImageInstances[imageId].ImageQuantity = numberOfImages;
      rotateImageInstances[imageId].UrlArray = urlArray;
      rotateImageInstances[imageId].ToolTipArray = toolTipArray;
      rotateImageInstances[imageId].DivId = divId;
      rotateImageInstances[imageId].LinkId = linkId;
      rotateImageInstances[imageId].ImageId = imageId;
      rotateImageInstances[imageId].Duration = duration;
      rotateImageInstances[imageId].FadingSpeed = fadingSpeed;
      rotateImageInstances[imageId].Init();

      document.getElementById(divId).style.backgroundImage = 'url(' + rotateImageInstances[imageId].GetImage(0) + ')';
      document.getElementById(imageId).src = rotateImageInstances[imageId].GetImage(0);

      rotateImageInstances[imageId].Rotate();
    }
  }
}

Roller v3.0 on WebLogic v9.2

After some tinkling, I managed to get Roller v3.0 running on WebLogic 9.2 as well. So here's the steps.


Preparation
1) Download source code for Roller v3.0.
2) Create a temp folder (we shall named it as {temp} from here onwards). Place downloaded source code in '{temp}\roller'.
3) Create a new Dynamic Web Project in WLP 9.2 named 'roller'. Use the default settings for Project Facets selection. Change 'Content Directory' to 'roller' (We will refer to the application directory as 'roller' from here onwards).
4) Rename '{roller}\roller\WEB-INF\web.xml' to '{roller}\roller\WEB-INF\web.bak'
5) Rename '{roller}\roller\index.jsp' to '{roller}\roller\index.bak'. (in case you want to keep it for later).
 
First compilation of roller
(The steps for compiling roller is still the same as before).
 

Setup WebLogic roller
1) Remove the additional filter added by XDoclet.

<filter>
<filter-name></filter-name>
<filter-class>org.apache.roller.ui.webapp.RollerAuthenticationProcessingFilter</filter-class>
</filter>


2) Copy '{roller}\roller\WEB-INF\classes\META-INF\tlds\roller.tld' to '{roller}\roller\WEB-INF'.
3) Open up '{roller}\roller\jsps\tiles\search.jsp', under line 37, change the special character '»' to '&raquo;'.
4) Amend '{roller}\roller\jsps\authoring\spellcheck-entry.jsp' and add the following.
  (Hint: Paste after the </script> tag.)
  <%!
  public static String makeSelect(String word, List words)
  {
    StringBuffer buf = new StringBuffer("<select name=\"");
    buf.append("replacementWords\"style=\"font-size: 10px;\">");
    buf.append("<option selected=\"selected\" value=\"").append(word);
    buf.append("\">").append(word).append("</option>");
    if (words == null || words.size() < 1)
    {
      buf.append("<option value=\"").append(word);
      buf.append("\">No Suggestions</option>");
    }
    else
    {
      for (Iterator it2=words.iterator(); it2.hasNext();)
      {
        word = it2.next().toString();
        buf.append("<option value=\"").append(word);
        buf.append("\">").append(word).append("</option>");
      }
    }
    buf.append("</select>");
    return buf.toString();
  }
  %>
 
  (Hint: Paste after the 'Use Spell Check Results' wordings.)
  <%
    String escapeText = StringUtils.replace( text, "<", "{" );
    escapeText = StringUtils.replace( escapeText, ">", "}" );
    StringBuffer newText = new StringBuffer(escapeText);
    ArrayList events = (ArrayList) session.getAttribute("spellCheckEvents");
    SpellCheckEvent event = null;
    String word = null;
    int start = -1;
    int end = -1;
    String select = null;
    for(ListIterator it=events.listIterator(events.size()); it.hasPrevious();)
    {
      event = (SpellCheckEvent)it.previous();
      word = event.getInvalidWord();
      start = event.getWordContextPosition();
      end = start + word.length();
      select = makeSelect(word, event.getSuggestions());
 
      newText.replace( start, end, select );
    }

    escapeText = StringUtils.replace(newText.toString(), "}", "&gt;" );
    escapeText = StringUtils.replace(escapeText, "{", "&lt;" );
  %>

5) Create the necessary datasource with JNDI name, jdbc/rollerdb" as well as the mail session with JNDI name mail/Session" in WebLogic console.

6) Amend the line, "${catalina.base}/logs/roller.log" in '{roller}\roller\WEB-INF\classes\log4j.properties' to the right place where you want your log to be.

7) Next we need to amend '{roller}\roller\WEB-INF\classes\hibernate.cfg.xml' with the following (See sample below, I'm using Oracle so modify for your DB).
 
  <!-- By default Roller uses a JNDI DataSource -->
  <property name="jndi.url">t3://localhost:7001</property>
  <property name="jndi.class">weblogic.jndi.WLInitialContextFactory</property>
  <property name="connection.datasource">jdbc/rollerdb</property>
  <property name="show_sql">false</property>

  <property name="current_session_context_class">thread</property>

  <!-- select SQL dialect, MySQL 3.X or 4.X by default -->
  <property name="hibernate.dialect">org.hibernate.dialect.Oracle9Dialect</property>

8) Comment off the line in web.xml as below:
 
  <!--<listener>
  <listener-class>org.apache.roller.ui.core.RollerContext</listener-class>
  </listener>-->

9) Modify '{temp}\roller\src\org\apache\roller\business\utils\UpgradeDatabase.java' at
line 190 if you are using Oracle like me.
 
  PreparedStatement websitesQuery = con.prepareStatement(
    "select w.id as wid, u.id as uuid, u.username as uname from "
    + "website w, rolleruser u where u.id=w.userid");

10) Change the context-root in '{roller}\roller\WEB-INF\weblogic.xml' to "roller" if it is not already so.
 
11) Deploy and enjoy.

Wednesday, February 7, 2007

Roller v3.0 with Active Directory (non-SSL)

Roller v3.0 on Weblogic Platform v8.1.5 with Active Directory


I added integration with Active Directory after getting Roller to run on WebLogic. However, the process is fairly complicated and there is as yet no SSL. But if you are also looking into doing the same thing perhaps the below steps will help to shorten the learning curve. Enjoy!

Preparation
1) Amend '{DefaultApp}\roller\WEB-INF\security.xml'.
    1.1) Remove the line 'anonymousProcessingFilter' as below.

    <!-- ======================== FILTER CHAIN ======================= -->
    <bean id="filterChainProxy" class="org.acegisecurity.util.FilterChainProxy">
        <property name="filterInvocationDefinitionSource">
            <value>
               CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
               PATTERN_TYPE_APACHE_ANT
               /**=httpSessionContextIntegrationFilter,authenticationProcessingFilter,rememberMeProcessingFilter,
               channelProcessingFilter,remoteUserFilter,exceptionTranslationFilter,filterInvocationInterceptor
            </value>
        </property>
    </bean>

    1.2) Comment out 'daoAuthenticationProvider' and 'anonymousAuthenticationProvider' and add 'ldapAuthProvider' as below.

    <bean id="authenticationManager" class="org.acegisecurity.providers.ProviderManager">
        <property name="providers">
            <list>
               <ref local="ldapAuthProvider"/>
               <!--<ref local="daoAuthenticationProvider"/>-->
               <!--<ref local="anonymousAuthenticationProvider"/>-->
               <!-- rememberMeAuthenticationProvider added programmatically -->
            </list>
        </property>
    </bean>

    1.3) Uncomment and modify the LDAP parameters to suit your
environment. A sample is given as below.
    <!-- Sample LDAP/RollerDB hybrid security configuration -->
    <bean id="initialDirContextFactory" class="org.acegisecurity.ldap.DefaultInitialDirContextFactory">
        <constructor-arg value="ldap://192.168.0.100:389/DC=roller,DC=com"/>
            <property name="managerDn">
               <value>CN=tommy,OU=Users,DC=roller,DC=com</value>
            </property>
            <property name="managerPassword">
               <value>xxxxx</value>
            </property>
            <property name="useConnectionPool">
               <value>false</value>
            </property>
    </bean>

    <bean id="ldapUserSearch" class="org.acegisecurity.ldap.search.FilterBasedLdapUserSearch">
        <constructor-arg index="0">
           <value></value>
        </constructor-arg>
        <constructor-arg index="1">
            <value>sAMAccountName={0}</value>
        </constructor-arg>
        <constructor-arg index="2">
            <ref local="initialDirContextFactory"/>
        </constructor-arg>
        <property name="searchSubtree">
           <value>true</value>
        </property>
    </bean>

    <bean id="ldapAuthProvider"
            class="org.acegisecurity.providers.ldap.LdapAuthenticationProvider">
        <constructor-arg>
            <bean class="org.acegisecurity.providers.ldap.authenticator.BindAuthenticator">
               <constructor-arg><ref local="initialDirContextFactory"/></constructor-arg>
               <property name="userSearch"><ref bean="ldapUserSearch"/></property>
            </bean>
        </constructor-arg>
        <constructor-arg><ref local="jdbcAuthoritiesPopulator"/></constructor-arg>
        <property name="userCache" ref="userCache"/>
    </bean>

    <bean id="jdbcAuthoritiesPopulator" class="org.apache.roller.ui.core.security.AuthoritiesPopulator">
        <property name="dataSource">
            <bean class="org.springframework.jndi.JndiObjectFactoryBean">
               <property name="jndiName" value="jdbc/rollerdb"/>
            </bean>
        </property>
        <property name="authoritiesByUsernameQuery">
            <value>SELECT username,rolename FROM userrole WHERE username = ?</value>
        </property>
        <property name="defaultRole"><value>register</value></property>
    </bean>

    1.4) Comment out the below to prevent anonymous users.
    <!--<bean id="anonymousAuthenticationProvider" class="org.acegisecurity.providers.anonymous.AnonymousAuthenticationProvider">
            <property name="key" value="anonymous"/>
    </bean>-->

    <!--
    <bean id="anonymousProcessingFilter" class="org.acegisecurity.providers.anonymous.AnonymousProcessingFilter">
        <property name="key" value="anonymous"/>
        <property name="userAttribute" value="anonymous,ROLE_ANONYMOUS"/>
    </bean>
    -->

2) As we are no longer allowing users to register anonymously, we need to enable auto-provisioning. Add the custom properties file, '{DefaultApp}\roller\WEB-INF\classes\roller-custom.properties' with the following properties (Note: the property 'users.sso.enabled' must be set so that the CustomUserRegistry class will return the authenticated LDAP user data).
    # Enables Roller to behave differently when registering new users
    # in an SSO-enabled environment. You must configure security.xml appropriately.
    users.sso.enabled=true

    # Set these properties for a custom LDAP schema (optional).
    # Note by Damon: Change below parameters accordingly for your environment
    users.sso.registry.ldap.attributes.name=displayName
    users.sso.registry.ldap.attributes.email=userPrincipalName

    users.sso.autoProvision.enabled=true
    users.sso.autoProvision.className=org.apache.roller.ui.core.security.BasicUserAutoProvision

3) After configuring the above, you should get user auto-provisioning working. Just go to the login page (e.g. http://localhost:7001/roller/roller-ui/login.do). Enter the AD user id and password to login.

    However, the default logic now will result in a '403 - Access Denied' HTTP error immediately after the new user login (although the new user account will be created in the DB table by now). The '403' error arise will disappear once the user cache in the application's memory is cleared (such as when application restart). In order for a new user to proceed to create his weblog he needs at least a 'editor' role. Currently, the roles are added when the ACEGI security filter classes retrieve any additional privileges from a DB table while creating the user principal object. For new accounts, this happens too late as the HibernateUserManagerImpl class only adds the additional 'editor' role into the DB table after the user principal object was created. There are probably a few ways to tackle this. I chose to extend the ACEGI classes LdapAuthenticationProvider and create a custom class in '{temp}\roller\src\org\apache\roller\ui\providers\RollerLdapAuthenticationProvider.java'. This class will check if a user exists after a successful LDAP retrieval. If the user does not exists, it will insert a default "editor" role for the new user.

    In addition, we need to extend RollerSession class so that it will not only provision a new user account in the DB table but also re-populate the user principal with the newly granted roles. The re-population will require recreating the user principal object, reset the Authentication object in the SecurityContextHolder and refresh the cache in the ACEGI class AbstractUserDetailsAuthenticationProvider. To refresh the cache, we will also have to extend AbstractUserDetailsAuthenticationProvider as well as a few other ACEGI classes.

4) Add 'RollerLdapAuthenticationProvider.java' into '{temp}\roller\src\org\apache\roller\ui\providers\ldap\',
code as below:

/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.apache.roller.ui.providers.ldap;

import org.acegisecurity.Authentication;
import org.acegisecurity.AuthenticationException;
import org.acegisecurity.BadCredentialsException;
import org.acegisecurity.GrantedAuthority;
import org.acegisecurity.AuthenticationServiceException;

import org.acegisecurity.providers.UsernamePasswordAuthenticationToken;
import org.acegisecurity.providers.ldap.LdapAuthenticator;
import org.acegisecurity.providers.ldap.LdapAuthoritiesPopulator;
import org.acegisecurity.providers.dao.AbstractUserDetailsAuthenticationProvider;

import org.acegisecurity.userdetails.UserDetails;
import org.acegisecurity.userdetails.ldap.LdapUserDetails;
import org.acegisecurity.userdetails.ldap.LdapUserDetailsImpl;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import org.springframework.dao.DataAccessException;
// For user provision and reauthentication, Damon Chong.
import org.apache.roller.model.RollerFactory;
import org.apache.roller.model.UserManager;
import org.apache.roller.pojos.UserData;
import org.apache.roller.config.RollerConfig;
import
org.apache.roller.ui.providers.dao.RollerAbstractUserDetailsAuthenticationProvider;
import org.apache.roller.ui.providers.RollerUsernamePasswordAuthenticationToken;
import org.apache.roller.ui.providers.ldap.authenticator.RollerBindAuthenticator;
import org.acegisecurity.GrantedAuthorityImpl;
import org.apache.roller.RollerException;

public class RollerLdapAuthenticationProvider extends RollerAbstractUserDetailsAuthenticationProvider {
    //~ Static fields/initializers =====================================================================================

    private static final Log logger = LogFactory.getLog(RollerLdapAuthenticationProvider.class);

    //~ Instance fields ================================================================================================

    private LdapAuthenticator authenticator;
    private LdapAuthoritiesPopulator authoritiesPopulator;

    //~ Constructors ===================================================================================================

    public RollerLdapAuthenticationProvider(LdapAuthenticator authenticator, LdapAuthoritiesPopulator authoritiesPopulator) {
        Assert.notNull(authenticator, "An LdapAuthenticator must be supplied");
        Assert.notNull(authoritiesPopulator, "An LdapAuthoritiesPopulator must be supplied");

        this.authenticator = authenticator;
        this.authoritiesPopulator = authoritiesPopulator;
    }

    //~ Methods ========================================================================================================

    protected void additionalAuthenticationChecks(UserDetails userDetails,
                                                                   UsernamePasswordAuthenticationToken authentication)
        throws AuthenticationException {
        if (!userDetails.getPassword().equals(authentication.getCredentials().toString())) {
            throw new BadCredentialsException(messages.getMessage(
               "AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"), userDetails);
        }
    }

    protected UserDetails createUserDetails(LdapUserDetails ldapUser, String username, String password) {
        LdapUserDetailsImpl.Essence user = new LdapUserDetailsImpl.Essence(ldapUser);
        user.setUsername(username);
        user.setPassword(password);

        GrantedAuthority[] extraAuthorities = authoritiesPopulator.getGrantedAuthorities(ldapUser);

        for (int i = 0; i < extraAuthorities.length; i++) {
           user.addAuthority(extraAuthorities[i]);
        }

        return user.createUserDetails();
    }

    protected UserDetails createNewUserDetails(LdapUserDetails ldapUser, String username, String password) {
        LdapUserDetailsImpl.Essence user = new LdapUserDetailsImpl.Essence(ldapUser);
        user.setUsername(username);
        user.setPassword(password);

        GrantedAuthority[] extraAuthorities = authoritiesPopulator.getGrantedAuthorities(ldapUser);

        for (int i = 0; i < extraAuthorities.length; i++) {
           user.addAuthority(extraAuthorities[i]);
        }
        // For new user we grant them an additional role so that they don't have a 403 error.
        GrantedAuthority editorAuth = new GrantedAuthorityImpl("editor");
        user.addAuthority(editorAuth);

        return user.createUserDetails();
    }

    protected LdapAuthoritiesPopulator getAuthoritiesPoulator() {
        return authoritiesPopulator;
    }

    protected UserDetails retrieveUserAgain(String username, RollerUsernamePasswordAuthenticationToken authentication)
        throws AuthenticationException {
        if (!StringUtils.hasLength(username)) {
            throw new BadCredentialsException(messages.getMessage("LdapAuthenticationProvider.emptyUsername",
               "Empty Username"));
        }

        String password = (String) authentication.getCredentials();

        if (logger.isDebugEnabled()) {
           logger.debug("Retrieving user " + username);
        }

        try {
           LdapUserDetails ldapUser;

            // As password will be null or empty, we cannot authenticate with an invalid password
            // so we extended BindAuthenticator to accept only one parameter
            ldapUser = ((RollerBindAuthenticator)authenticator).authenticate(username);

            return createUserDetails(ldapUser, username, password);

        } catch (DataAccessException ldapAccessFailure) {
            throw new AuthenticationServiceException(ldapAccessFailure.getMessage(), ldapAccessFailure);
        }
    }

    protected UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication)
        throws AuthenticationException {
        if (!StringUtils.hasLength(username)) {
            throw new BadCredentialsException(messages.getMessage("LdapAuthenticationProvider.emptyUsername",
               "Empty Username"));
        }

        if (logger.isDebugEnabled()) {
           logger.debug("Retrieving user " + username);
        }

        String password = (String) authentication.getCredentials();
        Assert.notNull(password, "Null password was supplied in authentication token");

        if (password.length() == 0) {
           logger.debug("Rejecting empty password for user " + username);
            throw new BadCredentialsException(messages.getMessage("LdapAuthenticationProvider.emptyPassword",
               "Empty Password"));
        }

        try {
           LdapUserDetails ldapUser = authenticator.authenticate(username, password);
            // Always check if the user exists.
            if( isNewUser(username) )
            {
               return createNewUserDetails(ldapUser, username, password);
            }
            else{
               return createUserDetails(ldapUser, username, password);
            }

        } catch (DataAccessException ldapAccessFailure) {
            throw new AuthenticationServiceException(ldapAccessFailure.getMessage(), ldapAccessFailure);
        }
    }

    private boolean isNewUser(String username){

        try
        {
            UserManager umgr = RollerFactory.getRoller().getUserManager();
            UserData user = umgr.getUserByUserName(username);

            if(user == null && RollerConfig.getBooleanProperty("users.sso.autoProvision.enabled")) {
               return true;
            }
        }
        catch (RollerException e)
        {
           e.printStackTrace();
        }

        return false;
    }
}

5) Add 'RollerBindAuthenticator.java' into into '{temp}\roller\src\org\apache\roller\ui\providers\ldap\dao\',
code as below:

/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.apache.roller.ui.providers.ldap.authenticator;

import org.acegisecurity.BadCredentialsException;

import org.acegisecurity.providers.ldap.authenticator.AbstractLdapAuthenticator;
import org.acegisecurity.ldap.InitialDirContextFactory;
import org.acegisecurity.ldap.LdapTemplate;

import org.acegisecurity.userdetails.ldap.LdapUserDetails;
import org.acegisecurity.userdetails.ldap.LdapUserDetailsImpl;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import java.util.Iterator;

public class RollerBindAuthenticator extends AbstractLdapAuthenticator {
    //~ Static fields/initializers =====================================================================================

    private static final Log logger = LogFactory.getLog(RollerBindAuthenticator.class);

    //~ Constructors ===================================================================================================

    public RollerBindAuthenticator(InitialDirContextFactory initialDirContextFactory) {
        super(initialDirContextFactory);
    }

    //~ Methods ========================================================================================================

    public LdapUserDetails authenticate(String username, String password) {
        LdapUserDetails user = null;

        // If DN patterns are configured, try authenticating with them directly
        Iterator dns = getUserDns(username).iterator();

        while (dns.hasNext() && (user == null)) {
            user = bindWithDn((String) dns.next(), username, password);
        }

        // Otherwise use the configured locator to find the user
        // and authenticate with the returned DN.
        if ((user == null) && (getUserSearch() != null)) {
           LdapUserDetails userFromSearch = getUserSearch().searchForUser(username);
            user = bindWithDn(userFromSearch.getDn(), username, password);
        }

        if (user == null) {
            throw new BadCredentialsException(messages.getMessage("BindAuthenticator.badCredentials", "Bad credentials"));
        }

        return user;
    }

    public LdapUserDetails authenticate(String username) {
        LdapUserDetails user = null;

        // If DN patterns are configured, try authenticating with them directly
        Iterator dns = getUserDns(username).iterator();

        while (dns.hasNext() && (user == null)) {
            user = bindWithDn((String) dns.next(), username);
        }
       
        // Otherwise use the configured locator to find the user
        // and authenticate with the returned DN.
        if ((user == null) && (getUserSearch() != null)) {
           LdapUserDetails userFromSearch = getUserSearch().searchForUser(username);           

            user = bindWithDn(userFromSearch.getDn(), username);           

        }

        if (user == null) {
            throw new BadCredentialsException(messages.getMessage("BindAuthenticator.badCredentials", "Bad credentials"));
        }

        return user;
    }

    private LdapUserDetails bindWithDn(String userDn, String username, String password) {
        LdapTemplate template = new LdapTemplate(getInitialDirContextFactory(), userDn, password);

        if (logger.isDebugEnabled()) {
           logger.debug("Attempting to bind with DN = " + userDn);
        }

        try {
           LdapUserDetailsImpl.Essence user = (LdapUserDetailsImpl.Essence)template.retrieveEntry(userDn,
               getUserDetailsMapper(), getUserAttributes());
           user.setUsername(username);

            return user.createUserDetails();
        } catch (BadCredentialsException e) {
            // This will be thrown if an invalid user name is used and the method may
            // be called multiple times to try different names, so we trap the exception.
            if (logger.isDebugEnabled()) {
               logger.debug("Failed to bind as " + userDn + ": " + e.getCause());
            }
        }

        return null;
    }

    private LdapUserDetails bindWithDn(String userDn, String username) {
        LdapTemplate template = new LdapTemplate(getInitialDirContextFactory());

        if (logger.isDebugEnabled()) {
           logger.debug("Attempting to bind with DN = " + userDn);
        }

        try {
           LdapUserDetailsImpl.Essence user = (LdapUserDetailsImpl.Essence) template.retrieveEntry(userDn,
           getUserDetailsMapper(), getUserAttributes());
           user.setUsername(username);

            return user.createUserDetails();
        } catch (BadCredentialsException e) {
            // This will be thrown if an invalid user name is used and the method may
            // be called multiple times to try different names, so we trap the exception.
            if (logger.isDebugEnabled()) {
               logger.debug("Failed to bind as " + userDn + ": " + e.getCause());
            }
        }

        return null;
    }
}

6) Add 'RollerUsernamePasswordAuthenticationToken.java' into into '{temp}\roller\src\org\apache\roller\ui\providers\', code as below:

/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.apache.roller.ui.providers;

import org.acegisecurity.GrantedAuthority;
import org.acegisecurity.providers.UsernamePasswordAuthenticationToken;


public class RollerUsernamePasswordAuthenticationToken extends UsernamePasswordAuthenticationToken {
    //~ Constructors ===================================================================================================
    public RollerUsernamePasswordAuthenticationToken(Object principal, Object credentials) {
        super(principal, credentials);
    }

    public RollerUsernamePasswordAuthenticationToken(Object principal, Object credentials, GrantedAuthority[] authorities) {
        super(principal, credentials, authorities);
    }

}

7) Add 'RollerAbstractUserDetailsAuthenticationProvider.java' into into '{temp}\roller\src\org\apache\roller\ui\providers\dao', code as below:

/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.apache.roller.ui.providers.dao;

import org.acegisecurity.AccountExpiredException;
import org.acegisecurity.AcegiMessageSource;
import org.acegisecurity.Authentication;
import org.acegisecurity.AuthenticationException;
import org.acegisecurity.BadCredentialsException;
import org.acegisecurity.CredentialsExpiredException;
import org.acegisecurity.DisabledException;
import org.acegisecurity.LockedException;

import org.acegisecurity.providers.AuthenticationProvider;
import org.acegisecurity.providers.UsernamePasswordAuthenticationToken;
import org.acegisecurity.providers.dao.cache.NullUserCache;
import org.acegisecurity.providers.dao.UserCache;

import org.acegisecurity.userdetails.UserDetails;
import org.acegisecurity.userdetails.UserDetailsService;
import org.acegisecurity.userdetails.UsernameNotFoundException;

import org.springframework.beans.factory.InitializingBean;

import org.springframework.context.MessageSource;
import org.springframework.context.MessageSourceAware;
import org.springframework.context.support.MessageSourceAccessor;

import org.springframework.util.Assert;
// For re-authentication, Damon Chong.
import org.apache.roller.ui.providers.RollerUsernamePasswordAuthenticationToken;

public abstract class RollerAbstractUserDetailsAuthenticationProvider implements AuthenticationProvider, InitializingBean,
    MessageSourceAware {
    //~ Instance fields ================================================================================================

    protected MessageSourceAccessor messages = AcegiMessageSource.getAccessor();
    private UserCache userCache = new NullUserCache();
    private boolean forcePrincipalAsString = false;
    protected boolean hideUserNotFoundExceptions = true;

    //~ Methods ========================================================================================================

    protected abstract void additionalAuthenticationChecks(UserDetails userDetails,
        UsernamePasswordAuthenticationToken authentication)
        throws AuthenticationException;
   
    public final void afterPropertiesSet() throws Exception {
        Assert.notNull(this.userCache, "A user cache must be set");
        Assert.notNull(this.messages, "A message source must be set");
        doAfterPropertiesSet();
    }

    public Authentication authenticate(Authentication authentication)
        throws AuthenticationException {
       Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication,
           messages.getMessage("AbstractUserDetailsAuthenticationProvider.onlySupports",
               "Only UsernamePasswordAuthenticationToken is supported"));

        // Determine username
        String username = (authentication.getPrincipal() == null) ? "NONE_PROVIDED" : authentication.getName();

        boolean cacheWasUsed = true;
        boolean reAuthenticate = (authentication instanceof RollerUsernamePasswordAuthenticationToken);
   
        UserDetails user = this.userCache.getUserFromCache(username);

        if ( (user == null) || reAuthenticate ){

            cacheWasUsed = false;

            try {
               if( reAuthenticate ){
                   user = retrieveUserAgain(username, (RollerUsernamePasswordAuthenticationToken) authentication);
               }
               else{
                   user = retrieveUser(username, (UsernamePasswordAuthenticationToken) authentication);
               }

            } catch (UsernameNotFoundException notFound) {
               notFound.printStackTrace();
               if (hideUserNotFoundExceptions) {
                   throw new BadCredentialsException(messages.getMessage(
                       "AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
               } else {
                   throw notFound;
               }
            } catch (Exception e) {
               e.printStackTrace();
            }

           Assert.notNull(user, "retrieveUser returned null - a violation of the interface contract");
        }
   
        if (!user.isAccountNonLocked()) {
            throw new LockedException(messages.getMessage("AbstractUserDetailsAuthenticationProvider.locked",
               "User account is locked"));
        }

        if (!user.isEnabled()) {
            throw new DisabledException(messages.getMessage("AbstractUserDetailsAuthenticationProvider.disabled",
               "User is disabled"));
        }

        if (!user.isAccountNonExpired()) {
            throw new AccountExpiredException(messages.getMessage("AbstractUserDetailsAuthenticationProvider.expired",
               "User account has expired"));
        }

        // This check must come here, as we don't want to tell users
        // about account status unless they presented the correct credentials
        try {
           additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken) authentication);   
        } catch (AuthenticationException exception) {
        // There was a problem, so try again after checking we're using latest data
            cacheWasUsed = false;
            user = retrieveUser(username, (UsernamePasswordAuthenticationToken) authentication);
            additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken)
authentication);
        }

        if (!user.isCredentialsNonExpired()) {
            throw new CredentialsExpiredException(messages.getMessage(
               "AbstractUserDetailsAuthenticationProvider.credentialsExpired", "User credentials have expired"));
        }

        if (!cacheWasUsed) {
           this.userCache.putUserInCache(user);
        }

        Object principalToReturn = user;

        if (forcePrincipalAsString) {
           principalToReturn = user.getUsername();
        }
        return createSuccessAuthentication(principalToReturn, authentication, user);
    }

    protected Authentication createSuccessAuthentication(Object principal, Authentication authentication,
    UserDetails user) {
        // Ensure we return the original credentials the user supplied,
        // so subsequent attempts are successful even with encoded passwords.
        // Also ensure we return the original getDetails(), so that future
        // authentication events after cache expiry contain the details
        UsernamePasswordAuthenticationToken result = new UsernamePasswordAuthenticationToken(principal,
                                                                           authentication.getCredentials(), user.getAuthorities());
        result.setDetails(authentication.getDetails());

        return result;
    }

    protected void doAfterPropertiesSet() throws Exception {}

    public UserCache getUserCache() {
        return userCache;
    }

    public boolean isForcePrincipalAsString() {
        return forcePrincipalAsString;
    }

    public boolean isHideUserNotFoundExceptions() {
        return hideUserNotFoundExceptions;
    }

    protected abstract UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication)
    throws AuthenticationException;

    protected abstract UserDetails retrieveUserAgain(String username, RollerUsernamePasswordAuthenticationToken authentication)
    throws AuthenticationException;

    public void setForcePrincipalAsString(boolean forcePrincipalAsString) {
        this.forcePrincipalAsString = forcePrincipalAsString;
    }
   
    public void setHideUserNotFoundExceptions(boolean hideUserNotFoundExceptions) {
        this.hideUserNotFoundExceptions = hideUserNotFoundExceptions;
    }

    public void setMessageSource(MessageSource messageSource) {
        this.messages = new MessageSourceAccessor(messageSource);
    }

    public void setUserCache(UserCache userCache) {
        this.userCache = userCache;
    }

    public boolean supports(Class authentication) {
        return (UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication));
    }
}

8) Add 'RollerAuthenticationProcessingFilter.java' into into '{temp}\roller\src\org\apache\roller\ui\webapp',
code as below:

/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.apache.roller.ui.webapp;

import org.acegisecurity.Authentication;
import org.acegisecurity.AuthenticationException;
import org.acegisecurity.context.SecurityContextHolder;

import org.acegisecurity.providers.UsernamePasswordAuthenticationToken;

import org.acegisecurity.ui.AbstractProcessingFilter;

import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;

import org.apache.roller.ui.providers.RollerUsernamePasswordAuthenticationToken;

public class RollerAuthenticationProcessingFilter extends AbstractProcessingFilter {
    //~ Static fields/initializers =====================================================================================

    public static final String ACEGI_SECURITY_FORM_USERNAME_KEY = "j_username";
    public static final String ACEGI_SECURITY_FORM_PASSWORD_KEY = "j_password";
    public static final String ACEGI_SECURITY_LAST_USERNAME_KEY = "ACEGI_SECURITY_LAST_USERNAME";

    //~ Methods ========================================================================================================

    public Authentication attemptAuthentication(HttpServletRequest request)
    throws AuthenticationException {
        String username = obtainUsername(request);
        String password = obtainPassword(request);

        if (username == null) {
            username = "";
        }

        if (password == null) {
            password = "";
        }

        UsernamePasswordAuthenticationToken authRequest;

        // Damon Chong, enhanced to recreate credentials after auto provisioning.
        String usrname = null;
        if( null==(usrname=(String)request.getAttribute(
            RollerAuthenticationProcessingFilter.ACEGI_SECURITY_FORM_USERNAME_KEY)) ){
            authRequest = new UsernamePasswordAuthenticationToken(username, password);
        }
        else{
            // This is a re-authentication after user provision was completed. Damon.
            username = usrname; // as username will be null.
            authRequest = new RollerUsernamePasswordAuthenticationToken(username, password);
        }

        // Place the last username attempted into HttpSession for views
        request.getSession().setAttribute(ACEGI_SECURITY_LAST_USERNAME_KEY, username);

        // Allow subclasses to set the "details" property
        setDetails(request, authRequest);
        Authentication authResult = this.getAuthenticationManager().authenticate(authRequest);
        // Reset the principal, Damon. 9/1/07.
        if( null!=usrname ){
            SecurityContextHolder.getContext().setAuthentication(authResult);
        }

        return authResult;
    }

    public String getDefaultFilterProcessesUrl() {
        return "/j_acegi_security_check";
    }

    public void init(FilterConfig filterConfig) throws ServletException {}

    protected String obtainPassword(HttpServletRequest request) {
        return request.getParameter(ACEGI_SECURITY_FORM_PASSWORD_KEY);
    }

    protected String obtainUsername(HttpServletRequest request) {
        return request.getParameter(ACEGI_SECURITY_FORM_USERNAME_KEY);
    }

    protected void setDetails(HttpServletRequest request, UsernamePasswordAuthenticationToken authRequest) {
        authRequest.setDetails(authenticationDetailsSource.buildDetails(request));
    }
}

9) Modify '{temp}\roller\src\org\apache\roller\ui\core\RollerSession.java' as below:

(Import the additional classes)

// Added by Damon Chong to re-authenticate again. 5/1/2007
import org.springframework.context.ApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;
import org.apache.roller.ui.webapp.RollerAuthenticationProcessingFilter;

(Insert the following line of codes after the line 'user = umgr.getUserByUserName(principal.getName());')

// One more thing to do if user is provision,
// call authentication again to get any additional,
// granted authorities. Damon, 5/1/2007.
    ApplicationContext ctx =
       WebApplicationContextUtils.getRequiredWebApplicationContext(
    request.getSession().getServletContext());
    RollerAuthenticationProcessingFilter filter =
        (RollerAuthenticationProcessingFilter) ctx.getBean("authenticationProcessingFilter");
   
// Re-authenticate again to populate any additional
// grants. Flag for RollerAuthenticationProcessingFilter
// to treat as an re-authentication due to provisioning
// and for RollerLdapAuthenticationProvider not to
// failed it due to an empty length password.
    request.setAttribute(
           RollerAuthenticationProcessingFilter.ACEGI_SECURITY_FORM_USERNAME_KEY,
           principal.getName());

    filter.attemptAuthentication(request);

10) Modify '{DefaultApp}\roller\WEB-INF\security.xml' to make use of new
classes.

    <bean id="ldapAuthProvider"
            class="org.apache.roller.ui.providers.ldap.RollerLdapAuthenticationProvider">
        <constructor-arg>
            <bean class="org.apache.roller.ui.providers.ldap.authenticator.RollerBindAuthenticator">

    <bean id="authenticationProcessingFilter" class="org.apache.roller.ui.webapp.RollerAuthenticationProcessingFilter">

Tuesday, January 30, 2007

Roller Weblog 3.0 on Weblogic Platform v8.1.5

One fine day, I came across a Java based weblogging tool called "Roller" that was quite cool and decided to port it to Weblogic. Only problem was there weren't any how-tos so I decided to roll-up my sleeve and port it myself. Below are the steps I took to do the porting. There maybe better ways of doing it so please let me know.  Cheers!

Lastly, all credits to Dave Johnson and his wonderful team for making Roller available to the community. Keep up the great works, guys!

Roller v3.0 on Weblogic Platform v8.1.5

---------------------------------------------------
Preparation.

1) Download source code for Roller v3.0.
2) Create a temp folder (we shall named it as {temp} from here onwards). Place downloaded source code in '{temp}\roller'.
3) Create a new Default application with a new Web Project in Workshop and renamed the Web Project as 'roller'. (We shall call this folder '{DefaultApp}\roller' from here onwards).
4) Rename '{DefaultApp}\roller\WEB-INF\web.xml' to '{DefaultApp}\roller\WEB-INF\web.bak'
5) Rename '{DefaultApp}\roller\WEB-INF\validator-rules.xml' to '{DefaultApp}\roller\WEB-INF\validator-rules.bak' (You can remove it otherwise).
6) Rename '{DefaultApp}\roller\index.jsp' to '{DefaultApp}\roller\index.bak'. (in case you want to keep it for later).

First compilation of roller.

1) Remember to copy over any dependant libraries into '{temp}\roller\tools'.
2) Either modify properties.xmlf or simply dump 'jazzy.jar' into '{temp}\roller\tools\hibernate-3.1\lib'.
3) Add 'RollerSpellCheck.java' to '{temp}\roller\src\org\apache\roller\model', it. See source code below.


   package org.apache.roller.model;

   import com.swabunga.spell.engine.SpellDictionary;
   import com.swabunga.spell.engine.SpellDictionaryHashMap;
   import com.swabunga.spell.event.SpellCheckEvent;
   import com.swabunga.spell.event.SpellCheckListener;
   import com.swabunga.spell.event.SpellChecker;
   import com.swabunga.spell.event.StringWordTokenizer;

   import org.apache.commons.logging.Log;
   import org.apache.commons.logging.LogFactory;
   import org.apache.roller.RollerException;

   import java.io.IOException;
   import java.io.InputStream;
   import java.io.InputStreamReader;
   import java.util.ArrayList;

   /**
    * Since this class cannot be thread-safe due to the
    * SpellCheckListener interface, first a "canonical"
    * SpellDictionary must be instantiated with the path
    * to the dictionary. Subsequently, call getInstance()
    * which will return a new instance of RollerSpellCheck
    * using the "precompiled" SpellDictionary, or will throw
    * a NullPointerException.  A better way can
    * probably be found.
    **/
   public class RollerSpellCheck implements SpellCheckListener
   {
      private static Log mLogger =
         LogFactory.getFactory().getInstance(RollerSpellCheck.class);

      private static SpellDictionary dictionary = null;

      private SpellChecker spellCheck = null;
      private ArrayList spellCheckEvents = new ArrayList();

      /**
       * Initializer which takes an InputStream for
       * /WEB-INF/english.0 to load the SpellDictionary.
       * Building the SpellDictionary, and thus the SpellChecker
       * is an expensive operation.
       * You can get this InputStream with ServletContext.getResource(
       * "/WEB-INF/english.0").  Throws a RollerException if
       * SpellDictionary cannot be instantiated.
       **/
      public static void init(InputStream in) throws RollerException
      {
         try {
            InputStreamReader inR = new InputStreamReader( in );
            RollerSpellCheck.dictionary = new SpellDictionaryHashMap( inR );
         } catch (IOException ioe) {
            mLogger.error("RollerSpellCheck unable to load SpellDictionary",
                ioe);
            throw new RollerException(ioe);
         }
       }

      /**
       * Private constructor taking a prebuilt SpellChecker
       * as an argument.
       **/
      private RollerSpellCheck()
      {
         spellCheck = new SpellChecker(dictionary);
         spellCheck.addSpellCheckListener(this);
      }

      /**
       * Returns a new instance of RollerSpellCheck, using
       * the (hopefully) prebuilt SpellChecker.
       **/
      public static RollerSpellCheck getInstance() throws RollerException
      {
         if (RollerSpellCheck.dictionary == null)
         {
            throw new RollerException(
               "RollerSpellCheck.SpellDictionary has not been defined");
         }

          return new RollerSpellCheck();
      }

      /**
       * Fulfills interface SpellCheckListener.
       * SpellCheckEvent is placed into the ArrayList
       * spellCheckEvents held by this RollerSpellCheck.
       **/
      public void spellingError(SpellCheckEvent event)
      {
          spellCheckEvents.add( event );
      }

      /**
       * This is the method to check spelling.
       * The submitted String is "parsed" by SpellChecker,
       * SpellCheckEvents are placed into an ArrayList, which
       * is returned to the caller.  A SpellCheckEvent contains
       * the "suspect" word, and a LinkedList of suggested replacements.
       */
      public ArrayList checkSpelling(String str) throws RollerException
      {
         spellCheck.checkSpelling( new StringWordTokenizer(str) );
         return spellCheckEvents;
      }

      /**
       * Convenience method. Creates a RollerSpellCheck object
       * and calls checkSpelling(str) on it, returning the ArrayList.
       */
      public static ArrayList getSpellingErrors(String str) throws RollerException
      {
         RollerSpellCheck rCheck = RollerSpellCheck.getInstance();
         return rCheck.checkSpelling(str);
      }
   }


4) Do an initial compilation in '{temp}\roller' (Assuming you already got Ant just type "build all").
5) Copy all files under '{temp}\roller\build\webapp\' to '{DefaultApp}\roller'. (If using Windows Explorer, reply "Yes to All" when prompt whether to override existing files/folders).

Setup WebLogic roller

As WebLogic v8.1 is a servlet 2.3 container, we need to replace some jar files
1) Copy and replace the following files.   
   1.1) Rename or remove the following files in '{DefaultApp}\roller\WEB-INF\lib'
         i) jstl.jar
         ii) standard.jar
   1.2) Copy the jar files 'jstl-1.0.6.jar' and 'standard-1.0.6.jar' to '{DefaultApp}\roller\WEB-INF\lib'.
2) Copy 'jazzy.jar' to '{DefaultApp}\roller\WEB-INF\lib' if it does not exists.
   
3) We need to make changes to some jsp files.    
   3.1) Copy '{DefaultApp}\roller\WEB-INF\classes\META-INF\tlds\roller.tld' to '{DefaultApp}\roller\WEB-INF'.
   3.2) Open up {DefaultApp}\roller\taglibs.jsp' and change the taglib declarations to use jstl v1.0. Example, change taglib URI "http://java.sun.com/jsp/jstl/core" to "http://java.sun.com/jstl/core". Also uncomment the following "<%@ page language="java" contentType="text/html; charset=UTF-8" %>" to <%--@ page language="java" contentType="text/html; charset=UTF-8" --%>
   3.3) Open up '{DefaultApp}\roller\jsps\admin\rollerProperties.jsp' and change taglib declarations as per 3.2.
   3.4) Open up '{DefaultApp}\roller\jsps\tiles\search.jsp', under line 37, change the special character '»' to '&raquo;'.
   3.5) Copy 'struts-bean.tld' to '{DefaultApp}\roller\WEB-INF\'.
       
4) Amend '{DefaultApp}\roller\jsps\authoring\spellcheck-entry.jsp' and add the following.
   (Hint: Paste after the </script> tag.)

   <%!
   public static String makeSelect(String word, List words)
   {
      StringBuffer buf = new StringBuffer("<select name=\"");
      buf.append("replacementWords\" style=\"font-size: 10px;\">");
      buf.append("<option selected=\"selected\" value=\"").append(word);
      buf.append("\">").append(word).append("</option>");
       if (words == null || words.size() < 1)
       {
          buf.append("<option value=\"").append(word);
          buf.append("\">No Suggestions</option>");
       }
       else
       {
          for (Iterator it2=words.iterator(); it2.hasNext();)
          {
              word = it2.next().toString();
              buf.append("<option value=\"").append(word);
              buf.append("\">").append(word).append("</option>");
          }
      }    
      buf.append("</select>");
      return buf.toString();
   }
   %>

   (Hint: Paste after the 'Use Spell Check Results' wordings.)

   <%
      String escapeText = StringUtils.replace( text, "<", "{" );
          escapeText = StringUtils.replace( escapeText, ">", "}" );
      StringBuffer newText = new StringBuffer(escapeText);
      ArrayList events = (ArrayList) session.getAttribute("spellCheckEvents");
      SpellCheckEvent event = null;
      String word = null;
      int start = -1;
      int end = -1;
      String select = null;
      for(ListIterator it=events.listIterator(events.size()); it.hasPrevious();)
      {
         event = (SpellCheckEvent)it.previous();
         word = event.getInvalidWord();
         start = event.getWordContextPosition();
         end = start + word.length();
         select = makeSelect(word, event.getSuggestions());

           newText.replace( start, end, select );
       }
       escapeText = StringUtils.replace( newText.toString(), "}", "&gt;" );
       escapeText = StringUtils.replace( escapeText, "{", "&lt;" );
    %>


5) Modify web.xml and comment out the <dispatcher> tag for <filter-mapping> tag. Alternatively, open up the new web.xml and the old web.bak in '{DefaultApp}\roller\WEB-INF\' and merge the 2 files taking note that the new web.xml is meant for servlet 2.4 container. If merging, remove the 'PageFlowJspFilter' filter and its filter mapping. You will also have to remove or comment out the 'PageFlowActionServlet'.
   5.1) Also uncomment the below. For some reasons, this cause Weblogic to load RollerContext twice and cause spring framework to complain about multiple ContextLoader definitions.

   <!--<listener>
      <listener-class>org.apache.roller.ui.core.RollerContext</listener-class>
   </listener>-->

6) Next we need to amend '{DefaultApp}\roller\WEB-INF\classes\hibernate.cfg.xml' with the following (See sample below, I'm using Oracle so modify for your DB).


       <!-- By default Roller uses a JNDI DataSource -->
       <property name="jndi.url">t3://localhost:7001</property>
       <property name="jndi.class">weblogic.jndi.WLInitialContextFactory</property>
       <property name="connection.datasource">jdbc/rollerdb</property>
       <property name="show_sql">false</property>
                       
       <property name="current_session_context_class">thread</property>
       <!-- select SQL dialect, MySQL 3.X or 4.X by default -->
       <property name="hibernate.dialect">org.hibernate.dialect.Oracle9Dialect</property>
       


7) Create the roller database tables using the DB scripts in '{DefaultApp}\roller\WEB-INF\dbscripts'
8) Create the necessary JDBC connection pool as well as a data source to the roller DB. Remember to name the JNDI name as 'jdbc/rollerdb'.
9) Changes to '{temp}\roller\src\org\apache\roller\ui\core\RollerContext.java', replace the line 'RollerConfig.setContextRealPath(mContext.getRealPath("/"));' as below.

       // To allow application to run from Weblogic Workshop 8.1. Damon, 8/12/2006.
       String realPath = mContext.getRealPath("/");
       if( -1==realPath.indexOf(".workshop\\output") ){
           RollerConfig.setContextRealPath(realPath);
        }
       else{
           realPath = realPath.replaceFirst(".workshop\\\\output\\\\","");
           RollerConfig.setContextRealPath(realPath);
        }


10) There are some JNDI lookup which we need to change. In my case, I used a custom DB connection class called 'DBConnection'. Also define a mail session with JNDI name 'mail/Session' in the Weblogic server console.
   10.1) In '{temp}\roller\src\org\apache\roller\ui\core\RollerContext.java', modify the method 'upgradeDatabaseIfNeeded'.


          // Added to get DB connection. Damon
           import sp.DBConnection;

          try {
              DBConnection dbc = new DBConnection("jdbc/rollerdb");
              Connection con = dbc.openConnection();
              UpgradeDatabase.upgradeDatabase(con, mVersion);  
            
              dbc.closeConnection();
          } catch (NamingException e) {
              mLogger.warn("Unable to access DataSource", e);
          } catch (SQLException e) {
              mLogger.warn(e);
          }


           
   10.3) In '{temp}\roller\src\org\apache\roller\ui\rendering\servlets\CommentServlet.java' change the JNDI lookup.
   10.4) In '{temp}\roller\src\org\apache\roller\ui\rendering\servlets\PreviewServlet.java' change the line 'String mimeType = RollerContext.getServletContext().getMimeType(pageLink);' to below.

          // WebLogic throws an error here for some reason. Damon, 8/12/06.
          String mimeType = null;
              try{
              mimeType = RollerContext.getServletContext().getMimeType(pageLink);
          }
          catch( java.lang.NullPointerException npe ){
              //System.err.println("PreviewServlet: Encounter error when getting mime type.");
          }

   10.5) In '{temp}\roller\src\org\apache\roller\ui\authoring\struts\BookmarksAction.java' change line 142 to below.
          // Remove from parent collection. Damon, 14/12/2006.
           FolderData fd = pageModel.getFolder();
           Set bookmarkSet = fd.getBookmarks();

          for (int j = 0; j < bookmarks.length; j++)
          {
             bookmark = bmgr.getBookmark(bookmarks[j]);
             // Remove from parent collection. Damon, 14/12/2006.
             bookmarkSet.remove(bookmark);  
            
             bmgr.removeBookmark(bookmark);
          }


          Also line 375 for the method 'getFolder()' modify as below.

           /* Need to do this for WebLogic v8.1.5. If not bookmarks
              cannot be retrieved. Suspect cause of issue is due to
              WL using a child classloader for its JSP container.
              Damon, 13/12/06.
           */
           BookmarkManager bmgr = null;
           try{
               bmgr = RollerFactory.getRoller().getBookmarkManager();
               /* This forces lazy instantiation. For some reasons,
               if we don't force instantiation, the JSTL fails to get
               the object with Hibernate complaining that there is no
               session or session is closed. Suspect that WL JSP container
               actually loads its own copy of Hibernate during JSTL
               parsing, if anybody knows the real cause, i would appreciate
               if you can enlighten me on exactly what's
               happening. ;P
               Damon, 14/12/2006 Email: damonchong@aim.com
               */
               folder.getBookmarks().toString();
           }
           catch(Exception e){
               //e.printStackTrace();
               try{
                   /* For some unknown reasons, the folder instance
                   becomes stale especially when performing deletion of
                   bookmarks and bookmark folders. Thus, we need to refresh
                   it again. Damon, 14/12/2006.
                   */
                   folder = bmgr.getFolder(folder.getId());
               }
               catch(Exception ex){
                   ex.printStackTrace();
               }
           }
           return folder;


   10.6) In '{temp}\roller\src\org\apache\roller\ui\authoring\struts\InvitationsAction.java' modify from line 150 onwards, codes affecting JNDI lookup.   
   10.7) In '{temp}\roller\src\org\apache\roller\ui\authoring\struts\InviteMemberAction.java' modify from line 197 onwards, codes affecting JNDI lookup.

   
11) In '{temp}\roller\src\org\apache\roller\config\RollerRuntimeConfig.java', modify method 'isFrontPageWeblog' by adding after the line 'String frontPageHandle = getProperty("site.frontpage.weblog.handle");'.

          // Handle for Oracle DB, suspect null value for varchar2 is
          // not auto convert to empty string. Damon @ 8 Dec 2006
          if(null==frontPageHandle)
              frontPageHandle = "";


12) Update '{DefaultApp}\roller\WEB-INF\security.xml' change all instance of 'java:comp/env/jdbc/rollerdb' to 'jdbc/rollerdb'.
13) Do a compilation at '{temp}\roller' and copy 'roller-business.jar' and 'roller-web.jar' from '{temp}\roller\build\lib' to '{DefaultApp}\roller\WEB-INF\lib'.
14) Do a build of the 'DefaultApp' in Workshop and deploy.
15) Go to URL http://localhost:7001/roller to view Roller weblog. Enjoy!