To fully integrate Spine with struts you will need to perform the activities defined below.
  1. Initialization
  2. Strategies For Usage
  3. Additional Libraries
> Initialization

To use spine with struts, you will need to  initialize the spine engines  core variables. This can  be done by either:

  1. Writing a custom struts plugin and using the struts-configuration file to specify the spine default variables. A map containing the properties of spine's defined variables must then be used to initialize spine's ApplicationConfigurator  object. 

    You should  read the information provided in section 2 for further details on using this method.

  2. Downloading the files provided for integrating spine with struts at the spine framework website. A custom struts plugin is provided with an example struts configuration file.

    You will need to copy and paste the definitions for the Spine plugin into your struts configuration files as specified in the example struts configuration file. The basic definition is shown below:

    <plug-in className="com.zphinx.spine.plugin.StrutsInitPlugIn">
       <set-property property="startConfigFile" value="/WEB-INF/spine-init.xml" />
         
      </plug-in>


    If you wish to add additional fuctionality at startup you may pass initialization parameters via the plugin. A SpinePlugin must then be defined which will use this additional properties to create whatever objects it wishes to initialize.

    The example provided uses a RoleSpinePlugin to initialize the groups needed by the ActiveGroups object.

    NB: The default role configuration in spine is based on a flat file implementation. Users are encouraged to implement a database based role system as shown in the provided example.

  3. Or extending the Struts servlet and adding custom name/value properties to the web.xml  configuration properties.
    You will the need to create a map from the properties defined which will then be passed to the sole instance of the ApplicationConfigurator. This must be done in the Servlet.init() method which is loaded at startup. An example of how this is achieved in the Servlet.init() method is shown below:

