/*
============================================================================
The Apache Software License, Version 1.1
============================================================================
Copyright (C) 1999-2003 The Apache Software Foundation. All rights reserved.
Redistribution and use in source and binary forms, with or without modifica-
tion, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. The end-user documentation included with the redistribution, if any, must
include the following acknowledgment: "This product includes software
developed by the Apache Software Foundation (http://www.apache.org/)."
Alternately, this acknowledgment may appear in the software itself, if
and wherever such third-party acknowledgments normally appear.
4. The names "Apache Cocoon" and "Apache Software Foundation" must not be
used to endorse or promote products derived from this software without
prior written permission. For written permission, please contact
apache@apache.org.
5. Products derived from this software may not be called "Apache", nor may
"Apache" appear in their name, without prior written permission of the
Apache Software Foundation.
THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
APACHE SOFTWARE FOUNDATION OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLU-
DING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
This software consists of voluntary contributions made by many individuals
on behalf of the Apache Software Foundation and was originally created by
Stefano Mazzocchi <stefano@apache.org>. For more information on the Apache
Software Foundation, please see <http://www.apache.org/>.
*/
package org.apache.cocoon.acting;
import org.apache.avalon.excalibur.pool.Poolable;
import org.apache.avalon.excalibur.pool.Recyclable;
import org.apache.avalon.framework.CascadingRuntimeException;
import org.apache.avalon.framework.parameters.Parameters;
import org.apache.cocoon.Constants;
import org.apache.cocoon.components.source.SourceUtil;
import org.apache.cocoon.components.validation.Schema;
import org.apache.cocoon.components.validation.SchemaFactory;
import org.apache.cocoon.components.validation.Validator;
import org.apache.cocoon.components.xmlform.Form;
import org.apache.cocoon.components.xmlform.FormListener;
import org.apache.cocoon.environment.ObjectModelHelper;
import org.apache.cocoon.environment.Redirector;
import org.apache.cocoon.environment.Request;
import org.apache.cocoon.environment.Session;
import org.apache.cocoon.environment.SourceResolver;
import org.apache.excalibur.source.Source;
import org.xml.sax.InputSource;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
/**
* This is the base action class for
* xmlform handling.
*
* This action is Poolable which means that
* subclasses of this class should not be
* concerned about thread safety.
* The framework ensures that only one thread
* has access to a concrete instance at any time.
*
* However once an action is complete, the instance
* will be recycled and reused for another request.
*
*
* Several ideas are borrowed from the original work of
* Torsten Curdt.
*
* @author Ivelin Ivanov <ivelin@apache.org>
* @version CVS $Id: AbstractXMLFormAction.java,v 1.5 2003/10/15 20:47:15 cziegeler Exp $
*/
public abstract class AbstractXMLFormAction
extends ConfigurableServiceableAction
implements Poolable, Recyclable, FormListener {
public static final String OBJECT_MAP_NEXT_PAGE = "page";
protected static final Map PREPARE_RESULT_CONTINUE = null;
// action state objects
private Redirector redirector_;
private SourceResolver resolver_;
private Map objectModel_;
private Parameters params_;
private String src_;
private String command_;
/**
* The first method which is called
* when an action is invoked.
*
* It is called before population.
*
* @return null if the Action is prepared to continue.
* an objectModel map which will be immediately returned by the action.
*
* This method is a good place to handle buttons with Cancel
* kind of semantics. For example
* <pre>return page("input")</pre>
*/
protected Map prepare() {
// by default, assume that there is
// no preparation needed
return PREPARE_RESULT_CONTINUE;
}
/**
* FormListener callback
* called in the beginning of Form.populate()
* before population starts.
*
* This is the place to intialize the model for this request.
*
* This method should not handle unchecked check boxes
* when the form is session scope, which is the most common case.
* It should only do so, if the form is request scoped.
*
* @param form
*/
public void reset(Form form) {
// Do Nothing by default
return;
}
/**
* FormListener callback.
*
* Invoked during Form.populate();
*
* It is invoked before a request parameter is mapped to
* an attribute of the form model.
*
* It is appropriate to use this method for filtering
* custom request parameters which do not reference
* the model.
*
* Another appropriate use of this method is for graceful filtering of invalid
* values, in case that knowledge of the system state or
* other circumstainces make the standard validation
* insufficient. For example if a registering user choses a username which
* is already taken - the check requires database transaction, which is
* beyond the scope of document validating schemas.
* Of course customized Validators can be implemented to do
* this kind of domain specific validation
* instead of using this method.
*
* @param form
* @param parameterName
* @return false if the request parameter should not be filtered.
* true otherwise.
*/
public boolean filterRequestParameter(Form form, String parameterName) {
// in this example we do not expect "custom" parameters
return false;
}
/**
* Invoked during the form population process
*
* Provides default implementation, which
* can be extended or replaced by subclasses
*
* Implementations of this method are responsible
* for creating and
* returning the Form object which the action
* is working on.
*
* @return Form the form object this action works with
*/
protected Form getForm() {
Form form = Form.lookup(getObjectModel(), getFormId());
if (form!=null) {
return form;
} else {
// create new form
form = new Form(getFormId(), getFormModel());
Validator v = getFormValidator();
form.setValidator(v);
form.save(getObjectModel(), getFormScope());
return form;
}
}
public Map act(Redirector redirector, SourceResolver resolver,
Map objectModel, String src,
Parameters params) throws Exception {
// populate action state objects
redirector_ = redirector;
resolver_ = resolver;
objectModel_ = objectModel;
src_ = src;
params_ = params;
// ensure that there is a form available
// through the rest of the flow
Form form = getForm();
if (form==null) {
throw new IllegalStateException("Action could not obtain the Form");
}
// find and save the action command
findCommand();
// call the subclass prepare()
// give it a chance to get ready for action
Map prepareResult = prepare();
if (prepareResult!=null) {
return prepareResult;
}
// attache callback hooks to the form
// in case the action subclasses are interested in
// form events
getForm().addFormListener(this);
Map result = null;
try {
// populate form with request parameters
// population is automatically followed by validation by default.
// If this is not the desired behaviour, the Form class can be subclassed
form.populate(objectModel);
result = perform();
} finally {
// since the action may be recycled immediately after
// the request. It is important that it's callback hooks
// are removed from the Form.
getForm().removeFormListener(this);
}
return result;
}
/**
* Get the command which was submitted with the form.
* It is extracted from the standard cocoon-action-* request parameter
*
*/
public String getCommand() {
return command_;
}
protected void findCommand() {
command_ = null;
Enumeration enum = getRequest().getParameterNames();
while (enum.hasMoreElements()) {
String paramName = (String) enum.nextElement();
// search for the command
if (paramName.startsWith(Constants.ACTION_PARAM_PREFIX)) {
command_ = paramName.substring(Constants.ACTION_PARAM_PREFIX.length(),
paramName.length());
}
}
}
/**
* @return the @view attribute of the xmlform form tag.
* This attribute is used to identify the part(or view)
* of the model which is used in the specific xmlform
* document.
*
*/
public String getFormView() {
return getForm().getFormView(getObjectModel());
}
/**
* Called to determine the exit point of an action.
* The pageName is made available in the objectMap,
* which can be then referenced in the pipeline
* @param pageName logical name for a next page
* @return Map a pipeline objectMap containing the pageName
*/
protected Map page(String pageName) {
Map objectModel = new HashMap();
objectModel.put(OBJECT_MAP_NEXT_PAGE, pageName);
return objectModel;
}
/**
* Invoked after form population
* unless a Cancel button was pressed,
* in which case population is skipped and this method
* is invoked immediately.
*
* Semanticly similar to Struts Action.perform()
*
* Take appropriate action based on the command.
*/
public abstract Map perform();
protected SourceResolver getSourceResolver() {
return resolver_;
}
protected Redirector getRedirector() {
return redirector_;
}
protected Map getObjectModel() {
return objectModel_;
}
protected Parameters getParameters() {
return params_;
}
protected String getSrc() {
return src_;
}
protected Request getRequest() {
return (Request) (getObjectModel().get(ObjectModelHelper.REQUEST_OBJECT));
}
protected Session getSession(boolean shouldCreateNew) {
return getRequest().getSession(shouldCreateNew);
}
protected Session getSession() {
return getSession(true);
}
/**
* Extract action parameters and
* instantiate a new validator based on them.
*
* xmlform-validator-schema-ns
* xmlform-validator-schema
*
* Subclasses may override this method
* to use custom validators
*
*/
protected Validator getFormValidator() {
try {
// initialize the Validor with a schema file
String schNS = getParameters().getParameter("xmlform-validator-schema-ns",
null);
String schDoc = getParameters().getParameter("xmlform-validator-schema",
null);
// if validator params are not specified, then
// there is no validation by default
if ((schNS==null) || (schDoc==null)) {
return null;
}
Source schemaSrc = getSourceResolver().resolveURI(schDoc);
try {
InputSource is = SourceUtil.getInputSource(schemaSrc);
SchemaFactory schf = SchemaFactory.lookup(schNS);
Schema sch = schf.compileSchema(is);
return sch.newValidator();
} finally {
getSourceResolver().release(schemaSrc);
}
} catch (Exception e) {
// couldn't load the validator
throw new CascadingRuntimeException(" Failed loading validating schema ",
e);
}
}
/**
* Extract xmlform-model action parameter and
* instantiate a new form model it.
*
* Subclasses may override this method
* to use custom model instantiation technique
*
*/
protected Object getFormModel() {
try {
String modelClassName = getParameters().getParameter("xmlform-model",
null);
Class modelClass = Class.forName(modelClassName);
Object o = modelClass.newInstance();
return o;
} catch (Exception e) {
throw new CascadingRuntimeException(" Failed instantiating form model ",
e);
}
}
protected String getFormId() {
String formId = getParameters().getParameter("xmlform-id", null);
if (formId==null) {
throw new RuntimeException(" xmlform-id not specified ");
} else {
return formId;
}
}
protected String getFormScope() {
String formScope = getParameters().getParameter("xmlform-scope",
null);
if (formScope==null) {
// default to request scope
formScope = Form.SCOPE_REQUEST;
}
return formScope;
}
/**
* Recycle this component.
*/
public void recycle() {
redirector_ = null;
resolver_ = null;
objectModel_ = null;
params_ = null;
src_ = null;
command_ = null;
}
}