/*
* AdminFormHandler.java
*
* This work is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published
* by the Free Software Foundation; either version 2 of the License,
* or (at your option) any later version.
*
* This work is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
* USA
*
* Copyright (c) 2004-2005 Per Cederberg. All rights reserved.
*/
package org.liquidsite.app.admin;
import org.liquidsite.app.admin.view.AdminView;
import org.liquidsite.core.content.Content;
import org.liquidsite.core.content.ContentException;
import org.liquidsite.core.content.ContentManager;
import org.liquidsite.core.content.ContentSecurityException;
import org.liquidsite.core.content.Domain;
import org.liquidsite.core.content.Lock;
import org.liquidsite.core.content.User;
import org.liquidsite.core.web.FormHandler;
import org.liquidsite.core.web.FormHandlingException;
import org.liquidsite.core.web.FormValidationException;
import org.liquidsite.core.web.Request;
import org.liquidsite.util.log.Log;
/**
* The administration base form request handler. This class provides
* some of the basic form handling for all administration workflows.
*
* @author Per Cederberg, <per at percederberg dot net>
* @version 1.0
*/
abstract class AdminFormHandler extends FormHandler {
/**
* The class logger.
*/
private static final Log LOG = new Log(AdminFormHandler.class);
/**
* The default start page for the workflow. This is where
* redirects go when the workflow is finished unless another url
* is specified in the form.
*/
private String startPage;
/**
* The form page for the workflow. This is where all form request
* will be posted.
*/
private String formPage;
/**
* The automatic content object lock flag. When this flag is set,
* the content objects referenced will be locked and unlocked
* upon entering and exiting the workflow.
*/
private boolean autoLock;
/**
* Creates a new administration form handler. If the content lock
* flag is set, a referenced content object will be locked when
* starting the workflow. Upon further requests the existance of
* the lock will be checked. When the workflow exits, finally,
* the lock will be removed.
*
* @param start the start page (originating page)
* @param page the form page
* @param lock the content locking flag
*/
protected AdminFormHandler(String start, String page, boolean lock) {
this.startPage = start;
this.formPage = page;
this.autoLock = lock;
}
/**
* Returns the default start page for the workflow. This is where
* redirects go when the workflow is finished, unless a start page
* is specified in the form.
*
* @return the start page for the workflow
*/
public String getStartPage() {
return startPage;
}
/**
* Returns the form page for the workflow. This is where all form
* request will be posted.
*
* @return the form page for the workflow
*/
public String getFormPage() {
return formPage;
}
/**
* Displays a form for the specified workflow step. The special
* workflow step zero (0) is used for indicating display of the
* originating page outside the form workflow. Normally that
* would cause a redirect.
*
* @param request the request object
* @param step the workflow step, or zero (0)
*
* @throws FormHandlingException if an error was encountered
* while processing the form
*/
protected final void display(Request request, int step)
throws FormHandlingException {
if (step == 0) {
displayDone(request);
} else {
try {
displayStep(request, step);
} catch (ContentException e) {
LOG.error(e.getMessage());
throw new FormHandlingException(e);
} catch (ContentSecurityException e) {
LOG.warning(e.getMessage());
throw new FormHandlingException(e);
}
}
}
/**
* Displays a form for the specified workflow step. This method
* will NOT be called when returning to the start page.
*
* @param request the request object
* @param step the workflow step
*
* @throws ContentException if the database couldn't be accessed
* properly
* @throws ContentSecurityException if the user didn't have the
* required permissions
*/
protected abstract void displayStep(Request request, int step)
throws ContentException, ContentSecurityException;
/**
* Displays the workflow finished page. By default this method
* sends a redirect to the start page.
*
* @param request the request object
*/
protected void displayDone(Request request) {
String url;
url = request.getParameter("liquidsite.startpage", startPage);
AdminView.BASE.viewRedirect(request, url);
}
/**
* Displays the workflow error page. By default this method
* displays the site view error page and then redirects to the
* start page.
*
* @param request the request object
* @param message the error message
*/
protected void displayError(Request request, String message) {
String url;
url = request.getParameter("liquidsite.startpage", startPage);
AdminView.BASE.viewError(request, message, url);
}
/**
* Validates a form for the specified workflow step. If the form
* validation fails in this step, the form page for the workflow
* step will be displayed again with an 'error' attribute
* containing the message in the validation exception.
*
* @param request the request object
* @param step the workflow step
*
* @throws FormHandlingException if an error was encountered
* while processing the form
* @throws FormValidationException if the form request data
* validation failed
*/
protected final void validate(Request request, int step)
throws FormHandlingException, FormValidationException {
Object ref;
try {
if (autoLock) {
ref = AdminUtils.getReference(request);
if (ref instanceof Content) {
lock((Content) ref, request.getUser(), false);
}
}
validateStep(request, step);
} catch (ContentException e) {
LOG.error(e.getMessage());
throw new FormHandlingException(e);
} catch (ContentSecurityException e) {
LOG.warning(e.getMessage());
throw new FormHandlingException(e);
}
}
/**
* Validates a form for the specified workflow step. If the form
* validation fails in this step, the form page for the workflow
* step will be displayed again with an 'error' attribute
* containing the message in the validation exception.
*
* @param request the request object
* @param step the workflow step
*
* @throws ContentException if the database couldn't be accessed
* properly
* @throws ContentSecurityException if the user didn't have the
* required permissions
* @throws FormValidationException if the form request data
* validation failed
*/
protected abstract void validateStep(Request request, int step)
throws ContentException, ContentSecurityException,
FormValidationException;
/**
* Validates a content name with regard to its parent. That is,
* this method will check that no other content objects with the
* same parent have the same name.
*
* @param request the request object
* @param field the parent id field
* @param error the message to use on validation error
*
* @throws ContentException if the database couldn't be accessed
* properly
* @throws ContentSecurityException if the user didn't have the
* required permissions
* @throws FormValidationException if the form request data
* validation failed
*/
protected void validateParent(Request request,
String field,
String error)
throws ContentException, ContentSecurityException,
FormValidationException {
ContentManager manager = AdminUtils.getContentManager();
String name;
String parentId;
Object parent;
int id;
Content content;
// Find parent object and content object id
name = request.getParameter("name");
parentId = request.getParameter(field);
if (parentId == null) {
parent = AdminUtils.getReference(request);
id = 0;
} else {
content = (Content) AdminUtils.getReference(request);
try {
id = Integer.parseInt(parentId);
if (id <= 0) {
parent = content.getDomain();
} else {
parent = manager.getContent(request.getUser(), id);
}
} catch (NumberFormatException ignore) {
parent = content.getDomain();
}
id = content.getId();
}
// Check for existing child with identical name
if (parent instanceof Domain) {
content = manager.getContentChild(request.getUser(),
(Domain) parent,
name);
} else {
content = manager.getContentChild(request.getUser(),
(Content) parent,
name);
}
if (content != null && content.getId() != id) {
throw new FormValidationException("name", error);
}
}
/**
* Handles a validated form for the specified workflow step. This
* method returns the next workflow step, i.e. the step used when
* calling the display method. If the special zero (0) workflow
* step is returned, the workflow is assumed to have terminated.
* Note that this method also allows additional validation to
* occur. By returning the incoming workflow step number and
* setting the appropriate request attributes the same results as
* in the normal validate method can be achieved. For recoverable
* errors, this is the recommended course of action.
*
* @param request the request object
* @param step the workflow step
*
* @return the next workflow step, or
* zero (0) if the workflow has finished
*
* @throws FormHandlingException if an error was encountered
* while processing the form
*/
protected final int handle(Request request, int step)
throws FormHandlingException {
try {
return handleStep(request, step);
} catch (ContentException e) {
LOG.error(e.getMessage());
throw new FormHandlingException(e);
} catch (ContentSecurityException e) {
LOG.warning(e.getMessage());
throw new FormHandlingException(e);
}
}
/**
* Handles a validated form for the specified workflow step. This
* method returns the next workflow step, i.e. the step used when
* calling the display method. If the special zero (0) workflow
* step is returned, the workflow is assumed to have terminated.
* Note that this method also allows additional validation to
* occur. By returning the incoming workflow step number and
* setting the appropriate request attributes the same results as
* in the normal validate method can be achieved. For recoverable
* errors, this is the recommended course of action.
*
* @param request the request object
* @param step the workflow step
*
* @return the next workflow step, or
* zero (0) if the workflow has finished
*
* @throws ContentException if the database couldn't be accessed
* properly
* @throws ContentSecurityException if the user didn't have the
* required permissions
*/
protected abstract int handleStep(Request request, int step)
throws ContentException, ContentSecurityException;
/**
* This method locks content objects when entering the workflow.
* Nothing will be done if the lock flag hasn't been set of if
* the request doesn't reference a content object.
*
* @param request the request object
*
* @throws FormHandlingException if an error was encountered
* while processing the form
*/
protected void workflowEntered(Request request)
throws FormHandlingException {
Object ref;
if (autoLock) {
try {
ref = AdminUtils.getReference(request);
if (ref instanceof Content) {
lock((Content) ref, request.getUser(), true);
}
} catch (ContentException e) {
LOG.error(e.getMessage());
throw new FormHandlingException(e);
} catch (ContentSecurityException e) {
LOG.warning(e.getMessage());
throw new FormHandlingException(e);
}
}
}
/**
* This method unlocks content objects when exiting the workflow.
* Nothing will be done if the lock flag hasn't been set of if
* the request doesn't reference a content object.
*
* @param request the request object
*
* @throws FormHandlingException if an error was encountered
* while processing the form
*/
protected void workflowExited(Request request)
throws FormHandlingException {
Object ref;
if (autoLock) {
try {
ref = AdminUtils.getReference(request);
if (ref instanceof Content) {
unlock((Content) ref, request.getUser(), false);
}
} catch (ContentException e) {
LOG.error(e.getMessage());
throw new FormHandlingException(e);
} catch (ContentSecurityException e) {
LOG.warning(e.getMessage());
throw new FormHandlingException(e);
}
}
}
/**
* This method is called when an error was encountered during the
* processing. This event can be used for logging errors and
* displaying a simple error page. By default this method does
* nothing.
*
* @param request the request object
* @param e the form handling exception
*/
protected void workflowError(Request request, FormHandlingException e) {
displayError(request, e.getMessage());
}
/**
* Checks or acquires a content lock. This method will verify
* that any existing lock is owned by the correct user. If the
* acquire flag is not set, an existing lock will be checked for.
* If such a lock does not belong to the specified user, an
* exception will be thrown.
*
* @param content the content object
* @param user the user acquiring the lock
* @param acquire the acquire lock flag
*
* @throws ContentException if the database couldn't be accessed
* properly
* @throws ContentSecurityException if the user didn't own and
* couldn't acquire the lock
*/
protected void lock(Content content, User user, boolean acquire)
throws ContentException, ContentSecurityException {
Lock lock = content.getLock();
if (lock == null && acquire) {
lock = new Lock(AdminUtils.getContentManager(), content);
lock.save(user);
} else if (lock == null) {
throw new ContentSecurityException(
"object is not locked by " + user.getName());
} else if (!lock.isOwner(user)) {
throw new ContentSecurityException(
"object locked by " + lock.getUserName() +
" since " + lock.getAcquiredDate());
}
}
/**
* Removes a content lock. This method will quietly ignore a
* missing lock or a lock owner by another user. If the force
* flag is specified, any existing lock will be removed.
*
* @param content the content object
* @param user the user removing the lock
* @param force the force removal flag
*
* @throws ContentException if the database couldn't be accessed
* properly
* @throws ContentSecurityException if the user didn't have
* permission to remove the lock
*/
protected void unlock(Content content, User user, boolean force)
throws ContentException, ContentSecurityException {
Lock lock = content.getLock();
if (lock == null) {
// Do nothing
} else if (lock.isOwner(user) || force) {
lock.delete(user);
}
}
/**
* Converts a file name to a valid file name. All space
* characters and non-ASCII letters will be converted to '_' or
* '-'.
*
* @param name the file name to convert
*
* @return the converted file name
*/
protected String convertFileName(String name) {
StringBuffer buffer = new StringBuffer();
char c;
for (int i = 0; i < name.length(); i++) {
c = name.charAt(i);
if (c == ' ') {
buffer.append("_");
} else if (Content.NAME_CHARS.indexOf(c) >= 0) {
buffer.append(c);
} else {
buffer.append("-");
}
}
return buffer.toString();
}
}