/**
     * Adds additional
     *
     * @see javax.servlet.GenericServlet#init(javax.servlet.ServletConfig)
     */
    @Override
    public void init(ServletConfig config) throws ServletException {
        super.init(config);
        ServletContext context = config.getServletContext();
        Enumeration e = config.getInitParameterNames();
        Map configMap = null;
        String initFile = null;

        while (e.hasMoreElements()){
            String parameterName = (String) e.nextElement();
            String parameterValue = config.getInitParameter(parameterName);

//check that only non null values are used          

            if(((parameterName != null) && (parameterName.trim().length() > 0)) && ((parameterValue != null) && (parameterValue.trim().length() >   0))){
                System.out.println("The name is: " + parameterName + " value is: " + parameterValue);
               
               //add the realpath for all configuration names ending with the word 'File'
               
               if(parameterName.endsWith("File"){
                    parameterValue = context.getRealPath(parameterValue);
                }
                if(parameterName.equalsIgnoreCase("startConfigFile")){
                    initFile = parameterValue;
                 }

            }
            else{
                System.err.println("Name/Value pair improper!! name: " + parameterName + " value: " + parameterValue);
            }
        }
        ConfigReader confReader = new ConfigReader();
        configMap = confReader.createConfig(initFile);
        ApplicationConfigurator ap = ApplicationConfigurator.getInstance();

        ap.configure(configMap);
    }

You will also need to define the minimal required variables in the web.xml as shown below:

    <servlet>
        <servlet-name>action</servlet-name>
        <servlet-class>
            foo.bar.ActionExtensionServlet
        </servlet-class>

        <init-param>
            <param-name>config</param-name>
            <param-value>/WEB-INF/struts-config.xml</param-value>
        </init-param>

        <init-param>
            <param-name>validate</param-name>
            <param-value>true</param-value>
        </init-param>
        <init-param>
            <param-name>startConfigFile</param-name>
            <param-value>/WEB-INF/spine-init.xml</param-value>
        </init-param>
        <load-on-startup>2</load-on-startup>
       
    </servlet>

If you wish to add additional fuctionality at startup you may pass initialization parameters via a SpinePlugin. A SpinePlugin must then be defined which will use this additional properties to create whatever objects it wishes to initialize.


 
>
Strategies For Usage


  1. Separating Business Logic from the MVC

  2. Calling ViewProcessors From an Action

  3. Using a DispatchAction With MultiViewProcessors

Separating Business Logic from the MVC



The idea behind using an api like Spine is to ensure that all layers of application infrastructure are cleanly separated from each other.

To effectively use spine  all business logic must be written in the appropriate BusinessDelegate with the calling class performing the role of a validator, front controller,DTO packager and result interpreter for the business process.

This is done to ensure that the application which is been created can be easily adapted to use any other MVC system without needing to make any changes to the business or data tier of the application. It should also be possible to easily use differing view systems to access the same application which obeys this principles.

Client developers are advised that the ViewProcessor is not part of the business tier but acts as a connector between the business tier and the  front controller.
 

Calling ViewProcessors From an Action



To call a ViewProcessor from within a struts action, the client developer need only pass the alias of the ViewProcessor as defined in the configuration file to the ViewProcessorFactory. A ViewProcessor is obtained and its processData method is called to obtain a ResultObject will then be obtained which contains:

  • The returned object created by the invocation
  • A flag indicating success or failure of the invocation
  • Success and/or error messages detailing the invocation process
To comply with the architectural definition, the StrutsAction must perform validation and instantiate other objects which may be needed by the invocation call to the ViewProcessor. These may include the DataSource,the DTO,the DAOInput object used to initialize the DAO and any other parameters that are needed by the invocation call.

If using a custom ViewProcessor, you may override its processNavigation method to send the StrutsAction a flag inidicating  where the  StrutsAction should forward  the  user, otherwise navigation may be handled directly in the StrutsAction.

Messages which are obtained from the ResultObject should also be sent to the user via the Struts message handling features or a custom taglib can be written to handle the errors directly.
An example is shown below:

public class ActivationAction extends Action{ 


        public ActionForward execute(final ActionMapping mapping,

            final ActionForm form, final HttpServletRequest request,
            final HttpServletResponse response) throws Exception {

        Common.setLocale(request);
        Locale locale = Common.getLocale(request);
        ActionForward forward = null;
        final ActionErrors errors = new ActionErrors();
        final ActionMessages messages = new ActionMessages();
        final String activationCode = request.getParameter("actc");
        final String userId = request.getParameter("uvc");
        if ((userId != null) && (activationCode != null)) {
            // create a DataTransferObject
            final StringAttributeBean lvb = new StringAttributeBean(userId,activationCode);

            final ResultObject resultObject = runProcessor("Activation",lvb,  null,-1);

            // process the result object
            if (!resultObject.isErrorFlag()) {
                final Identifier id = (Identifier) resultObject.getObj();
                messages.add(ActionMessages.GLOBAL_MESSAGE, new ActionMessage(
                        "activation.success.msg", id.getFirstName(), id
                                .getUserName(), id.getAccountSecrets()
                                .getPassword1()));
            } else {
                String s = resultObject.getDisplayMessages(locale).getAllErrorString();
                log.debug(s);
                errors.add(ActionMessages.GLOBAL_MESSAGE, new ActionMessage(
                        "activation.error"));
            }

        } else {
            errors.add(ActionMessages.GLOBAL_MESSAGE, new ActionMessage(
                    "activation.null.code"));
        }
        if (errors.isEmpty()) {
            forward = mapping.findForward("activation.success");
            request.setAttribute("messageTitle",
                    "Your Registration Has Been Verified!!");
            if (!messages.isEmpty()) {
                saveMessages(request, messages);
            }

        } else {
            forward = mapping.findForward("activation.failure");
            request.setAttribute("messageTitle", "Registration Failure!!");
            saveMessages(request, errors);

        }

        return forward;
    }


    /**
     * Runs the process from view to data layer, accepts null as the DAOInput value
     *
     * @param processorName The name/key of the processor we are using
     * @param dto The dto which will be used by the system while processing this request
     * @param request The http request in use by this invocation
     * @param key The key by which the datasource is known in configuration
     * @param daoConstructor The constructor object used by the data access object for this process
     * @return A result object containing the result of this invocation
     */
    public ResultObject runProcessor(String processorName, DataTransferObject dto, DAOInput daoConstructor,int operation) {
       DataSource dataSource = DataSourceServiceLocator.getDataSource();
return getProcessor(processorName).processData(dto, dataSource, daoConstructor,operation);

    }

/**
     * Invokes the named view processor and returns a copy for further use
     *
     * @param processorName
     *            The name of the processor to invoke
     */
    private ViewProcessor getProcessor(final String processorName) {
        return ViewProcessorFactory.getInstance().createProcessor( processorName);
    }

   
}



Using a DispatchAction With MultiViewProcessors



A
ll the rules above apply to DispatchActions but Spine goes a step further to provide a means of declaring the main object been processed via configuration, it also makes it possible to use one BusinessDelegate to service all the methods of the DispatchAction whilst providing a Builder pattern (object) which can be used to instantiate,passivate,service or delegate methods needed by the managedobjects used in this  configuration.

This  makes it posible to use a MultiViewProcessor with a DispatchAction as compared to using several ViewProcessors with one Struts DispatchAction. A typical MultiViewProcessor configuration is as shown below:

    <processor name="MultiViewProcessor">
        <processorClass>com.zphinx.spine.unittests.impl.processors.MultiViewTestProcessor </processorClass>
        <multiDelegate>
            <subclass> com.zphinx.spine.unittests.impl.delegates.MultiViewTestDelegate</subclass>
           
               <managedObject>
                <objectClass>com.zphinx.spine.examples.beans.SpineBean1</objectClass>
                <dataAccessObject>
                    <className>com.zphinx.spine.examples.dao.SpineBean1DAO</className>
                    <proxyIndex>1</proxyIndex>
                </dataAccessObject>
                <pageIndex>1</pageIndex>
            </managedObject>

            <managedObject>

                <objectClass>com.zphinx.spine.examples.beans.SpineBean2</objectClass>
                <dataAccessObject>
                    <className>com.zphinx.spine.examples.dao.SpineBean2DAO</className>
                    <proxyIndex>2</proxyIndex>
                </dataAccessObject>
                <pageIndex>2</pageIndex>
            </managedObject>
            <managedObject>
                <objectClass>com.zphinx.spine.examples.beans.SpineBean3</objectClass>
                <dataAccessObject>
                       <className> com.zphinx.spine.examples.dao.SpineBean3DAO
</className>
                     <proxyIndex>2</proxyIndex>
                </dataAccessObject>
                <pageIndex>3</pageIndex>
            </managedObject>

            <builder>com.zphinx.spine.examples.processors.SomeBuilder</builder>
        </multiDelegate>
    </processor>

To use a configuration like that shown above, the DispatchAction or any of its affiliated objects will instantiate the ViewProcessor from the ViewProcessorFactory, the developer can then create a SpineBean from a builder specified in configuration and subsequently invoke the process flow. eg:

try{
      DataSource dataSource =
DataSourceServiceLocator.getDataSource(); 
      MultiViewProcessor mvp = ViewProcessorFactory.getInstance().createProcessor( processorClass);
      SomeBuilder builder = mvp.findBuilder("multiViewProcessor","MultiViewTestDelegate");

//create a bean for your use
     SpineBean2 sp2 = builder.createBean(
2);
//popuate the bean's methods
     sp2.setModifiedDate(new Date());

//to invoke a process and run methods in the business delegate
    
ResultObject resultObject = mvp.processData(sp2, dataSource,null,4,-1);
//process the ResultObject as in the above example

}
catch(Exception e){
     e.printStackTrace();
}


It can be seen from the example above that passing a pageNumber/pageIndex to the  MultiViewProcessor will  indicate to the  MultiViewProcessor which object is been processed and which DataAccessObject should be invoked for the particular process flow.

User should note that in certain instances (eg invoking the builder) there  is a need to pass the name of the BusinessDelegate in use to the stated method. This is because a MultiViewProcessor is capable of using more than one BusinessDelegate to service it's request.

 
>
Additional Libraries


To use spine with the struts api, you will need to have the struts library in your classpath. This can usually be deployed in your /path_to_application_context/WEB_INF/lib/.

You may also deploy any other libaries in this directory to ensure that you application  is able to access the required class libraries. You may download the latest copy of the struts library at the apache struts website


<< Back    |     Home    |     Index    |     Forward >>

Copyright © 2008 Zphinx Software Solutions , all rights reserved