blob: 3b3d86ccd666de34fa2b019458b1477d2d31f748 [file] [log] [blame]
/*
* Copyright 1999-2004 The Apache Software Foundation.
*
* 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.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.6 2004/03/05 13:02:37 bdelacretaz 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;
}
}