Merge V1.4 to HEAD

- Ignored Enterprise-specific changes
   svn merge svn://svn.alfresco.com:3691/alfresco/BRANCHES/V1.4@3701 svn://svn.alfresco.com:3691/alfresco/BRANCHES/V1.4@3703 .
   svn merge svn://svn.alfresco.com:3691/alfresco/BRANCHES/V1.4@3704 svn://svn.alfresco.com:3691/alfresco/BRANCHES/V1.4@3705 .
   svn merge svn://svn.alfresco.com:3691/alfresco/BRANCHES/V1.4@3707 svn://svn.alfresco.com:3691/alfresco/BRANCHES/V1.4@3876 .
   svn revert root\projects\web-client\source\web\jsp\admin\admin-console.jsp


git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@3879 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
This commit is contained in:
Derek Hulley
2006-09-21 23:35:51 +00:00
parent 35594dadf8
commit 7df3c602a1
54 changed files with 1634 additions and 675 deletions

View File

@@ -996,10 +996,22 @@ reassign_select_user=Select the user to assign the task to, then press OK.
error_reassign_task=Unable to reassign the task due to system error:
part_of_workflow=Part of Workflow
initiated_by=Initiated by
start_date=Start date
started_on=Started on
add_resource=Add Resource
view_properties=View Content Properties
edit_properties=Edit Content Properties
save_changes=Save Changes
no_tasks=No tasks found.
no_resources=No resources found.
in_progress=In Progress
# Workflow Definitions
wf_review_due_date=Review Due Date
wf_review_priority=Review Priority
wf_reviewer=Reviewer
wf_adhoc_due_date=Due Date
wf_adhoc_priority=Priority
wf_adhoc_assignee=Assign To
# Admin Console messages
title_admin_console=Administration Console

View File

@@ -230,9 +230,21 @@
<property-sheet>
<separator name="sep1" display-label-id="general" component-generator="HeaderSeparatorGenerator" />
<show-property name="bpm:taskId" />
<show-property name="bpm:description" component-generator="TextAreaGenerator" read-only="true"/>
<show-property name="bpm:dueDate" read-only="true" />
<show-property name="bpm:priority" read-only="true" />
<show-property name="bpm:status" />
<show-property name="bpm:dueDate" />
<show-property name="bpm:priority" />
</property-sheet>
</config>
<config evaluator="node-type" condition="bpm:startTask" replace="true">
<property-sheet>
<separator name="sep1" display-label-id="general" component-generator="HeaderSeparatorGenerator" />
<show-property name="bpm:workflowDescription" component-generator="TextAreaGenerator" />
<show-property name="bpm:workflowPriority" />
<show-property name="bpm:workflowDueDate" />
<separator name="sep2" display-label-id="users_and_roles" component-generator="HeaderSeparatorGenerator" />
<show-association name="bpm:assignee" />
</property-sheet>
</config>
@@ -242,19 +254,21 @@
<property-sheet>
<separator name="sep1" display-label-id="general" component-generator="HeaderSeparatorGenerator" />
<show-property name="bpm:taskId" />
<show-property name="bpm:description" component-generator="TextAreaGenerator" read-only="true"/>
<show-property name="bpm:dueDate" read-only="true" />
<show-property name="bpm:priority" read-only="true" />
<show-property name="bpm:status" />
<show-property name="bpm:dueDate" />
<show-property name="bpm:priority" />
</property-sheet>
</config>
<config evaluator="node-type" condition="wf:submitReviewTask" replace="true">
<property-sheet>
<separator name="sep1" display-label-id="general" component-generator="HeaderSeparatorGenerator" />
<show-property name="wf:reviewPriority" />
<show-property name="wf:reviewDueDate" />
<show-property name="bpm:workflowDescription" component-generator="TextAreaGenerator" />
<show-property name="bpm:workflowPriority" display-label-id="wf_review_priority" />
<show-property name="bpm:workflowDueDate" display-label-id="wf_review_due_date" />
<separator name="sep2" display-label-id="users_and_roles" component-generator="HeaderSeparatorGenerator" />
<show-association name="wf:reviewer" />
<show-association name="bpm:assignee" display-label-id="wf_reviewer" />
</property-sheet>
</config>
@@ -263,12 +277,12 @@
<config evaluator="node-type" condition="wf:submitAdhocTask" replace="true">
<property-sheet>
<separator name="sep1" display-label-id="general" component-generator="HeaderSeparatorGenerator" />
<show-property name="wf:adhocDescription" component-generator="TextAreaGenerator" />
<show-property name="wf:adhocPriority" />
<show-property name="wf:adhocDueDate" />
<show-property name="bpm:workflowDescription" component-generator="TextAreaGenerator" />
<show-property name="bpm:workflowPriority" display-label-id="wf_adhoc_priority" />
<show-property name="bpm:workflowDueDate" display-label-id="wf_adhoc_due_date" />
<show-property name="wf:notifyMe" />
<separator name="sep2" display-label-id="users_and_roles" component-generator="HeaderSeparatorGenerator" />
<show-association name="wf:assignee" />
<separator name="sep2" display-label-id="user_filter_user" component-generator="HeaderSeparatorGenerator" />
<show-association name="bpm:assignee" display-label-id="wf_adhoc_assignee" />
</property-sheet>
</config>
@@ -276,10 +290,10 @@
<property-sheet>
<separator name="sep1" display-label-id="general" component-generator="HeaderSeparatorGenerator" />
<show-property name="bpm:taskId" />
<show-property name="wf:adhocDescription" ignore-if-missing="false" component-generator="TextAreaGenerator" />
<show-property name="bpm:description" component-generator="TextAreaGenerator" read-only="true" />
<show-property name="bpm:dueDate" read-only="true" />
<show-property name="bpm:priority" read-only="true" />
<show-property name="bpm:status" />
<show-property name="bpm:dueDate" />
<show-property name="bpm:priority" />
</property-sheet>
</config>
@@ -287,9 +301,9 @@
<property-sheet>
<separator name="sep1" display-label-id="general" component-generator="HeaderSeparatorGenerator" />
<show-property name="bpm:taskId" />
<show-property name="wf:adhocDescription" component-generator="TextAreaGenerator" />
<show-property name="bpm:dueDate" />
<show-property name="bpm:priority" />
<show-property name="bpm:description" component-generator="TextAreaGenerator" read-only="true" />
<show-property name="bpm:dueDate" read-only="true" />
<show-property name="bpm:priority" read-only="true" />
</property-sheet>
</config>

View File

@@ -13,19 +13,40 @@
</params>
</action>
<action id="manage_task">
<label-id>manage_task</label-id>
<image>/images/icons/manage_workflow_task.gif</image>
<action>dialog:manageTask</action>
<action-listener>#{DialogManager.setupParameters}</action-listener>
<params>
<param name="id">#{actionContext.id}</param>
</params>
</action>
<action id="view_completed_task">
<label-id>view_completed_task_title</label-id>
<image>/images/icons/view_workflow_task.gif</image>
<action>dialog:viewCompletedTask</action>
<action-listener>#{DialogManager.setupParameters}</action-listener>
<params>
<param name="id">#{actionContext.id}</param>
</params>
</action>
<action id="reassign_task">
<label-id>reassign</label-id>
<image>/images/icons/reassign_task.gif</image>
<action>dialog:reassignTask</action>
<action-listener>#{DialogManager.setupParameters}</action-listener>
<params>
<param name="task-id">#{actionContext.id}</param>
<param name="id">#{actionContext.id}</param>
</params>
</action>
<action id="cancel_workflow">
<label-id>cancel_workflow</label-id>
<image>/images/icons/cancel_workflow.gif</image>
<evaluator>org.alfresco.web.action.evaluator.CancelWorkflowEvaluator</evaluator>
<action>dialog:cancelWorkflow</action>
<action-listener>#{DialogManager.setupParameters}</action-listener>
<params>
@@ -51,7 +72,7 @@
<action id="view_content_properties">
<label-id>view_properties</label-id>
<image>/images/icons/View_details.gif</image>
<image>/images/icons/view_properties.gif</image>
<action>dialog:viewContentProperties</action>
<action-listener>#{BrowseBean.setupContentAction}</action-listener>
<params>
@@ -64,7 +85,7 @@
<permission allow="true">Write</permission>
</permissions>
<label-id>edit_properties</label-id>
<image>/images/icons/Change_details.gif</image>
<image>/images/icons/edit_properties.gif</image>
<action>dialog:editContentProperties</action>
<action-listener>#{BrowseBean.setupContentAction}</action-listener>
<params>
@@ -72,6 +93,45 @@
</params>
</action>
<!-- Checkin document from workflow package -->
<action id="workflow_checkin_doc">
<evaluator>org.alfresco.web.action.evaluator.CheckinDocEvaluator</evaluator>
<label-id>checkin</label-id>
<image>/images/icons/CheckIn_icon.gif</image>
<action-listener>#{CheckinCheckoutBean.setupWorkflowContentAction}</action-listener>
<action>dialog:checkinFile</action>
<params>
<param name="id">#{actionContext.id}</param>
<param name="taskId">#{actionContext.taskId}</param>
</params>
</action>
<!-- Checkout document from workflow package -->
<action id="workflow_checkout_doc">
<evaluator>org.alfresco.web.action.evaluator.CheckoutDocEvaluator</evaluator>
<label-id>checkout</label-id>
<image>/images/icons/CheckOut_icon.gif</image>
<action-listener>#{CheckinCheckoutBean.setupWorkflowContentAction}</action-listener>
<action>dialog:checkoutFile</action>
<params>
<param name="id">#{actionContext.id}</param>
<param name="taskId">#{actionContext.taskId}</param>
</params>
</action>
<!-- Cancel checkout document from workflow package -->
<action id="workflow_cancelcheckout_doc">
<evaluator>org.alfresco.web.action.evaluator.CancelCheckoutDocEvaluator</evaluator>
<label-id>undocheckout</label-id>
<image>/images/icons/undo_checkout.gif</image>
<action-listener>#{CheckinCheckoutBean.setupWorkflowContentAction}</action-listener>
<action>dialog:undoCheckoutFile</action>
<params>
<param name="id">#{actionContext.id}</param>
<param name="taskId">#{actionContext.taskId}</param>
</params>
</action>
<action-group id="document_browse_menu">
<action idref="start_workflow" />
</action-group>
@@ -81,10 +141,12 @@
</action-group>
<action-group id="dashlet_todo_actions">
<action idref="manage_task" />
<action idref="reassign_task" />
</action-group>
<action-group id="dashlet_completed_actions">
<action idref="view_completed_task" />
<action idref="cancel_workflow" />
</action-group>
@@ -104,9 +166,9 @@
<action idref="edit_doc_webdav" />
<action idref="edit_doc_cifs" />
<action idref="update_doc" />
<action idref="checkout_doc" />
<action idref="checkin_doc" />
<action idref="cancelcheckout_doc" />
<action idref="workflow_checkout_doc" />
<action idref="workflow_checkin_doc" />
<action idref="workflow_cancelcheckout_doc" />
</action-group>
<action-group id="workflow_collection_actions">

View File

@@ -0,0 +1,85 @@
/*
* Copyright (C) 2005 Alfresco, Inc.
*
* Licensed under the Mozilla Public License version 1.1
* with a permitted attribution clause. You may obtain a
* copy of the License at
*
* http://www.alfresco.org/legal/license.txt
*
* 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.alfresco.web.action.evaluator;
import javax.faces.context.FacesContext;
import org.alfresco.model.ContentModel;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.NodeService;
import org.alfresco.service.cmr.workflow.WorkflowService;
import org.alfresco.service.cmr.workflow.WorkflowTask;
import org.alfresco.util.ISO9075;
import org.alfresco.web.action.ActionEvaluator;
import org.alfresco.web.app.Application;
import org.alfresco.web.bean.repository.Node;
import org.alfresco.web.bean.repository.Repository;
import org.alfresco.web.bean.repository.User;
/**
* UI Action Evaluator for cancel workflow action. The action
* is only allowed if the workflow the task belongs to was
* started by the current user.
*
* @author gavinc
*/
public class CancelWorkflowEvaluator implements ActionEvaluator
{
/**
* @see org.alfresco.web.action.ActionEvaluator#evaluate(org.alfresco.web.bean.repository.Node)
*/
public boolean evaluate(Node node)
{
boolean result = false;
// get the id of the task
String taskId = (String)node.getProperties().get("id");
if (taskId != null)
{
FacesContext context = FacesContext.getCurrentInstance();
// get the initiator of the workflow the task belongs to
WorkflowService workflowSvc = Repository.getServiceRegistry(
context).getWorkflowService();
WorkflowTask task = workflowSvc.getTaskById(taskId);
if (task != null)
{
NodeRef initiator = task.path.instance.initiator;
if (initiator != null)
{
// find the current username
User user = Application.getCurrentUser(context);
String currentUserName = ISO9075.encode(user.getUserName());
// get the username of the initiator
NodeService nodeSvc = Repository.getServiceRegistry(
context).getNodeService();
String userName = (String)nodeSvc.getProperty(initiator, ContentModel.PROP_USERNAME);
// if the current user started the workflow allow the cancel action
if (currentUserName.equals(userName))
{
result = true;
}
}
}
}
return result;
}
}

View File

@@ -0,0 +1,110 @@
/*
* Copyright (C) 2005 Alfresco, Inc.
*
* Licensed under the Mozilla Public License version 1.1
* with a permitted attribution clause. You may obtain a
* copy of the License at
*
* http://www.alfresco.org/legal/license.txt
*
* 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.alfresco.web.app.servlet;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.alfresco.web.bean.repository.User;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* This servlet filter is used to restrict direct URL access to administration
* resource in the web client, for example the admin and jBPM consoles.
*
* @author gavinc
*/
public class AdminAuthenticationFilter implements Filter
{
private static final Log logger = LogFactory.getLog(AdminAuthenticationFilter.class);
/**
* @see javax.servlet.Filter#doFilter(javax.servlet.ServletRequest, javax.servlet.ServletResponse, javax.servlet.FilterChain)
*/
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException
{
HttpServletRequest httpRequest = (HttpServletRequest)req;
HttpServletResponse httpResponse = (HttpServletResponse)res;
// The fact that this filter is being called means a request for a protected
// resource has taken place, check that the current user is in fact an
// administrator.
if (logger.isDebugEnabled())
logger.debug("Authorising request for protected resource: " + httpRequest.getRequestURI());
// there should be a user at this point so retrieve it
User user = AuthenticationHelper.getUser(httpRequest, httpResponse);
// if the user is present check to see whether it is an admin user
boolean isAdmin = (user != null && user.isAdmin());
if (isAdmin)
{
if (logger.isDebugEnabled())
logger.debug("Current user has admin authority, allowing access.");
// continue filter chaining if current user is admin user
chain.doFilter(req, res);
}
else
{
// return the 401 Forbidden error as the current user is not an administrator
// if the response has already been committed there's nothing we can do but
// print out a warning
if (httpResponse.isCommitted() == false)
{
if (logger.isDebugEnabled())
logger.debug("Current user does not have admin authority, returning 401 Forbidden error...");
httpResponse.sendError(HttpServletResponse.SC_FORBIDDEN);
}
else
{
if (logger.isWarnEnabled())
logger.warn("Access denied to '" + httpRequest.getRequestURI() +
"'. The response has already been committed so a 401 Forbidden error could not be sent!");
}
}
}
/**
* @see javax.servlet.Filter#init(javax.servlet.FilterConfig)
*/
public void init(FilterConfig config) throws ServletException
{
// nothing to do
}
/**
* @see javax.servlet.Filter#destroy()
*/
public void destroy()
{
// nothing to do
}
}

View File

@@ -100,39 +100,15 @@ public final class AuthenticationHelper
{
HttpSession session = httpRequest.getSession();
// examine the appropriate session for our User object
User user = null;
// retrieve the User object
User user = getUser(httpRequest, httpResponse);
// get the login bean if we're not in the portal
LoginBean loginBean = null;
if (Application.inPortalServer() == false)
{
user = (User)session.getAttribute(AUTHENTICATION_USER);
loginBean = (LoginBean)session.getAttribute(LOGIN_BEAN);
}
else
{
// naff solution as we need to enumerate all session keys until we find the one that
// should match our User objects - this is weak but we don't know how the underlying
// Portal vendor has decided to encode the objects in the session
if (portalUserKeyName.get() == null)
{
String userKeyPostfix = "?" + AUTHENTICATION_USER;
Enumeration enumNames = session.getAttributeNames();
while (enumNames.hasMoreElements())
{
String name = (String)enumNames.nextElement();
if (name.endsWith(userKeyPostfix))
{
// cache the key value once found!
portalUserKeyName.set(name);
break;
}
}
}
if (portalUserKeyName.get() != null)
{
user = (User)session.getAttribute(portalUserKeyName.get());
}
}
// setup the authentication context
WebApplicationContext wc = WebApplicationContextUtils.getRequiredWebApplicationContext(context);
@@ -388,6 +364,52 @@ public final class AuthenticationHelper
return AuthenticationStatus.Failure;
}
/**
* Attempts to retrieve the User object stored in the current session.
*
* @param httpRequest The HTTP request
* @param httpResponse The HTTP response
* @return The User object representing the current user or null if it could not be found
*/
public static User getUser(HttpServletRequest httpRequest, HttpServletResponse httpResponse)
{
HttpSession session = httpRequest.getSession();
User user = null;
// examine the appropriate session to try and find the User object
if (Application.inPortalServer() == false)
{
user = (User)session.getAttribute(AUTHENTICATION_USER);
}
else
{
// naff solution as we need to enumerate all session keys until we find the one that
// should match our User objects - this is weak but we don't know how the underlying
// Portal vendor has decided to encode the objects in the session
if (portalUserKeyName.get() == null)
{
String userKeyPostfix = "?" + AUTHENTICATION_USER;
Enumeration enumNames = session.getAttributeNames();
while (enumNames.hasMoreElements())
{
String name = (String)enumNames.nextElement();
if (name.endsWith(userKeyPostfix))
{
// cache the key value once found!
portalUserKeyName.set(name);
break;
}
}
}
if (portalUserKeyName.get() != null)
{
user = (User)session.getAttribute(portalUserKeyName.get());
}
}
return user;
}
/**
* Setup the Alfresco auth cookie value.
*

View File

@@ -0,0 +1,301 @@
/*
* Copyright (C) 2005 Alfresco, Inc.
*
* Licensed under the Mozilla Public License version 1.1
* with a permitted attribution clause. You may obtain a
* copy of the License at
*
* http://www.alfresco.org/legal/license.txt
*
* 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.alfresco.web.app.servlet;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.SocketException;
import java.net.URLEncoder;
import java.text.MessageFormat;
import java.util.Date;
import java.util.StringTokenizer;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.alfresco.error.AlfrescoRuntimeException;
import org.alfresco.model.ContentModel;
import org.alfresco.repo.content.filestore.FileContentReader;
import org.alfresco.service.ServiceRegistry;
import org.alfresco.service.cmr.repository.ContentReader;
import org.alfresco.service.cmr.repository.ContentService;
import org.alfresco.service.cmr.repository.MimetypeService;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.NodeService;
import org.alfresco.service.cmr.repository.StoreRef;
import org.alfresco.service.cmr.security.AccessStatus;
import org.alfresco.service.cmr.security.PermissionService;
import org.alfresco.service.namespace.QName;
import org.alfresco.web.app.Application;
import org.alfresco.web.ui.common.Utils;
import org.apache.commons.logging.Log;
/**
* Base class for the download content servlets. Provides common
* processing for the request.
*
* @see org.alfresco.web.app.servlet.DownloadContentServlet
* @see org.alfresco.web.app.servlet.GuestDownloadContentServlet
*
* @author Kevin Roast
* @author gavinc
*/
public abstract class BaseDownloadContentServlet extends BaseServlet
{
private static final long serialVersionUID = -4558907921887235966L;
protected static final String MIMETYPE_OCTET_STREAM = "application/octet-stream";
protected static final String MSG_ERROR_CONTENT_MISSING = "error_content_missing";
protected static final String ARG_PROPERTY = "property";
protected static final String ARG_ATTACH = "attach";
protected static final String ARG_PATH = "path";
/**
* Gets the logger to use for this request.
* <p>
* This will show all debug entries from this class as though they
* came from the subclass.
*
* @return The logger
*/
protected abstract Log getLogger();
/**
* Processes the download request using the current context i.e. no
* authentication checks are made, it is presumed they have already
* been done.
*
* @param req The HTTP request
* @param res The HTTP response
* @param redirectToLogin Flag to determine whether to redirect to the login
* page if the user does not have the correct permissions
*/
protected void processDownloadRequest(HttpServletRequest req, HttpServletResponse res,
boolean redirectToLogin)
throws ServletException, IOException
{
Log logger = getLogger();
String uri = req.getRequestURI();
if (logger.isDebugEnabled())
{
String queryString = req.getQueryString();
logger.debug("Processing URL: " + uri +
((queryString != null && queryString.length() > 0) ? ("?" + queryString) : ""));
}
// TODO: add compression here?
// see http://servlets.com/jservlet2/examples/ch06/ViewResourceCompress.java for example
// only really needed if we don't use the built in compression of the servlet container
uri = uri.substring(req.getContextPath().length());
StringTokenizer t = new StringTokenizer(uri, "/");
int tokenCount = t.countTokens();
t.nextToken(); // skip servlet name
// attachment mode (either 'attach' or 'direct')
String attachToken = t.nextToken();
boolean attachment = attachToken.equals(ARG_ATTACH);
// get or calculate the noderef and filename to download as
NodeRef nodeRef;
String filename;
// do we have a path parameter instead of a NodeRef?
String path = req.getParameter(ARG_PATH);
if (path != null && path.length() != 0)
{
// process the name based path to resolve the NodeRef and the Filename element
PathRefInfo pathInfo = resolveNamePath(getServletContext(), path);
nodeRef = pathInfo.NodeRef;
filename = pathInfo.Filename;
}
else
{
// a NodeRef must have been specified if no path has been found
if (tokenCount < 6)
{
throw new IllegalArgumentException("Download URL did not contain all required args: " + uri);
}
// assume 'workspace' or other NodeRef based protocol for remaining URL elements
StoreRef storeRef = new StoreRef(t.nextToken(), t.nextToken());
String id = t.nextToken();
// build noderef from the appropriate URL elements
nodeRef = new NodeRef(storeRef, id);
// filename is last remaining token
filename = t.nextToken();
}
// get qualified of the property to get content from - default to ContentModel.PROP_CONTENT
QName propertyQName = ContentModel.PROP_CONTENT;
String property = req.getParameter(ARG_PROPERTY);
if (property != null && property.length() != 0)
{
propertyQName = QName.createQName(property);
}
if (logger.isDebugEnabled())
{
logger.debug("Found NodeRef: " + nodeRef.toString());
logger.debug("Will use filename: " + filename);
logger.debug("For property: " + propertyQName);
logger.debug("With attachment mode: " + attachment);
}
// get the services we need to retrieve the content
ServiceRegistry serviceRegistry = getServiceRegistry(getServletContext());
NodeService nodeService = serviceRegistry.getNodeService();
ContentService contentService = serviceRegistry.getContentService();
PermissionService permissionService = serviceRegistry.getPermissionService();
try
{
// check that the user has at least READ_CONTENT access - else redirect to the login page
if (permissionService.hasPermission(nodeRef, PermissionService.READ_CONTENT) == AccessStatus.DENIED)
{
if (logger.isDebugEnabled())
logger.debug("User does not have permissions to read content for NodeRef: " + nodeRef.toString());
if (redirectToLogin)
{
if (logger.isDebugEnabled())
logger.debug("Redirecting to login page...");
redirectToLoginPage(req, res, getServletContext());
}
else
{
if (logger.isDebugEnabled())
logger.debug("Returning 403 Forbidden error...");
res.sendError(HttpServletResponse.SC_FORBIDDEN);
}
return;
}
// check If-Modified-Since header and set Last-Modified header as appropriate
Date modified = (Date)nodeService.getProperty(nodeRef, ContentModel.PROP_MODIFIED);
long modifiedSince = req.getDateHeader("If-Modified-Since");
if (modifiedSince > 0L)
{
// round the date to the ignore millisecond value which is not supplied by header
long modDate = (modified.getTime() / 1000L) * 1000L;
if (modDate <= modifiedSince)
{
res.setStatus(304);
return;
}
}
res.setDateHeader("Last-Modified", modified.getTime());
if (attachment == true)
{
// set header based on filename - will force a Save As from the browse if it doesn't recognise it
// this is better than the default response of the browser trying to display the contents
res.setHeader("Content-Disposition", "attachment");
}
// get the content reader
ContentReader reader = contentService.getReader(nodeRef, propertyQName);
// ensure that it is safe to use
reader = FileContentReader.getSafeContentReader(
reader,
Application.getMessage(req.getSession(), MSG_ERROR_CONTENT_MISSING),
nodeRef, reader);
String mimetype = reader.getMimetype();
// fall back if unable to resolve mimetype property
if (mimetype == null || mimetype.length() == 0)
{
MimetypeService mimetypeMap = serviceRegistry.getMimetypeService();
mimetype = MIMETYPE_OCTET_STREAM;
int extIndex = filename.lastIndexOf('.');
if (extIndex != -1)
{
String ext = filename.substring(extIndex + 1);
String mt = mimetypeMap.getMimetypesByExtension().get(ext);
if (mt != null)
{
mimetype = mt;
}
}
}
// set mimetype for the content and the character encoding for the stream
res.setContentType(mimetype);
res.setCharacterEncoding(reader.getEncoding());
// get the content and stream directly to the response output stream
// assuming the repo is capable of streaming in chunks, this should allow large files
// to be streamed directly to the browser response stream.
try
{
reader.getContent( res.getOutputStream() );
}
catch (SocketException e)
{
if (e.getMessage().contains("ClientAbortException"))
{
// the client cut the connection - our mission was accomplished apart from a little error message
logger.error("Client aborted stream read:\n node: " + nodeRef + "\n content: " + reader);
}
else
{
throw e;
}
}
}
catch (Throwable err)
{
throw new AlfrescoRuntimeException("Error during download content servlet processing: " + err.getMessage(), err);
}
}
/**
* Helper to generate a URL to a content node for downloading content from the server.
*
* @param pattern The pattern to use for the URL
* @param ref NodeRef of the content node to generate URL for (cannot be null)
* @param name File name to return in the URL (cannot be null)
*
* @return URL to download the content from the specified node
*/
protected final static String generateUrl(String pattern, NodeRef ref, String name)
{
String url = null;
try
{
url = MessageFormat.format(pattern, new Object[] {
ref.getStoreRef().getProtocol(),
ref.getStoreRef().getIdentifier(),
ref.getId(),
Utils.replace(URLEncoder.encode(name, "UTF-8"), "+", "%20") } );
}
catch (UnsupportedEncodingException uee)
{
throw new AlfrescoRuntimeException("Failed to encode content URL for node: " + ref, uee);
}
return url;
}
}

View File

@@ -73,7 +73,7 @@ public class CommandServlet extends BaseServlet
/**
* @see javax.servlet.http.HttpServlet#doGet(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
*/
protected void doGet(HttpServletRequest req, HttpServletResponse res)
protected void service(HttpServletRequest req, HttpServletResponse res)
throws ServletException, IOException
{
String uri = req.getRequestURI();

View File

@@ -17,32 +17,12 @@
package org.alfresco.web.app.servlet;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.SocketException;
import java.net.URLEncoder;
import java.text.MessageFormat;
import java.util.Date;
import java.util.StringTokenizer;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.alfresco.error.AlfrescoRuntimeException;
import org.alfresco.model.ContentModel;
import org.alfresco.repo.content.filestore.FileContentReader;
import org.alfresco.service.ServiceRegistry;
import org.alfresco.service.cmr.repository.ContentReader;
import org.alfresco.service.cmr.repository.ContentService;
import org.alfresco.service.cmr.repository.MimetypeService;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.NodeService;
import org.alfresco.service.cmr.repository.StoreRef;
import org.alfresco.service.cmr.security.AccessStatus;
import org.alfresco.service.cmr.security.PermissionService;
import org.alfresco.service.namespace.QName;
import org.alfresco.web.app.Application;
import org.alfresco.web.ui.common.Utils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
@@ -71,26 +51,30 @@ import org.apache.commons.logging.LogFactory;
* Like most Alfresco servlets, the URL may be followed by a valid 'ticket' argument for authentication:
* ?ticket=1234567890
* <p>
* And/or also followed by the "?guest=true" argument to force guest access login for the URL.
* And/or also followed by the "?guest=true" argument to force guest access login for the URL. If the
* guest=true parameter is used the current session will be logged out and the guest user logged in.
* Therefore upon completion of this request the current user will be "guest".
* <p>
* If the user attempting the request is not authorised to access the requested node the login page
* will be redirected to.
*
* @author Kevin Roast
* @author gavinc
*/
public class DownloadContentServlet extends BaseServlet
public class DownloadContentServlet extends BaseDownloadContentServlet
{
private static final long serialVersionUID = -4558907921887235966L;
private static final long serialVersionUID = -576405943603122206L;
private static Log logger = LogFactory.getLog(DownloadContentServlet.class);
private static final String DOWNLOAD_URL = "/download/attach/{0}/{1}/{2}/{3}";
private static final String BROWSER_URL = "/download/direct/{0}/{1}/{2}/{3}";
private static final String MIMETYPE_OCTET_STREAM = "application/octet-stream";
private static final String MSG_ERROR_CONTENT_MISSING = "error_content_missing";
private static final String ARG_PROPERTY = "property";
private static final String ARG_ATTACH = "attach";
private static final String ARG_PATH = "path";
@Override
protected Log getLogger()
{
return logger;
}
/**
* @see javax.servlet.http.HttpServlet#doGet(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
@@ -98,10 +82,12 @@ public class DownloadContentServlet extends BaseServlet
protected void doGet(HttpServletRequest req, HttpServletResponse res)
throws ServletException, IOException
{
String uri = req.getRequestURI();
if (logger.isDebugEnabled())
logger.debug("Processing URL: " + uri + (req.getQueryString() != null ? ("?" + req.getQueryString()) : ""));
{
String queryString = req.getQueryString();
logger.debug("Authenticating request to URL: " + req.getRequestURI() +
((queryString != null && queryString.length() > 0) ? ("?" + queryString) : ""));
}
AuthenticationStatus status = servletAuthenticate(req, res);
if (status == AuthenticationStatus.Failure)
@@ -109,159 +95,7 @@ public class DownloadContentServlet extends BaseServlet
return;
}
// TODO: add compression here?
// see http://servlets.com/jservlet2/examples/ch06/ViewResourceCompress.java for example
// only really needed if we don't use the built in compression of the servlet container
uri = uri.substring(req.getContextPath().length());
StringTokenizer t = new StringTokenizer(uri, "/");
int tokenCount = t.countTokens();
t.nextToken(); // skip servlet name
// attachment mode (either 'attach' or 'direct')
String attachToken = t.nextToken();
boolean attachment = attachToken.equals(ARG_ATTACH);
// get or calculate the noderef and filename to download as
NodeRef nodeRef;
String filename;
// do we have a path parameter instead of a NodeRef?
String path = req.getParameter(ARG_PATH);
if (path != null && path.length() != 0)
{
// process the name based path to resolve the NodeRef and the Filename element
PathRefInfo pathInfo = resolveNamePath(getServletContext(), path);
nodeRef = pathInfo.NodeRef;
filename = pathInfo.Filename;
}
else
{
// a NodeRef must have been specified if no path has been found
if (tokenCount < 6)
{
throw new IllegalArgumentException("Download URL did not contain all required args: " + uri);
}
// assume 'workspace' or other NodeRef based protocol for remaining URL elements
StoreRef storeRef = new StoreRef(t.nextToken(), t.nextToken());
String id = t.nextToken();
// build noderef from the appropriate URL elements
nodeRef = new NodeRef(storeRef, id);
// filename is last remaining token
filename = t.nextToken();
}
// get qualified of the property to get content from - default to ContentModel.PROP_CONTENT
QName propertyQName = ContentModel.PROP_CONTENT;
String property = req.getParameter(ARG_PROPERTY);
if (property != null && property.length() != 0)
{
propertyQName = QName.createQName(property);
}
if (logger.isDebugEnabled())
{
logger.debug("Found NodeRef: " + nodeRef.toString());
logger.debug("Will use filename: " + filename);
logger.debug("For property: " + propertyQName);
logger.debug("With attachment mode: " + attachment);
}
// get the services we need to retrieve the content
ServiceRegistry serviceRegistry = getServiceRegistry(getServletContext());
NodeService nodeService = serviceRegistry.getNodeService();
ContentService contentService = serviceRegistry.getContentService();
PermissionService permissionService = serviceRegistry.getPermissionService();
try
{
// check that the user has at least READ_CONTENT access - else redirect to the login page
if (permissionService.hasPermission(nodeRef, PermissionService.READ_CONTENT) == AccessStatus.DENIED)
{
if (logger.isDebugEnabled())
logger.debug("User does not have permissions to read content for NodeRef: " + nodeRef.toString());
redirectToLoginPage(req, res, getServletContext());
return;
}
// check If-Modified-Since header and set Last-Modified header as appropriate
Date modified = (Date)nodeService.getProperty(nodeRef, ContentModel.PROP_MODIFIED);
long modifiedSince = req.getDateHeader("If-Modified-Since");
if (modifiedSince > 0L)
{
// round the date to the ignore millisecond value which is not supplied by header
long modDate = (modified.getTime() / 1000L) * 1000L;
if (modDate <= modifiedSince)
{
res.setStatus(304);
return;
}
}
res.setDateHeader("Last-Modified", modified.getTime());
if (attachment == true)
{
// set header based on filename - will force a Save As from the browse if it doesn't recognise it
// this is better than the default response of the browser trying to display the contents
res.setHeader("Content-Disposition", "attachment");
}
// get the content reader
ContentReader reader = contentService.getReader(nodeRef, propertyQName);
// ensure that it is safe to use
reader = FileContentReader.getSafeContentReader(
reader,
Application.getMessage(req.getSession(), MSG_ERROR_CONTENT_MISSING),
nodeRef, reader);
String mimetype = reader.getMimetype();
// fall back if unable to resolve mimetype property
if (mimetype == null || mimetype.length() == 0)
{
MimetypeService mimetypeMap = serviceRegistry.getMimetypeService();
mimetype = MIMETYPE_OCTET_STREAM;
int extIndex = filename.lastIndexOf('.');
if (extIndex != -1)
{
String ext = filename.substring(extIndex + 1);
String mt = mimetypeMap.getMimetypesByExtension().get(ext);
if (mt != null)
{
mimetype = mt;
}
}
}
// set mimetype for the content and the character encoding for the stream
res.setContentType(mimetype);
res.setCharacterEncoding(reader.getEncoding());
// get the content and stream directly to the response output stream
// assuming the repo is capable of streaming in chunks, this should allow large files
// to be streamed directly to the browser response stream.
try
{
reader.getContent( res.getOutputStream() );
}
catch (SocketException e)
{
if (e.getMessage().contains("ClientAbortException"))
{
// the client cut the connection - our mission was accomplished apart from a little error message
logger.error("Client aborted stream read:\n node: " + nodeRef + "\n content: " + reader);
}
else
{
throw e;
}
}
}
catch (Throwable err)
{
throw new AlfrescoRuntimeException("Error during download content servlet processing: " + err.getMessage(), err);
}
processDownloadRequest(req, res, true);
}
/**
@@ -276,22 +110,7 @@ public class DownloadContentServlet extends BaseServlet
*/
public final static String generateDownloadURL(NodeRef ref, String name)
{
String url = null;
try
{
url = MessageFormat.format(DOWNLOAD_URL, new Object[] {
ref.getStoreRef().getProtocol(),
ref.getStoreRef().getIdentifier(),
ref.getId(),
Utils.replace(URLEncoder.encode(name, "UTF-8"), "+", "%20") } );
}
catch (UnsupportedEncodingException uee)
{
throw new AlfrescoRuntimeException("Failed to encode content URL for node: " + ref, uee);
}
return url;
return generateUrl(DOWNLOAD_URL, ref, name);
}
/**
@@ -306,21 +125,6 @@ public class DownloadContentServlet extends BaseServlet
*/
public final static String generateBrowserURL(NodeRef ref, String name)
{
String url = null;
try
{
url = MessageFormat.format(BROWSER_URL, new Object[] {
ref.getStoreRef().getProtocol(),
ref.getStoreRef().getIdentifier(),
ref.getId(),
Utils.replace(URLEncoder.encode(name, "UTF-8"), "+", "%20") } );
}
catch (UnsupportedEncodingException uee)
{
throw new AlfrescoRuntimeException("Failed to encode content URL for node: " + ref, uee);
}
return url;
return generateUrl(BROWSER_URL, ref, name);
}
}

View File

@@ -145,12 +145,31 @@ public final class FacesHelper
else
{
// make sure we do not have illegal characters in the id
id = makeLegalId(id);
}
component.setId(id);
}
/**
* Makes the given id a legal JSF component id by replacing illegal
* characters with underscores.
*
* @param id The id to make legal
* @return The legalised id
*/
public static String makeLegalId(String id)
{
if (id != null)
{
// replace illegal ID characters with an underscore
id = id.replace(':', '_');
id = id.replace(' ', '_');
// TODO: check all other illegal characters - only allowed dash and underscore
}
component.setId(id);
return id;
}
/**

View File

@@ -0,0 +1,147 @@
/*
* Copyright (C) 2005 Alfresco, Inc.
*
* Licensed under the Mozilla Public License version 1.1
* with a permitted attribution clause. You may obtain a
* copy of the License at
*
* http://www.alfresco.org/legal/license.txt
*
* 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.alfresco.web.app.servlet;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.alfresco.repo.security.authentication.AuthenticationUtil;
import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.security.PermissionService;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* Servlet responsible for streaming node content from the repo directly to the response stream.
* The appropriate mimetype is calculated based on filename extension.
* <p>
* The URL to the servlet should be generated thus:
* <pre>/alfresco/guestDownload/attach/workspace/SpacesStore/0000-0000-0000-0000/myfile.pdf</pre>
* or
* <pre>/alfresco/guestDownload/direct/workspace/SpacesStore/0000-0000-0000-0000/myfile.pdf</pre>
* or
* <pre>/alfresco/guestDownload/[direct|attach]?path=/Company%20Home/MyFolder/myfile.pdf</pre>
* The protocol, followed by either the store and Id (NodeRef) or instead specify a name based
* encoded Path to the content, note that the filename element is used for mimetype lookup and
* as the returning filename for the response stream.
* <p>
* The 'attach' or 'direct' element is used to indicate whether to display the stream directly
* in the browser or download it as a file attachment.
* <p>
* By default, the download assumes that the content is on the
* {@link org.alfresco.model.ContentModel#PROP_CONTENT content property}.<br>
* To retrieve the content of a specific model property, use a 'property' arg, providing the workspace,
* node ID AND the qualified name of the property.
* <p>
* This servlet only accesses content available to the guest user. If the guest user does not
* have access to the requested a 401 Forbidden response is returned to the caller.
* <p>
* This servlet does not effect the current session, therefore if guest access is required to a
* resource this servlet can be used without logging out the current user.
*
* @author gavinc
*/
public class GuestDownloadContentServlet extends BaseDownloadContentServlet
{
private static final long serialVersionUID = -5258137503339817457L;
private static Log logger = LogFactory.getLog(GuestDownloadContentServlet.class);
private static final String DOWNLOAD_URL = "/guestDownload/attach/{0}/{1}/{2}/{3}";
private static final String BROWSER_URL = "/guestDownload/direct/{0}/{1}/{2}/{3}";
@Override
protected Log getLogger()
{
return logger;
}
/**
* @see javax.servlet.http.HttpServlet#doGet(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
*/
protected void doGet(HttpServletRequest req, HttpServletResponse res)
throws ServletException, IOException
{
if (logger.isDebugEnabled())
{
String queryString = req.getQueryString();
logger.debug("Setting up guest access to URL: " + req.getRequestURI() +
((queryString != null && queryString.length() > 0) ? ("?" + queryString) : ""));
}
DownloadContentWork dcw = new DownloadContentWork(req, res);
AuthenticationUtil.runAs(dcw, PermissionService.GUEST_AUTHORITY);
}
/**
* Helper to generate a URL to a content node for downloading content from the server.
* The content is supplied as an HTTP1.1 attachment to the response. This generally means
* a browser should prompt the user to save the content to specified location.
*
* @param ref NodeRef of the content node to generate URL for (cannot be null)
* @param name File name to return in the URL (cannot be null)
*
* @return URL to download the content from the specified node
*/
public final static String generateDownloadURL(NodeRef ref, String name)
{
return generateUrl(DOWNLOAD_URL, ref, name);
}
/**
* Helper to generate a URL to a content node for downloading content from the server.
* The content is supplied directly in the reponse. This generally means a browser will
* attempt to open the content directly if possible, else it will prompt to save the file.
*
* @param ref NodeRef of the content node to generate URL for (cannot be null)
* @param name File name to return in the URL (cannot be null)
*
* @return URL to download the content from the specified node
*/
public final static String generateBrowserURL(NodeRef ref, String name)
{
return generateUrl(BROWSER_URL, ref, name);
}
/**
* Class to wrap the call to processDownloadRequest.
*
* @author gavinc
*/
public class DownloadContentWork implements RunAsWork<Object>
{
private HttpServletRequest req = null;
private HttpServletResponse res = null;
public DownloadContentWork(HttpServletRequest req, HttpServletResponse res)
{
this.req = req;
this.res = res;
}
public Object doWork() throws Exception
{
processDownloadRequest(this.req, this.res, false);
return null;
}
}
}

View File

@@ -86,8 +86,6 @@ public class TemplateContentServlet extends BaseServlet
private static final String DEFAULT_URL = "/template/{0}/{1}/{2}";
private static final String TEMPLATE_URL = "/template/{0}/{1}/{2}/{3}/{4}/{5}";
private static final String MSG_ERROR_CONTENT_MISSING = "error_content_missing";
private static final String ARG_MIMETYPE = "mimetype";
private static final String ARG_TEMPLATE_PATH = "templatePath";
private static final String ARG_CONTEXT_PATH = "contextPath";
@@ -95,13 +93,17 @@ public class TemplateContentServlet extends BaseServlet
/**
* @see javax.servlet.http.HttpServlet#doGet(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
*/
protected void doGet(HttpServletRequest req, HttpServletResponse res)
protected void service(HttpServletRequest req, HttpServletResponse res)
throws ServletException, IOException
{
String uri = req.getRequestURI();
if (logger.isDebugEnabled())
logger.debug("Processing URL: " + uri + (req.getQueryString() != null ? ("?" + req.getQueryString()) : ""));
{
String queryString = req.getQueryString();
logger.debug("Processing URL: " + uri +
((queryString != null && queryString.length() > 0) ? ("?" + queryString) : ""));
}
AuthenticationStatus status = servletAuthenticate(req, res);
if (status == AuthenticationStatus.Failure)
@@ -258,6 +260,7 @@ public class TemplateContentServlet extends BaseServlet
*
* @return an object model ready for executing template against
*/
@SuppressWarnings("unchecked")
private Object getModel(ServiceRegistry services, HttpServletRequest req, NodeRef templateRef, NodeRef nodeRef)
{
// build FreeMarker default model and merge

View File

@@ -22,7 +22,7 @@ public interface AjaxCommand
* expression. Parameters required to call the method can be retrieved
* from the request.
*
* Currently the content type of the response will always be text/html, in the
* Currently the content type of the response will always be text/xml, in the
* future sublcasses may provide a mechanism to allow the content type to be set
* dynamically.
*

View File

@@ -42,7 +42,7 @@ public class InvokeCommand extends BaseAjaxCommand
// NOTE: it doesn't seem to matter what the content type of the response is (at least with Dojo),
// it determines it's behaviour from the mimetype specified in the AJAX call on the client,
// therefore, for now we will always return a content type of text/html.
// therefore, for now we will always return a content type of text/xml.
// In the future we may use annotations on the method to be called to specify what content
// type should be used for the response.
@@ -53,7 +53,7 @@ public class InvokeCommand extends BaseAjaxCommand
RenderKit renderKit = renderFactory.getRenderKit(facesContext,
viewRoot.getRenderKitId());
ResponseWriter writer = renderKit.createResponseWriter(
new OutputStreamWriter(os), MimetypeMap.MIMETYPE_HTML, "UTF-8");
new OutputStreamWriter(os), MimetypeMap.MIMETYPE_XML, "UTF-8");
facesContext.setResponseWriter(writer);
response.setContentType(writer.getContentType());

View File

@@ -848,7 +848,7 @@ public class AdvancedSearchBean
search.addFixedValueQuery(QName.createQName(qname), strVal);
}
}
else
else if (value != null)
{
// by default use toString() value - this is for text fields and unknown types
String strVal = value.toString();

View File

@@ -29,6 +29,7 @@ import javax.transaction.UserTransaction;
import org.alfresco.model.ContentModel;
import org.alfresco.repo.content.MimetypeMap;
import org.alfresco.repo.version.VersionModel;
import org.alfresco.repo.workflow.WorkflowModel;
import org.alfresco.service.cmr.coci.CheckOutCheckInService;
import org.alfresco.service.cmr.repository.ChildAssociationRef;
import org.alfresco.service.cmr.repository.ContentData;
@@ -40,6 +41,10 @@ import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.NodeService;
import org.alfresco.service.cmr.version.Version;
import org.alfresco.service.cmr.version.VersionType;
import org.alfresco.service.cmr.workflow.WorkflowService;
import org.alfresco.service.cmr.workflow.WorkflowTask;
import org.alfresco.service.namespace.NamespaceService;
import org.alfresco.service.namespace.QName;
import org.alfresco.web.app.AlfrescoNavigationHandler;
import org.alfresco.web.app.Application;
import org.alfresco.web.app.context.UIContextService;
@@ -131,6 +136,14 @@ public class CheckinCheckoutBean
this.contentService = contentService;
}
/**
* @param workflowService The WorkflowService to set.
*/
public void setWorkflowService(WorkflowService workflowService)
{
this.workflowService = workflowService;
}
/**
* @return The document node being used for the current operation
*/
@@ -357,13 +370,29 @@ public class CheckinCheckoutBean
if (id != null && id.length() != 0)
{
setupContentDocument(id);
}
}
else
{
setDocument(null);
}
clearUpload();
resetState();
}
public void setupWorkflowContentAction(ActionEvent event)
{
// do the common processing
setupContentAction(event);
// retrieve the id of the task
UIActionLink link = (UIActionLink)event.getComponent();
Map<String, String> params = link.getParameterMap();
this.workflowTaskId = params.get("taskId");
this.isWorkflowAction = true;
if (logger.isDebugEnabled())
logger.debug("Setup for workflow package action for task id: " + this.workflowTaskId);
}
/**
@@ -435,7 +464,7 @@ public class CheckinCheckoutBean
logger.debug("Checkout copy location: " + getCopyLocation());
logger.debug("Selected Space Id: " + this.selectedSpaceId);
}
NodeRef workingCopyRef;
NodeRef workingCopyRef = null;
if (getCopyLocation().equals(COPYLOCATION_OTHER) && this.selectedSpaceId != null)
{
// checkout to a arbituary parent Space
@@ -447,7 +476,31 @@ public class CheckinCheckoutBean
}
else
{
// checkout the content to the current space
workingCopyRef = this.versionOperationsService.checkout(node.getNodeRef());
// if this is a workflow action and there is a task id present we need
// to also link the working copy to the workflow package so it appears
// in the resources panel in the manage task dialog
if (this.isWorkflowAction && this.workflowTaskId != null)
{
WorkflowTask task = this.workflowService.getTaskById(this.workflowTaskId);
if (task != null)
{
NodeRef workflowPackage = (NodeRef)task.properties.get(WorkflowModel.ASSOC_PACKAGE);
if (workflowPackage != null)
{
this.nodeService.addChild(workflowPackage, workingCopyRef,
ContentModel.ASSOC_CONTAINS, QName.createQName(
NamespaceService.CONTENT_MODEL_1_0_URI,
QName.createValidLocalName((String)this.nodeService.getProperty(
workingCopyRef, ContentModel.PROP_NAME))));
if (logger.isDebugEnabled())
logger.debug("Added working copy to workflow package: " + workflowPackage);
}
}
}
}
// set the working copy Node instance
@@ -500,7 +553,7 @@ public class CheckinCheckoutBean
}
// clean up and clear action context
clearUpload();
resetState();
setDocument(null);
setWorkingDocument(null);
@@ -525,7 +578,7 @@ public class CheckinCheckoutBean
if (node != null)
{
// clean up and clear action context
clearUpload();
resetState();
setDocument(null);
setWorkingDocument(null);
@@ -631,7 +684,7 @@ public class CheckinCheckoutBean
tx.commit();
// clean up and clear action context
clearUpload();
resetState();
setDocument(null);
setDocumentContent(null);
setEditorOutput(null);
@@ -669,7 +722,7 @@ public class CheckinCheckoutBean
// try to cancel checkout of the working copy
this.versionOperationsService.cancelCheckout(node.getNodeRef());
clearUpload();
resetState();
outcome = AlfrescoNavigationHandler.CLOSE_DIALOG_OUTCOME;
}
@@ -718,10 +771,15 @@ public class CheckinCheckoutBean
{
throw new IllegalStateException("Node supplied for undo checkout has neither Working Copy or Locked aspect!");
}
clearUpload();
outcome = AlfrescoNavigationHandler.CLOSE_DIALOG_OUTCOME + AlfrescoNavigationHandler.OUTCOME_SEPARATOR + "browse";
outcome = AlfrescoNavigationHandler.CLOSE_DIALOG_OUTCOME;
if (this.isWorkflowAction == false)
{
outcome = outcome + AlfrescoNavigationHandler.OUTCOME_SEPARATOR + "browse";
}
resetState();
}
catch (Throwable err)
{
@@ -804,12 +862,16 @@ public class CheckinCheckoutBean
// commit the transaction
tx.commit();
outcome = AlfrescoNavigationHandler.CLOSE_DIALOG_OUTCOME;
if (this.isWorkflowAction == false)
{
outcome = outcome + AlfrescoNavigationHandler.OUTCOME_SEPARATOR + "browse";
}
// clear action context
setDocument(null);
clearUpload();
outcome = AlfrescoNavigationHandler.CLOSE_DIALOG_OUTCOME +
AlfrescoNavigationHandler.OUTCOME_SEPARATOR + "browse";
resetState();
}
catch (Throwable err)
{
@@ -863,7 +925,7 @@ public class CheckinCheckoutBean
// clear action context
setDocument(null);
clearUpload();
resetState();
outcome = AlfrescoNavigationHandler.CLOSE_DIALOG_OUTCOME;
}
@@ -889,7 +951,7 @@ public class CheckinCheckoutBean
public String cancel()
{
// reset the state
clearUpload();
resetState();
return AlfrescoNavigationHandler.CLOSE_DIALOG_OUTCOME;
}
@@ -897,7 +959,7 @@ public class CheckinCheckoutBean
/**
* Clear form state and upload file bean
*/
private void clearUpload()
private void resetState()
{
// delete the temporary file we uploaded earlier
if (this.file != null)
@@ -912,6 +974,8 @@ public class CheckinCheckoutBean
this.copyLocation = COPYLOCATION_CURRENT;
this.versionNotes = "";
this.selectedSpaceId = null;
this.isWorkflowAction = false;
this.workflowTaskId = null;
// remove the file upload bean from the session
FacesContext ctx = FacesContext.getCurrentInstance();
@@ -951,6 +1015,8 @@ public class CheckinCheckoutBean
private String fileName;
private boolean keepCheckedOut = false;
private boolean minorChange = true;
private boolean isWorkflowAction = false;
private String workflowTaskId;
private String copyLocation = COPYLOCATION_CURRENT;
private String versionNotes = "";
private NodeRef selectedSpaceId = null;
@@ -969,4 +1035,7 @@ public class CheckinCheckoutBean
/** The ContentService to be used by the bean */
protected ContentService contentService;
/** The WorkflowService to be used by the bean */
protected WorkflowService workflowService;
}

View File

@@ -121,15 +121,20 @@ public class ErrorBean
*/
public String getStackTrace()
{
StringWriter stringWriter = new StringWriter();
PrintWriter writer = new PrintWriter(stringWriter);
this.lastError.printStackTrace(writer);
String trace = "No stack trace available";
// format the message for HTML display
String trace = stringWriter.toString();
trace = trace.replaceAll("<", "&lt;");
trace = trace.replaceAll(">", "&gt;");
trace = trace.replaceAll("\n", "<br/>");
if (this.lastError != null)
{
StringWriter stringWriter = new StringWriter();
PrintWriter writer = new PrintWriter(stringWriter);
this.lastError.printStackTrace(writer);
// format the message for HTML display
trace = stringWriter.toString();
trace = trace.replaceAll("<", "&lt;");
trace = trace.replaceAll(">", "&gt;");
trace = trace.replaceAll("\n", "<br/>");
}
return trace;
}

View File

@@ -73,6 +73,7 @@ public class SpaceDetailsBean extends BaseDetailsBean
{
// initial state of some panels that don't use the default
panels.put("rules-panel", false);
panels.put("dashboard-panel", false);
}

View File

@@ -130,12 +130,12 @@ public abstract class BaseDialogBean implements IDialogBean
return true;
}
public String getTitle()
public String getContainerTitle()
{
return null;
}
public String getDescription()
public String getContainerDescription()
{
return null;
}

View File

@@ -141,7 +141,7 @@ public final class DialogManager
public String getTitle()
{
// try and get the title directly from the dialog
String title = this.currentDialogState.getDialog().getTitle();
String title = this.currentDialogState.getDialog().getContainerTitle();
if (title == null)
{
@@ -170,7 +170,7 @@ public final class DialogManager
public String getDescription()
{
// try and get the description directly from the dialog
String desc = this.currentDialogState.getDialog().getDescription();
String desc = this.currentDialogState.getDialog().getContainerDescription();
if (desc == null)
{

View File

@@ -73,7 +73,7 @@ public interface IDialogBean
*
* @return The title or null if the title is to be acquired via configuration
*/
public String getTitle();
public String getContainerTitle();
/**
* Returns the description to be used for the dialog
@@ -82,5 +82,5 @@ public interface IDialogBean
*
* @return The title or null if the title is to be acquired via configuration
*/
public String getDescription();
public String getContainerDescription();
}

View File

@@ -4,7 +4,6 @@ import java.util.Date;
import javax.faces.component.UIComponent;
import javax.faces.component.UIOutput;
import javax.faces.component.UISelectOne;
import javax.faces.context.FacesContext;
import javax.faces.convert.Converter;
@@ -25,6 +24,7 @@ import org.alfresco.web.ui.repo.component.property.UIPropertySheet;
*/
public class DatePickerGenerator extends BaseComponentGenerator
{
private boolean initialiseIfNull = false;
private int yearCount = 30;
private int startYear = new Date().getYear() + 1900 + 2;
@@ -61,6 +61,26 @@ public class DatePickerGenerator extends BaseComponentGenerator
{
this.yearCount = yearCount;
}
/**
* @return Determines whether the control should initially show
* today's date if the model value is null
*/
public boolean isInitialiseIfNull()
{
return initialiseIfNull;
}
/**
* @param initialiseIfNull Determines whether the control should
* initially show today's date if the model value is null.
* This will also hide the None button thus disallowing
* the user to set the date back to null.
*/
public void setInitialiseIfNull(boolean initialiseIfNull)
{
this.initialiseIfNull = initialiseIfNull;
}
@SuppressWarnings("unchecked")
public UIComponent generate(FacesContext context, String id)
@@ -71,6 +91,7 @@ public class DatePickerGenerator extends BaseComponentGenerator
FacesHelper.setupComponentId(context, component, id);
component.getAttributes().put("startYear", this.startYear);
component.getAttributes().put("yearCount", this.yearCount);
component.getAttributes().put("initialiseIfNull", new Boolean(this.initialiseIfNull));
component.getAttributes().put("style", "margin-right: 7px;");
return component;

View File

@@ -15,7 +15,7 @@ import org.alfresco.web.ui.repo.component.property.UIPropertySheet;
*/
public class HtmlSeparatorGenerator extends BaseComponentGenerator
{
protected String html = "<b>default</b>";
protected String html = "";
/**
* Returns the HTML configured to be used for this separator

View File

@@ -152,7 +152,7 @@ public final class WizardManager
public String getTitle()
{
// try and get the title directly from the wizard
String title = this.currentWizardState.getWizard().getTitle();
String title = this.currentWizardState.getWizard().getContainerTitle();
if (title == null)
{
@@ -181,7 +181,7 @@ public final class WizardManager
public String getDescription()
{
// try and get the description directly from the dialog
String desc = this.currentWizardState.getWizard().getDescription();
String desc = this.currentWizardState.getWizard().getContainerDescription();
if (desc == null)
{

View File

@@ -26,6 +26,7 @@ import org.alfresco.service.namespace.QName;
import org.alfresco.service.namespace.RegexQNamePattern;
import org.alfresco.web.app.AlfrescoNavigationHandler;
import org.alfresco.web.app.Application;
import org.alfresco.web.app.servlet.FacesHelper;
import org.alfresco.web.bean.dialog.BaseDialogBean;
import org.alfresco.web.bean.repository.MapNode;
import org.alfresco.web.bean.repository.Node;
@@ -105,16 +106,7 @@ public class ManageTaskDialog extends BaseDialogBean
this.workflowInstance = this.task.path.instance;
// setup the workflow package for the task
Serializable obj = this.task.properties.get(WorkflowModel.ASSOC_PACKAGE);
// TODO: remove this workaroud where JBPM may return a String and not the NodeRef
if (obj instanceof NodeRef)
{
this.workflowPackage = (NodeRef)obj;
}
else if (obj instanceof String)
{
this.workflowPackage = new NodeRef((String)obj);
}
this.workflowPackage = (NodeRef)this.task.properties.get(WorkflowModel.ASSOC_PACKAGE);
if (logger.isDebugEnabled())
{
@@ -208,7 +200,7 @@ public class ManageTaskDialog extends BaseDialogBean
@Override
public String getFinishButtonLabel()
{
return Application.getMessage(FacesContext.getCurrentInstance(), "save");
return Application.getMessage(FacesContext.getCurrentInstance(), "save_changes");
}
@Override
@@ -218,7 +210,7 @@ public class ManageTaskDialog extends BaseDialogBean
}
@Override
public String getTitle()
public String getContainerTitle()
{
String titleStart = Application.getMessage(FacesContext.getCurrentInstance(), "manage_task_title");
@@ -226,7 +218,7 @@ public class ManageTaskDialog extends BaseDialogBean
}
@Override
public String getDescription()
public String getContainerDescription()
{
return this.task.description;
}
@@ -251,7 +243,7 @@ public class ManageTaskDialog extends BaseDialogBean
String selectedTransition = null;
for (WorkflowTransition trans : this.transitions)
{
Object result = reqParams.get(CLIENT_ID_PREFIX + trans.title);
Object result = reqParams.get(CLIENT_ID_PREFIX + FacesHelper.makeLegalId(trans.title));
if (result != null)
{
// this was the button that was pressed
@@ -635,7 +627,10 @@ public class ManageTaskDialog extends BaseDialogBean
node.addPropertyResolver("displayPath", this.browseBean.resolverDisplayPath);
// add a property resolver to indicate whether the item has been completed or not
// node.addPropertyResolver("completed", this.completeResolver);
// node.addPropertyResolver("completed", this.completeResolver);
// add the id of the task being managed
node.getProperties().put("taskId", this.task.id);
this.resources.add(node);
}

View File

@@ -50,7 +50,7 @@ public class ReassignTaskDialog extends BaseDialogBean
{
super.init(parameters);
this.taskId = this.parameters.get("task-id");
this.taskId = this.parameters.get("id");
if (this.taskId == null || this.taskId.length() == 0)
{
throw new IllegalArgumentException("Reassign task dialog called without task id");

View File

@@ -46,6 +46,7 @@ import org.apache.commons.logging.LogFactory;
public class StartWorkflowWizard extends BaseWizardBean
{
protected String selectedWorkflow;
protected String previouslySelectedWorkflow;
protected List<SelectItem> availableWorkflows;
protected Map<String, WorkflowDefinition> workflows;
protected WorkflowService workflowService;
@@ -77,18 +78,13 @@ public class StartWorkflowWizard extends BaseWizardBean
this.selectedWorkflow = null;
}
this.previouslySelectedWorkflow = null;
this.startTaskNode = null;
this.resources = null;
this.itemsToAdd = null;
this.packageItemsToAdd = new ArrayList<String>();
this.isItemBeingAdded = false;
if (this.packageItemsRichList != null)
{
this.packageItemsRichList.setValue(null);
this.packageItemsRichList = null;
}
// TODO: Does this need to be in a read-only transaction??
resetRichList();
// add the item the workflow wizard was started on to the list of resources
String itemToWorkflowId = this.parameters.get("item-to-workflow");
@@ -110,11 +106,7 @@ public class StartWorkflowWizard extends BaseWizardBean
public void restored()
{
// reset the workflow package rich list so everything gets re-evaluated
if (this.packageItemsRichList != null)
{
this.packageItemsRichList.setValue(null);
this.packageItemsRichList = null;
}
resetRichList();
}
@Override
@@ -190,13 +182,14 @@ public class StartWorkflowWizard extends BaseWizardBean
{
String stepName = Application.getWizardManager().getCurrentStepName();
if ("options".equals(stepName) && this.startTaskNode == null)
if ("options".equals(stepName) &&
(this.selectedWorkflow.equals(this.previouslySelectedWorkflow) == false))
{
// retrieve the start task for the selected workflow, get the task
// definition and create a transient node to allow the property
// sheet to collect the required data.
WorkflowDefinition flowDef = this.workflows.get(this.selectedWorkflow);
WorkflowDefinition flowDef = this.workflows.get(this.selectedWorkflow);
if (logger.isDebugEnabled())
logger.debug("Selected workflow: "+ flowDef);
@@ -211,11 +204,29 @@ public class StartWorkflowWizard extends BaseWizardBean
this.startTaskNode = new TransientNode(taskDef.metadata.getName(),
"task_" + System.currentTimeMillis(), null);
}
// we also need to reset the resources list so that the actions get re-evaluated
resetRichList();
}
return null;
}
@Override
public String back()
{
String stepName = Application.getWizardManager().getCurrentStepName();
// if we have come back to the "choose-workflow" step remember
// the current workflow selection
if ("choose-workflow".equals(stepName))
{
this.previouslySelectedWorkflow = this.selectedWorkflow;
}
return null;
}
@Override
public boolean getNextButtonDisabled()
{
@@ -293,7 +304,7 @@ public class StartWorkflowWizard extends BaseWizardBean
// reset the rich list so it re-renders
this.packageItemsRichList.setValue(null);
}
// ------------------------------------------------------------------------------
// Bean Getters and Setters
@@ -465,33 +476,34 @@ public class StartWorkflowWizard extends BaseWizardBean
*/
public List<SelectItem> getStartableWorkflows()
{
if (this.availableWorkflows == null)
// NOTE: we don't cache the list of startable workflows as they could get
// updated, in which case we need the latest instance id, they could
// theoretically also get removed.
this.availableWorkflows = new ArrayList<SelectItem>(4);
this.workflows = new HashMap<String, WorkflowDefinition>(4);
List<WorkflowDefinition> workflowDefs = this.workflowService.getDefinitions();
for (WorkflowDefinition workflowDef : workflowDefs)
{
this.availableWorkflows = new ArrayList<SelectItem>(4);
this.workflows = new HashMap<String, WorkflowDefinition>(4);
List<WorkflowDefinition> workflowDefs = this.workflowService.getDefinitions();
for (WorkflowDefinition workflowDef : workflowDefs)
String label = workflowDef.title;
if (workflowDef.description != null && workflowDef.description.length() > 0)
{
String label = workflowDef.title;
if (workflowDef.description != null && workflowDef.description.length() > 0)
{
label = label + " (" + workflowDef.description + ")";
}
this.availableWorkflows.add(new SelectItem(workflowDef.id, label));
this.workflows.put(workflowDef.id, workflowDef);
}
// set the initial selected workflow to the first in the list, unless there are no
// workflows, in which disable the next button
if (this.availableWorkflows.size() > 0)
{
this.selectedWorkflow = (String)this.availableWorkflows.get(0).getValue();
}
else
{
this.nextButtonDisabled = true;
label = label + " (" + workflowDef.description + ")";
}
this.availableWorkflows.add(new SelectItem(workflowDef.id, label));
this.workflows.put(workflowDef.id, workflowDef);
}
// set the initial selected workflow to the first in the list, unless there are no
// workflows, in which disable the next button
if (this.availableWorkflows.size() > 0)
{
this.selectedWorkflow = (String)this.availableWorkflows.get(0).getValue();
}
else
{
this.nextButtonDisabled = true;
}
return availableWorkflows;
@@ -559,4 +571,19 @@ public class StartWorkflowWizard extends BaseWizardBean
{
this.workflowService = workflowService;
}
// ------------------------------------------------------------------------------
// Helper methods
/**
* Resets the rich list
*/
protected void resetRichList()
{
if (this.packageItemsRichList != null)
{
this.packageItemsRichList.setValue(null);
this.packageItemsRichList = null;
}
}
}

View File

@@ -39,7 +39,7 @@ public class ViewCompletedTaskDialog extends ManageTaskDialog
}
@Override
public String getTitle()
public String getContainerTitle()
{
String titleStart = Application.getMessage(FacesContext.getCurrentInstance(), "view_completed_task_title");

View File

@@ -196,7 +196,10 @@ public class WorkflowBean
}
// add the targets for this particular association
params.put(assocQName, (Serializable)targets);
if (targets.size() > 0)
{
params.put(assocQName, (Serializable)targets);
}
}
return params;
@@ -223,19 +226,8 @@ public class WorkflowBean
node.getProperties().put("id", task.id);
// add the name of the source space (if there is one)
// TODO: remove this workaroud where JBPM may return a String and not the NodeRef
Serializable obj = task.properties.get(WorkflowModel.PROP_CONTEXT);
NodeRef context = null;
if (obj instanceof NodeRef)
{
context = (NodeRef)obj;
}
else if (obj instanceof String)
{
context = new NodeRef((String)obj);
}
if (context != null)
NodeRef context = (NodeRef)task.properties.get(WorkflowModel.PROP_CONTEXT);
if (context != null && this.nodeService.exists(context))
{
String name = Repository.getNameForNode(this.nodeService, context);
node.getProperties().put("sourceSpaceName", name);

View File

@@ -17,6 +17,7 @@
package org.alfresco.web.data;
import java.lang.reflect.Method;
import java.sql.Timestamp;
import java.text.CollationKey;
import java.text.Collator;
import java.util.ArrayList;
@@ -175,6 +176,10 @@ public abstract class Sort
{
this.comparator = new FloatComparator();
}
else if (returnType.equals(Timestamp.class))
{
this.comparator = new TimestampComparator();
}
else
{
s_logger.warn("Unsupported sort data type: " + returnType + " defaulting to .toString()");
@@ -408,6 +413,20 @@ public abstract class Sort
}
}
private static class TimestampComparator implements Comparator
{
/**
* @see org.alfresco.web.data.IDataComparator#compare(java.lang.Object, java.lang.Object)
*/
public int compare(final Object obj1, final Object obj2)
{
if (obj1 == null && obj2 == null) return 0;
if (obj1 == null) return -1;
if (obj2 == null) return 1;
return ((Timestamp)obj1).compareTo((Timestamp)obj2);
}
}
// ------------------------------------------------------------------------------
// Private Data

View File

@@ -55,6 +55,7 @@ import org.alfresco.service.cmr.model.FileFolderService;
import org.alfresco.service.cmr.model.FileInfo;
import org.alfresco.service.cmr.model.FileNotFoundException;
import org.alfresco.service.cmr.repository.InvalidNodeRefException;
import org.alfresco.service.cmr.repository.NoTransformerException;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.NodeService;
import org.alfresco.service.cmr.repository.Path;
@@ -968,7 +969,8 @@ public final class Utils
if (err != null)
{
if ((err instanceof InvalidNodeRefException == false &&
err instanceof AccessDeniedException == false) || logger.isDebugEnabled())
err instanceof AccessDeniedException == false &&
err instanceof NoTransformerException == false) || logger.isDebugEnabled())
{
logger.error(msg, err);
}

View File

@@ -111,165 +111,175 @@ public class ActionLinkRenderer extends BaseRenderer
*/
private String renderActionLink(FacesContext context, UIActionLink link)
{
Map attrs = link.getAttributes();
StringBuilder linkBuf = new StringBuilder(256);
// if there is no value for the link there will be no visible output
// on the page so don't bother rendering anything
String linkHtml = "";
Object linkValue = link.getValue();
if (link.getHref() == null)
if (linkValue != null)
{
linkBuf.append("<a href='#' onclick=\"");
Map attrs = link.getAttributes();
StringBuilder linkBuf = new StringBuilder(256);
// if we have an overriden onclick add that
if (link.getOnclick() != null)
if (link.getHref() == null)
{
linkBuf.append(link.getOnclick());
}
else
{
// generate JavaScript to set a hidden form field and submit
// a form which request attributes that we can decode
linkBuf.append(Utils.generateFormSubmit(context, link, Utils.getActionHiddenFieldName(context, link), link.getClientId(context), getParameterComponents(link)));
}
linkBuf.append('"');
}
else
{
String href = link.getHref();
// prefix the web context path if required
linkBuf.append("<a href=\"");
if (href.startsWith("/"))
{
linkBuf.append(context.getExternalContext().getRequestContextPath());
}
linkBuf.append(href);
// append arguments if specified
Map<String, String> actionParams = getParameterComponents(link);
if (actionParams != null)
{
boolean first = (href.indexOf('?') == -1);
for (String name : actionParams.keySet())
linkBuf.append("<a href='#' onclick=\"");
// if we have an overriden onclick add that
if (link.getOnclick() != null)
{
String paramValue = actionParams.get(name);
if (first)
{
linkBuf.append('?');
first = false;
}
else
{
linkBuf.append('&');
}
try
{
linkBuf.append(name).append("=").append(URLEncoder.encode(paramValue, "UTF-8"));
}
catch (UnsupportedEncodingException err)
{
// if this happens we have bigger problems than a missing URL parameter...!
}
}
}
linkBuf.append('"');
// output href 'target' attribute if supplied
if (link.getTarget() != null)
{
linkBuf.append(" target=\"")
.append(link.getTarget())
.append("\"");
}
}
if (attrs.get("style") != null)
{
linkBuf.append(" style=\"")
.append(attrs.get("style"))
.append('"');
}
if (attrs.get("styleClass") != null)
{
linkBuf.append(" class=")
.append(attrs.get("styleClass"));
}
if (link.getTooltip() != null)
{
linkBuf.append(" title=\"")
.append(Utils.encode(link.getTooltip()))
.append('"');
}
linkBuf.append('>');
StringBuilder buf = new StringBuilder(350);
if (link.getImage() != null)
{
int padding = link.getPadding();
if (padding != 0)
{
// TODO: make this width value a property!
buf.append("<table cellspacing=0 cellpadding=0><tr><td width=16>");
}
if (link.getShowLink() == false)
{
buf.append(linkBuf.toString());
}
// TODO: allow configuring of alignment attribute
buf.append(Utils.buildImageTag(context, link.getImage(), (String)link.getValue(), "absmiddle"));
if (link.getShowLink() == false)
{
buf.append("</a>");
}
else
{
if (padding != 0)
{
buf.append("</td><td style=\"padding:")
.append(padding)
.append("px\">");
linkBuf.append(link.getOnclick());
}
else
{
// TODO: add horizontal spacing as component property
buf.append("<span style='padding-left:2px");
// text next to an image may need alignment
if (attrs.get("verticalAlign") != null)
{
buf.append(";vertical-align:")
.append(attrs.get("verticalAlign"));
}
buf.append("'>");
// generate JavaScript to set a hidden form field and submit
// a form which request attributes that we can decode
linkBuf.append(Utils.generateFormSubmit(context, link, Utils.getActionHiddenFieldName(context, link), link.getClientId(context), getParameterComponents(link)));
}
buf.append(linkBuf.toString());
buf.append(Utils.encode(link.getValue().toString()));
buf.append("</a>");
linkBuf.append('"');
}
else
{
String href = link.getHref();
if (padding == 0)
// prefix the web context path if required
linkBuf.append("<a href=\"");
if (href.startsWith("/"))
{
buf.append("</span>");
linkBuf.append(context.getExternalContext().getRequestContextPath());
}
linkBuf.append(href);
// append arguments if specified
Map<String, String> actionParams = getParameterComponents(link);
if (actionParams != null)
{
boolean first = (href.indexOf('?') == -1);
for (String name : actionParams.keySet())
{
String paramValue = actionParams.get(name);
if (first)
{
linkBuf.append('?');
first = false;
}
else
{
linkBuf.append('&');
}
try
{
linkBuf.append(name).append("=").append(URLEncoder.encode(paramValue, "UTF-8"));
}
catch (UnsupportedEncodingException err)
{
// if this happens we have bigger problems than a missing URL parameter...!
}
}
}
linkBuf.append('"');
// output href 'target' attribute if supplied
if (link.getTarget() != null)
{
linkBuf.append(" target=\"")
.append(link.getTarget())
.append("\"");
}
}
if (padding != 0)
if (attrs.get("style") != null)
{
buf.append("</td></tr></table>");
linkBuf.append(" style=\"")
.append(attrs.get("style"))
.append('"');
}
}
else
{
buf.append(linkBuf.toString());
buf.append(Utils.encode(link.getValue().toString()));
buf.append("</a>");
if (attrs.get("styleClass") != null)
{
linkBuf.append(" class=")
.append(attrs.get("styleClass"));
}
if (link.getTooltip() != null)
{
linkBuf.append(" title=\"")
.append(Utils.encode(link.getTooltip()))
.append('"');
}
linkBuf.append('>');
StringBuilder buf = new StringBuilder(350);
if (link.getImage() != null)
{
int padding = link.getPadding();
if (padding != 0)
{
// TODO: make this width value a property!
buf.append("<table cellspacing=0 cellpadding=0><tr><td width=16>");
}
if (link.getShowLink() == false)
{
buf.append(linkBuf.toString());
}
// TODO: allow configuring of alignment attribute
buf.append(Utils.buildImageTag(context, link.getImage(), (String)link.getValue(), "absmiddle"));
if (link.getShowLink() == false)
{
buf.append("</a>");
}
else
{
if (padding != 0)
{
buf.append("</td><td style=\"padding:")
.append(padding)
.append("px\">");
}
else
{
// TODO: add horizontal spacing as component property
buf.append("<span style='padding-left:2px");
// text next to an image may need alignment
if (attrs.get("verticalAlign") != null)
{
buf.append(";vertical-align:")
.append(attrs.get("verticalAlign"));
}
buf.append("'>");
}
buf.append(linkBuf.toString());
buf.append(Utils.encode(link.getValue().toString()));
buf.append("</a>");
if (padding == 0)
{
buf.append("</span>");
}
}
if (padding != 0)
{
buf.append("</td></tr></table>");
}
}
else
{
buf.append(linkBuf.toString());
buf.append(Utils.encode(link.getValue().toString()));
buf.append("</a>");
}
linkHtml = buf.toString();
}
return buf.toString();
return linkHtml;
}
/**
@@ -282,71 +292,81 @@ public class ActionLinkRenderer extends BaseRenderer
*/
private String renderMenuAction(FacesContext context, UIActionLink link, int padding)
{
StringBuilder buf = new StringBuilder(256);
// if there is no value for the link there will be no visible output
// on the page so don't bother rendering anything
String linkHtml = "";
Object linkValue = link.getValue();
buf.append("<tr><td>");
// render image cell first for a menu
if (link.getImage() != null)
if (linkValue != null)
{
buf.append(Utils.buildImageTag(context, link.getImage(), (String)link.getValue()));
}
buf.append("</td><td");
if (padding != 0)
{
buf.append(" style=\"padding:")
.append(padding)
.append("px\"");
}
buf.append(">");
// render text link cell for the menu
if (link.getHref() == null)
{
buf.append("<a href='#' onclick=\"");
buf.append(Utils.generateFormSubmit(context, link, Utils.getActionHiddenFieldName(context, link), link.getClientId(context), getParameterComponents(link)));
buf.append('"');
}
else
{
String href = link.getHref();
if (href.startsWith("http") == false)
{
href = context.getExternalContext().getRequestContextPath() + href;
}
buf.append("<a href=\"")
.append(href)
.append('"');
StringBuilder buf = new StringBuilder(256);
// output href 'target' attribute if supplied
if (link.getTarget() != null)
buf.append("<tr><td>");
// render image cell first for a menu
if (link.getImage() != null)
{
buf.append(" target=\"")
.append(link.getTarget())
.append("\"");
buf.append(Utils.buildImageTag(context, link.getImage(), (String)link.getValue()));
}
buf.append("</td><td");
if (padding != 0)
{
buf.append(" style=\"padding:")
.append(padding)
.append("px\"");
}
buf.append(">");
// render text link cell for the menu
if (link.getHref() == null)
{
buf.append("<a href='#' onclick=\"");
buf.append(Utils.generateFormSubmit(context, link, Utils.getActionHiddenFieldName(context, link), link.getClientId(context), getParameterComponents(link)));
buf.append('"');
}
else
{
String href = link.getHref();
if (href.startsWith("http") == false)
{
href = context.getExternalContext().getRequestContextPath() + href;
}
buf.append("<a href=\"")
.append(href)
.append('"');
// output href 'target' attribute if supplied
if (link.getTarget() != null)
{
buf.append(" target=\"")
.append(link.getTarget())
.append("\"");
}
}
Map attrs = link.getAttributes();
if (attrs.get("style") != null)
{
buf.append(" style=\"")
.append(attrs.get("style"))
.append('"');
}
if (attrs.get("styleClass") != null)
{
buf.append(" class=")
.append(attrs.get("styleClass"));
}
buf.append('>');
buf.append(Utils.encode(link.getValue().toString()));
buf.append("</a>");
buf.append("</td></tr>");
linkHtml = buf.toString();
}
Map attrs = link.getAttributes();
if (attrs.get("style") != null)
{
buf.append(" style=\"")
.append(attrs.get("style"))
.append('"');
}
if (attrs.get("styleClass") != null)
{
buf.append(" class=")
.append(attrs.get("styleClass"));
}
buf.append('>');
buf.append(Utils.encode(link.getValue().toString()));
buf.append("</a>");
buf.append("</td></tr>");
return buf.toString();
return linkHtml;
}

View File

@@ -37,8 +37,6 @@ import javax.faces.model.SelectItem;
import org.alfresco.web.app.Application;
import org.alfresco.web.ui.common.Utils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* @author kevinr
@@ -58,8 +56,6 @@ public class DatePickerRenderer extends BaseRenderer
private static final int CMD_SET = 1;
private static final int CMD_RESET = 2;
private static final int CMD_TODAY = 3;
private static final Log logger = LogFactory.getLog(DatePickerRenderer.class);
/**
* @see javax.faces.render.Renderer#decode(javax.faces.context.FacesContext, javax.faces.component.UIComponent)
@@ -168,6 +164,7 @@ public class DatePickerRenderer extends BaseRenderer
* input component must render the submitted value if it's set, and use the local
* value only if there is no submitted value.
*/
@SuppressWarnings("deprecation")
public void encodeBegin(FacesContext context, UIComponent component)
throws IOException
{
@@ -178,6 +175,7 @@ public class DatePickerRenderer extends BaseRenderer
String clientId = component.getClientId(context);
ResponseWriter out = context.getResponseWriter();
String cmdFieldName = clientId + FIELD_CMD;
Boolean initIfNull = (Boolean)component.getAttributes().get("initialiseIfNull");
// this is part of the spec:
// first you attempt to build the date from the submitted value
@@ -188,12 +186,19 @@ public class DatePickerRenderer extends BaseRenderer
}
else
{
// second if no submitted value is found, default to the current value
// second - if no submitted value is found, default to the current value
Object value = ((ValueHolder)component).getValue();
if (value instanceof Date)
{
date = (Date)value;
}
// third - if no date is present and the initialiseIfNull attribute
// is set to true set the date to today's date
if (date == null && initIfNull != null && initIfNull.booleanValue())
{
date = new Date();
}
}
// create a flag to show if the component is disabled
@@ -253,18 +258,23 @@ public class DatePickerRenderer extends BaseRenderer
out.write("&nbsp;");
// render 2 links (if the component is not disabled) to allow the user to reset the
// date back to null or to select today's date
// date back to null (if initialiseIfNull is false) or to select today's date
if (disabled.booleanValue() == false)
{
out.write("<input type=\"button\" onclick=\"");
out.write(Utils.generateFormSubmit(context, component, cmdFieldName, Integer.toString(CMD_TODAY)));
out.write("\" value=\"");
out.write(Application.getMessage(context, "today"));
out.write("\">&nbsp;<input type=\"button\" onclick=\"");
out.write(Utils.generateFormSubmit(context, component, cmdFieldName, Integer.toString(CMD_RESET)));
out.write("\" value=\"");
out.write(Application.getMessage(context, "none"));
out.write("\">");
out.write("\">&nbsp;");
if (initIfNull != null && initIfNull.booleanValue() == false)
{
out.write("<input type=\"button\" onclick=\"");
out.write(Utils.generateFormSubmit(context, component, cmdFieldName, Integer.toString(CMD_RESET)));
out.write("\" value=\"");
out.write(Application.getMessage(context, "none"));
out.write("\">");
}
}
}
else
@@ -412,7 +422,7 @@ public class DatePickerRenderer extends BaseRenderer
Locale locale = Application.getLanguage(FacesContext.getCurrentInstance());
if (locale == null)
{
locale = locale.getDefault();
locale = Locale.getDefault();
}
DateFormatSymbols dfs = new DateFormatSymbols(locale);
String[] names = dfs.getMonths();

View File

@@ -60,6 +60,7 @@ public class InputDatePickerTag extends HtmlComponentTag
this.value = null;
this.showTime = null;
this.disabled = null;
this.initIfNull = null;
}
/**
@@ -75,6 +76,7 @@ public class InputDatePickerTag extends HtmlComponentTag
setStringProperty(component, "value", this.value);
setBooleanProperty(component, "showTime", this.showTime);
setBooleanProperty(component, "disabled", this.disabled);
setBooleanProperty(component, "initialiseIfNull", this.initIfNull);
}
/**
@@ -127,9 +129,22 @@ public class InputDatePickerTag extends HtmlComponentTag
this.disabled = disabled;
}
/**
* Sets whether today's date should be shown initially if the underlying
* model value is null. This will also hide the None button thus disallowing
* the user to set the date back to null.
*
* @param initialiseIfNull true to show today's date instead of 'None'
*/
public void setInitialiseIfNull(String initialiseIfNull)
{
this.initIfNull = initialiseIfNull;
}
private String startYear = null;
private String yearCount = null;
private String value = null;
private String showTime = null;
private String disabled = null;
private String initIfNull = null;
}

View File

@@ -186,6 +186,9 @@ public class UIDialogButtons extends SelfRenderingComponent
if (logger.isDebugEnabled())
logger.debug("Adding " + buttons.size() + " additional buttons: " + buttons);
// add a spacing row to separate the additional buttons from the OK button
addSpacingRow(context);
for (DialogButtonConfig buttonCfg : buttons)
{
UICommand button = (UICommand)context.getApplication().

View File

@@ -319,6 +319,7 @@ public class UISearchCustomProperties extends SelfRenderingComponent implements
inputFromDate.setRendererType(RepoConstants.ALFRESCO_FACES_DATE_PICKER_RENDERER);
inputFromDate.setValueBinding("startYear", startYearBind);
inputFromDate.setValueBinding("yearCount", yearCountBind);
inputFromDate.getAttributes().put("initialiseIfNull", Boolean.TRUE);
inputFromDate.getAttributes().put("showTime", showTime);
ValueBinding vbFromDate = facesApp.createValueBinding(
"#{" + beanBinding + "[\"" + PREFIX_DATE_FROM + propDef.getName().toString() + "\"]}");
@@ -338,6 +339,7 @@ public class UISearchCustomProperties extends SelfRenderingComponent implements
inputToDate.setRendererType(RepoConstants.ALFRESCO_FACES_DATE_PICKER_RENDERER);
inputToDate.setValueBinding("startYear", startYearBind);
inputToDate.setValueBinding("yearCount", yearCountBind);
inputToDate.getAttributes().put("initialiseIfNull", Boolean.TRUE);
inputToDate.getAttributes().put("showTime", showTime);
ValueBinding vbToDate = facesApp.createValueBinding(
"#{" + beanBinding + "[\"" + PREFIX_DATE_TO + propDef.getName().toString() + "\"]}");

View File

@@ -103,19 +103,25 @@ public class UIWorkflowSummary extends SelfRenderingComponent
out.write(userName);
}
out.write("</td></tr><tr><td>");
out.write(bundle.getString("start_date"));
out.write(bundle.getString("started_on"));
out.write(":</td><td>");
if (wi.startDate != null)
{
out.write(format.format(wi.startDate));
}
out.write("</td></tr><tr><td>");
out.write(bundle.getString("due_date"));
out.write(bundle.getString("completed_on"));
out.write(":</td><td>");
if (wi.endDate != null)
{
out.write(format.format(wi.endDate));
}
else
{
out.write("&lt;");
out.write(bundle.getString("in_progress"));
out.write("&gt;");
}
out.write("</td></tr></table>");
}
}

View File

@@ -75,6 +75,7 @@ public class UIPropertySheet extends UIPanel implements NamingContainer
private Boolean validationEnabled;
private String mode;
private String configArea;
private String nextButtonId;
private String finishButtonId;
/**
@@ -219,6 +220,7 @@ public class UIPropertySheet extends UIPanel implements NamingContainer
this.validationEnabled = (Boolean)values[7];
this.validations = (List<ClientValidation>)values[8];
this.finishButtonId = (String)values[9];
this.nextButtonId = (String)values[10];
}
/**
@@ -226,7 +228,7 @@ public class UIPropertySheet extends UIPanel implements NamingContainer
*/
public Object saveState(FacesContext context)
{
Object values[] = new Object[10];
Object values[] = new Object[11];
// standard component attributes are saved by the super class
values[0] = super.saveState(context);
values[1] = this.nodeRef;
@@ -238,6 +240,7 @@ public class UIPropertySheet extends UIPanel implements NamingContainer
values[7] = this.validationEnabled;
values[8] = this.validations;
values[9] = this.finishButtonId;
values[10] = this.nextButtonId;
return (values);
}
@@ -391,6 +394,26 @@ public class UIPropertySheet extends UIPanel implements NamingContainer
{
this.finishButtonId = finishButtonId;
}
/**
* Returns the id of the next button
*
* @return The id of the next button on the page
*/
public String getNextButtonId()
{
return this.nextButtonId;
}
/**
* Sets the id of the next button being used on the page
*
* @param nextButtonId The id of the next button
*/
public void setNextButtonId(String nextButtonId)
{
this.nextButtonId = nextButtonId;
}
/**
* @return Returns the mode
@@ -487,8 +510,12 @@ public class UIPropertySheet extends UIPanel implements NamingContainer
ResponseWriter out = context.getResponseWriter();
UIForm form = Utils.getParentForm(context, this);
// TODO: We need to encode all the JavaScript functions here
// with the client id of the property sheet so that we
// can potentially add more than one property sheet to
// page and have validation function correctly.
// output the validation.js script
// TODO: make sure its only included once per page!!
out.write("\n<script type='text/javascript' src='");
out.write(context.getExternalContext().getRequestContextPath());
out.write("/scripts/validation.js");
@@ -496,9 +523,11 @@ public class UIPropertySheet extends UIPanel implements NamingContainer
// output variable to hold flag for which submit button was pressed
out.write("var finishButtonPressed = false;\n");
out.write("var nextButtonPressed = false;\n");
// output the validate() function
out.write("function validate()\n{\n var result = true;\n if (finishButtonPressed && (");
out.write("function validate()\n{\n var result = true;\n ");
out.write("if ((finishButtonPressed || nextButtonPressed) && (");
int numberValidations = this.validations.size();
List<ClientValidation> realTimeValidations =
@@ -518,7 +547,8 @@ public class UIPropertySheet extends UIPanel implements NamingContainer
// return false if validation failed to stop the form submitting
out.write(")\n { result = false; }\n\n");
out.write(" finishButtonPressed = false;\n return result;\n}\n\n");
out.write(" finishButtonPressed = false;\n nextButtonPressed = false;\n");
out.write(" return result;\n}\n\n");
// output the processButtonState() function (if necessary)
int numberRealTimeValidations = realTimeValidations.size();
@@ -532,17 +562,41 @@ public class UIPropertySheet extends UIPanel implements NamingContainer
(x == (numberRealTimeValidations-1)), false);
}
// disable the finish button if validation failed
out.write("\n { document.getElementById('");
// disable the finish button if validation failed and
// also the next button if it is present
out.write("\n {\n document.getElementById('");
out.write(form.getClientId(context));
out.write(NamingContainer.SEPARATOR_CHAR);
out.write(getFinishButtonId());
out.write("').disabled = true; }\n");
out.write(" else { document.getElementById('");
out.write("').disabled = true; \n");
if (this.nextButtonId != null && this.nextButtonId.length() > 0)
{
out.write(" document.getElementById('");
out.write(form.getClientId(context));
out.write(NamingContainer.SEPARATOR_CHAR);
out.write(this.nextButtonId);
out.write("').disabled = true; \n");
}
out.write(" }\n");
out.write(" else\n {\n document.getElementById('");
out.write(form.getClientId(context));
out.write(NamingContainer.SEPARATOR_CHAR);
out.write(getFinishButtonId());
out.write("').disabled = false; }\n}\n\n");
out.write("').disabled = false;");
if (this.nextButtonId != null && this.nextButtonId.length() > 0)
{
out.write("\n document.getElementById('");
out.write(form.getClientId(context));
out.write(NamingContainer.SEPARATOR_CHAR);
out.write(this.nextButtonId);
out.write("').disabled = false;");
}
out.write("\n }\n}\n\n");
}
// write out a function to initialise everything
@@ -560,6 +614,16 @@ public class UIPropertySheet extends UIPanel implements NamingContainer
out.write(getFinishButtonId());
out.write("').onclick = function() { finishButtonPressed = true; }\n");
// set the flag when the finish button is clicked
if (this.nextButtonId != null && this.nextButtonId.length() > 0)
{
out.write(" document.getElementById('");
out.write(form.getClientId(context));
out.write(NamingContainer.SEPARATOR_CHAR);
out.write(this.nextButtonId);
out.write("').onclick = function() { nextButtonPressed = true; }\n");
}
// perform an initial check at page load time (if we have any real time validations)
if (numberRealTimeValidations > 0)
{

View File

@@ -39,6 +39,7 @@ public class PropertySheetGridTag extends BaseComponentTag
private String cellpadding;
private String cellspacing;
private String finishButtonId;
private String nextButtonId;
/**
* @see javax.faces.webapp.UIComponentTag#getComponentType()
@@ -144,6 +145,14 @@ public class PropertySheetGridTag extends BaseComponentTag
this.cellspacing = cellspacing;
}
/**
* @param nextButtonId Sets the next button id
*/
public void setNextButtonId(String nextButtonId)
{
this.nextButtonId = nextButtonId;
}
/**
* @param finishButtonId Sets the finish button id
*/
@@ -171,6 +180,7 @@ public class PropertySheetGridTag extends BaseComponentTag
setStringStaticProperty(component, "cellpadding", this.cellpadding);
setStringStaticProperty(component, "cellspacing", this.cellspacing);
setStringStaticProperty(component, "finishButtonId", this.finishButtonId);
setStringStaticProperty(component, "nextButtonId", this.nextButtonId);
}
/**
@@ -190,6 +200,7 @@ public class PropertySheetGridTag extends BaseComponentTag
this.cellpadding = null;
this.cellspacing = null;
this.finishButtonId = null;
this.nextButtonId = null;
super.release();
}

View File

@@ -73,6 +73,17 @@
<rtexprvalue>true</rtexprvalue>
<description>The number of years to display in the list</description>
</attribute>
<attribute>
<name>initialiseIfNull</name>
<required>false</required>
<rtexprvalue>true</rtexprvalue>
<description>
Whether to initialise the control showing today's date if the
underlying value in the model is null. This will also hide the
None button thus disallowing the user to set the date back to null.
</description>
</attribute>
</tag>
<tag>

View File

@@ -1021,6 +1021,10 @@
<property-name>contentService</property-name>
<value>#{ContentService}</value>
</managed-property>
<managed-property>
<property-name>workflowService</property-name>
<value>#{WorkflowService}</value>
</managed-property>
</managed-bean>
<managed-bean>
@@ -2071,6 +2075,10 @@
<property-name>yearCount</property-name>
<value>30</value>
</managed-property>
<managed-property>
<property-name>initialiseIfNull</property-name>
<value>false</value>
</managed-property>
-->
</managed-bean>
@@ -2090,6 +2098,10 @@
<property-name>yearCount</property-name>
<value>30</value>
</managed-property>
<managed-property>
<property-name>initialiseIfNull</property-name>
<value>false</value>
</managed-property>
-->
</managed-bean>

View File

@@ -61,6 +61,12 @@
<rtexprvalue>true</rtexprvalue>
</attribute>
<attribute>
<name>nextButtonId</name>
<required>false</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
<attribute>
<name>var</name>
<required>false</required>

View File

@@ -91,22 +91,18 @@
<filter-class>org.alfresco.repo.webdav.auth.NTLMAuthenticationFilter</filter-class>
-->
</filter>
<!-- -->
<!-- JBPM START -->
<!-- Used by the JPBM console for monitoring in-flight workflows -->
<!-- TODO: Remove this block - just a temporary placement for testing -->
<!-- -->
<filter>
<filter-name>Admin Authentication Filter</filter-name>
<filter-class>org.alfresco.web.app.servlet.AdminAuthenticationFilter</filter-class>
</filter>
<!-- Used by the JPBM console for monitoring in-flight workflows -->
<filter>
<filter-name>JbpmContextFilter</filter-name>
<filter-class>org.jbpm.webapp.filter.JbpmContextFilter</filter-class>
</filter>
<!-- -->
<!-- JBPM END -->
<!-- -->
<filter-mapping>
<filter-name>Authentication Filter</filter-name>
<url-pattern>/faces/*</url-pattern>
@@ -126,21 +122,47 @@
<url-pattern>/webdav/*</url-pattern>
</filter-mapping>
<!-- -->
<!-- JBPM START -->
<filter-mapping>
<filter-name>Admin Authentication Filter</filter-name>
<url-pattern>/faces/jsp/admin/*</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>Admin Authentication Filter</filter-name>
<url-pattern>/faces/jsp/categories/*</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>Admin Authentication Filter</filter-name>
<url-pattern>/faces/jsp/groups/*</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>Admin Authentication Filter</filter-name>
<url-pattern>/faces/jsp/users/delete-user.jsp</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>Admin Authentication Filter</filter-name>
<url-pattern>/faces/jsp/users/users.jsp</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>Admin Authentication Filter</filter-name>
<url-pattern>/faces/jsp/dialog/system-info.jsp</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>Admin Authentication Filter</filter-name>
<url-pattern>/faces/jbpm/*</url-pattern>
</filter-mapping>
<!-- Used by the JPBM console for monitoring in-flight workflows -->
<!-- TODO: Remove this block - just a temporary placement for testing -->
<!-- -->
<filter-mapping>
<filter-name>JbpmContextFilter</filter-name>
<url-pattern>/faces/jbpm/*</url-pattern>
</filter-mapping>
<!-- -->
<!-- JBPM END -->
<!-- -->
<listener>
<listener-class>org.apache.myfaces.webapp.StartupServletContextListener</listener-class>
</listener>
@@ -170,6 +192,11 @@
<servlet-class>org.alfresco.web.app.servlet.DownloadContentServlet</servlet-class>
</servlet>
<servlet>
<servlet-name>guestDownloadContent</servlet-name>
<servlet-class>org.alfresco.web.app.servlet.GuestDownloadContentServlet</servlet-class>
</servlet>
<servlet>
<servlet-name>externalAccess</servlet-name>
<servlet-class>org.alfresco.web.app.servlet.ExternalAccessServlet</servlet-class>
@@ -230,6 +257,11 @@
<url-pattern>/download/*</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>guestDownloadContent</servlet-name>
<url-pattern>/guestDownload/*</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>externalAccess</servlet-name>
<url-pattern>/navigate/*</url-pattern>
@@ -274,7 +306,7 @@
</welcome-file-list>
<error-page>
<error-code>500</error-code>
<exception-type>java.lang.Exception</exception-type>
<location>/jsp/error.jsp</location>
</error-page>

Binary file not shown.

After

Width:  |  Height:  |  Size: 601 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 626 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 565 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1023 B

View File

@@ -301,20 +301,20 @@
<td colspan="2"><h:selectBooleanCheckbox value="#{AdvancedSearchBean.modifiedDateChecked}" id="chkModDate" /><span style="vertical-align:20%"><h:outputText value="#{msg.modified_date}" id="modDate" />:</span></td>
</tr>
<tr>
<td style="padding-left:8px"><h:outputText value="#{msg.from}" id="modDateFrom" />:</td><td><a:inputDatePicker value="#{AdvancedSearchBean.modifiedDateFrom}" yearCount="#{DatePickerGenerator.yearCount}" startYear="#{DatePickerGenerator.startYear}" id="dateModFrom" /></td>
<td style="padding-left:8px"><h:outputText value="#{msg.from}" id="modDateFrom" />:</td><td><a:inputDatePicker value="#{AdvancedSearchBean.modifiedDateFrom}" yearCount="#{DatePickerGenerator.yearCount}" startYear="#{DatePickerGenerator.startYear}" id="dateModFrom" initialiseIfNull="true" /></td>
</tr>
<tr>
<td style="padding-left:8px"><h:outputText value="#{msg.to}" id="modDateTo" />:</td><td><a:inputDatePicker value="#{AdvancedSearchBean.modifiedDateTo}" yearCount="#{DatePickerGenerator.yearCount}" startYear="#{DatePickerGenerator.startYear}" id="dateModTo" /></td>
<td style="padding-left:8px"><h:outputText value="#{msg.to}" id="modDateTo" />:</td><td><a:inputDatePicker value="#{AdvancedSearchBean.modifiedDateTo}" yearCount="#{DatePickerGenerator.yearCount}" startYear="#{DatePickerGenerator.startYear}" id="dateModTo" initialiseIfNull="true" /></td>
</tr>
<tr>
<td colspan="2"><h:selectBooleanCheckbox value="#{AdvancedSearchBean.createdDateChecked}" id="chkCreateDate" /><span style="vertical-align:20%"><h:outputText value="#{msg.created_date}" id="createDate" />:</span></td>
</tr>
<tr>
<td style="padding-left:8px"><h:outputText value="#{msg.from}" id="createDateFrom" />:</td><td><a:inputDatePicker value="#{AdvancedSearchBean.createdDateFrom}" yearCount="#{DatePickerGenerator.yearCount}" startYear="#{DatePickerGenerator.startYear}" id="dateCreatedFrom" /></td>
<td style="padding-left:8px"><h:outputText value="#{msg.from}" id="createDateFrom" />:</td><td><a:inputDatePicker value="#{AdvancedSearchBean.createdDateFrom}" yearCount="#{DatePickerGenerator.yearCount}" startYear="#{DatePickerGenerator.startYear}" id="dateCreatedFrom" initialiseIfNull="true" /></td>
</tr>
<tr>
<td style="padding-left:8px"><h:outputText value="#{msg.to}" id="createDateTo" />:</td><td><a:inputDatePicker value="#{AdvancedSearchBean.createdDateTo}" yearCount="#{DatePickerGenerator.yearCount}" startYear="#{DatePickerGenerator.startYear}" id="dateCreatedTo" /></td>
<td style="padding-left:8px"><h:outputText value="#{msg.to}" id="createDateTo" />:</td><td><a:inputDatePicker value="#{AdvancedSearchBean.createdDateTo}" yearCount="#{DatePickerGenerator.yearCount}" startYear="#{DatePickerGenerator.startYear}" id="dateCreatedTo" initialiseIfNull="true" /></td>
</tr>
</table>
<div style="padding:4px"></div>

View File

@@ -31,11 +31,14 @@
<a:panel id="resources-panel" label="#{msg.resources}"
border="white" bgcolor="white" titleBorder="blue" titleBgcolor="#D3E6FE" styleClass="mainSubTitle">
<h:outputText value="#{msg.no_resources}" rendered="#{empty DialogManager.bean.resources}" />
<a:richList id="resources-list" viewMode="details" value="#{DialogManager.bean.resources}" var="r"
binding="#{DialogManager.bean.packageItemsRichList}"
styleClass="recordSet" headerStyleClass="recordSetHeader" rowStyleClass="recordSetRow"
altRowStyleClass="recordSetRowAlt" width="100%" pageSize="10"
initialSortColumn="name" initialSortDescending="true">
initialSortColumn="name" initialSortDescending="true"
rendered="#{not empty DialogManager.bean.resources}">
<%-- Name column --%>
<a:column id="col1" primary="true" width="200" style="padding:2px; text-align:left">
@@ -63,7 +66,8 @@
<f:facet name="header">
<a:sortLink id="col3-sort" label="#{msg.path}" value="path" styleClass="header"/>
</f:facet>
<r:nodePath id="col3-path" value="#{r.path}" />
<r:nodePath id="col3-path" value="#{r.path}" action="dialog:close:browse"
actionListener="#{BrowseBean.clickSpacePath}" />
</a:column>
<%-- Created Date column --%>

View File

@@ -42,7 +42,8 @@
titleBorder="blue" titleBgcolor="#D3E6FE" styleClass="mainSubTitle">
<r:propertySheetGrid id="task-props" value="#{WizardManager.bean.taskMetadataNode}"
var="taskProps" columns="1" externalConfig="true" />
var="taskProps" columns="1" externalConfig="true"
nextButtonId="next-button" />
</a:panel>
<h:outputText id="padding" styleClass="paddingRow" value="&nbsp;" escape="false" />
@@ -81,7 +82,7 @@
<f:facet name="header">
<a:sortLink id="col3-sort" label="#{msg.path}" value="path" styleClass="header"/>
</f:facet>
<r:nodePath id="col3-path" value="#{r.path}" />
<r:nodePath id="col3-path" value="#{r.path}" disabled="true" />
</a:column>
<%-- Created Date column --%>

View File

@@ -3,10 +3,13 @@
<%@ taglib uri="/WEB-INF/alfresco.tld" prefix="a" %>
<%@ taglib uri="/WEB-INF/repo.tld" prefix="r" %>
<h:outputText value="#{msg.no_tasks}" rendered="#{empty WorkflowBean.tasksCompleted}" />
<a:richList id="tasks-completed-list" viewMode="details" value="#{WorkflowBean.tasksCompleted}" var="r"
styleClass="recordSet" headerStyleClass="recordSetHeader" rowStyleClass="recordSetRow"
altRowStyleClass="recordSetRowAlt" width="100%" pageSize="10"
initialSortColumn="name" initialSortDescending="true">
initialSortColumn="bpm:completionDate" initialSortDescending="true"
rendered="#{not empty WorkflowBean.tasksCompleted}">
<%-- Primary column for details view mode --%>
<a:column id="col1" primary="true" width="200" style="padding:2px;text-align:left">
@@ -14,57 +17,78 @@
<a:sortLink id="col1-sort" label="#{msg.title}" value="name" mode="case-insensitive" styleClass="header"/>
</f:facet>
<f:facet name="small-icon">
<a:actionLink id="col1-act1" value="#{r.name}" image="/images/icons/completed_workflow_task.gif" showLink="false"
<a:actionLink id="col1-act1" value="#{r['bpm:description']}" image="/images/icons/completed_workflow_task.gif" showLink="false"
actionListener="#{DialogManager.setupParameters}" action="dialog:viewCompletedTask">
<f:param name="id" value="#{r.id}" />
</a:actionLink>
</f:facet>
<a:actionLink id="col1-act2" value="#{r.name}" actionListener="#{DialogManager.setupParameters}"
<a:actionLink id="col1-act2" value="#{r['bpm:description']}" actionListener="#{DialogManager.setupParameters}"
action="dialog:viewCompletedTask">
<f:param name="id" value="#{r.id}" />
</a:actionLink>
</a:column>
<%-- Task id column --%>
<a:column id="col2" style="text-align:left">
<a:column id="col2" style="padding:2px;text-align:left">
<f:facet name="header">
<a:sortLink id="col2-sort" label="#{msg.id}" value="bpm:taskId" styleClass="header"/>
</f:facet>
<h:outputText id="col2-txt" value="#{r['bpm:taskId']}" />
</a:column>
<%-- Task type --%>
<a:column id="col2a" style="padding:2px;text-align:left">
<f:facet name="header">
<a:sortLink id="col2a-sort" label="#{msg.type}" value="name" mode="case-insensitive" styleClass="header"/>
</f:facet>
<h:outputText id="col2a-txt" value="#{r.name}" />
</a:column>
<%-- Source column --%>
<a:column id="col3" style="text-align:left">
<a:column id="col3" style="padding:2px;text-align:left">
<f:facet name="header">
<a:sortLink id="col3-sort" label="#{msg.source}" value="sourceSpaceName" styleClass="header"/>
</f:facet>
<h:outputText id="col3-txt" value="#{r.sourceSpaceName}" />
<a:actionLink id="col3-act1" value="#{r.sourceSpaceName}"
actionListener="#{BrowseBean.clickSpace}" action="browse">
<f:param name="id" value="#{r.sourceSpaceId}" />
</a:actionLink>
</a:column>
<%-- Created Date column --%>
<a:column id="col4" style="padding:2px;text-align:left">
<f:facet name="header">
<a:sortLink id="col4-sort" label="#{msg.created}" value="created" styleClass="header"/>
</f:facet>
<h:outputText id="col4-txt" value="#{r.created}">
<a:convertXMLDate type="both" pattern="#{msg.date_time_pattern}" />
</h:outputText>
</a:column>
<%-- Completed date column --%>
<a:column id="col4" style="text-align:left">
<a:column id="col5" style="padding:2px;text-align:left">
<f:facet name="header">
<a:sortLink id="col4-sort" label="#{msg.completed_on}" value="bpm:completionDate" styleClass="header"/>
<a:sortLink id="col5-sort" label="#{msg.completed_on}" value="bpm:completionDate" styleClass="header"/>
</f:facet>
<h:outputText id="col4-txt" value="#{r['bpm:completionDate']}">
<h:outputText id="col5-txt" value="#{r['bpm:completionDate']}">
<a:convertXMLDate type="both" pattern="#{msg.date_pattern}" />
</h:outputText>
</a:column>
<%-- Outcome column --%>
<a:column id="col5" style="text-align:left">
<a:column id="col6" style="padding:2px;text-align:left">
<f:facet name="header">
<a:sortLink id="col5-sort" label="#{msg.outcome}" value="outcome" styleClass="header"/>
<a:sortLink id="col6-sort" label="#{msg.outcome}" value="outcome" styleClass="header"/>
</f:facet>
<h:outputText id="col5-txt" value="#{r.outcome}" />
<h:outputText id="col6-txt" value="#{r.outcome}" />
</a:column>
<%-- Actions column --%>
<a:column id="col6" actions="true" style="text-align:left">
<a:column id="col7" actions="true" style="padding:2px;text-align:left">
<f:facet name="header">
<h:outputText id="col6-txt" value="#{msg.actions}"/>
<h:outputText id="col7-txt" value="#{msg.actions}"/>
</f:facet>
<r:actions id="col6-actions" value="dashlet_completed_actions" context="#{r}" showLink="false"
<r:actions id="col7-actions" value="dashlet_completed_actions" context="#{r}" showLink="false"
styleClass="inlineAction" />
</a:column>

View File

@@ -3,76 +3,100 @@
<%@ taglib uri="/WEB-INF/alfresco.tld" prefix="a" %>
<%@ taglib uri="/WEB-INF/repo.tld" prefix="r" %>
<h:outputText value="#{msg.no_tasks}" rendered="#{empty WorkflowBean.tasksToDo}" />
<a:richList id="tasks-todo-list" viewMode="details" value="#{WorkflowBean.tasksToDo}" var="r"
styleClass="recordSet" headerStyleClass="recordSetHeader" rowStyleClass="recordSetRow"
altRowStyleClass="recordSetRowAlt" width="100%" pageSize="10"
initialSortColumn="name" initialSortDescending="true">
initialSortColumn="created" initialSortDescending="true"
rendered="#{not empty WorkflowBean.tasksToDo}">
<%-- Primary column for details view mode --%>
<a:column id="col1" primary="true" width="200" style="padding:2px;text-align:left">
<f:facet name="header">
<a:sortLink id="col1-sort" label="#{msg.title}" value="name" mode="case-insensitive" styleClass="header"/>
</f:facet>
<f:facet name="small-icon">
<a:actionLink id="col1-act1" value="#{r.name}" image="/images/icons/workflow_task.gif" showLink="false"
<a:actionLink id="col1-act1" value="#{r['bpm:description']}" image="/images/icons/workflow_task.gif" showLink="false"
actionListener="#{DialogManager.setupParameters}" action="dialog:manageTask">
<f:param name="id" value="#{r.id}" />
</a:actionLink>
</f:facet>
<a:actionLink id="col1-act2" value="#{r.name}" actionListener="#{DialogManager.setupParameters}"
<a:actionLink id="col1-act2" value="#{r['bpm:description']}" actionListener="#{DialogManager.setupParameters}"
action="dialog:manageTask">
<f:param name="id" value="#{r.id}" />
</a:actionLink>
</a:column>
<%-- Task id column --%>
<a:column id="col2" style="text-align:left">
<a:column id="col2" style="padding:2px;text-align:left">
<f:facet name="header">
<a:sortLink id="col2-sort" label="#{msg.id}" value="bpm:taskId" styleClass="header"/>
</f:facet>
<h:outputText id="col2-txt" value="#{r['bpm:taskId']}" />
</a:column>
<%-- Task type --%>
<a:column id="col2a" style="padding:2px;text-align:left">
<f:facet name="header">
<a:sortLink id="col2a-sort" label="#{msg.type}" value="name" mode="case-insensitive" styleClass="header"/>
</f:facet>
<h:outputText id="col2a-txt" value="#{r.name}" />
</a:column>
<%-- Source column --%>
<a:column id="col3" style="text-align:left">
<a:column id="col3" style="padding:2px;text-align:left">
<f:facet name="header">
<a:sortLink id="col3-sort" label="#{msg.source}" value="sourceSpaceName" styleClass="header"/>
</f:facet>
<h:outputText id="col3-txt" value="#{r.sourceSpaceName}" />
<a:actionLink id="col3-act1" value="#{r.sourceSpaceName}"
actionListener="#{BrowseBean.clickSpace}" action="browse">
<f:param name="id" value="#{r.sourceSpaceId}" />
</a:actionLink>
</a:column>
<%-- Created Date column --%>
<a:column id="col4" style="padding:2px;text-align:left">
<f:facet name="header">
<a:sortLink id="col4-sort" label="#{msg.created}" value="created" styleClass="header"/>
</f:facet>
<h:outputText id="col4-txt" value="#{r.created}">
<a:convertXMLDate type="both" pattern="#{msg.date_time_pattern}" />
</h:outputText>
</a:column>
<%-- Due date column --%>
<a:column id="col4" style="text-align:left">
<a:column id="col5" style="padding:2px;text-align:left">
<f:facet name="header">
<a:sortLink id="col4-sort" label="#{msg.due_date}" value="bpm:startDate" styleClass="header"/>
<a:sortLink id="col5-sort" label="#{msg.due_date}" value="bpm:dueDate" styleClass="header"/>
</f:facet>
<h:outputText id="col4-txt" value="#{r['bpm:dueDate']}">
<h:outputText id="col5-txt" value="#{r['bpm:dueDate']}">
<a:convertXMLDate type="both" pattern="#{msg.date_pattern}" />
</h:outputText>
</a:column>
<%-- Status column --%>
<a:column id="col5" style="text-align:left">
<a:column id="col6" style="padding:2px;text-align:left">
<f:facet name="header">
<a:sortLink id="col5-sort" label="#{msg.status}" value="bpm:status" styleClass="header"/>
<a:sortLink id="col6-sort" label="#{msg.status}" value="bpm:status" styleClass="header"/>
</f:facet>
<h:outputText id="col5-txt" value="#{r['bpm:status']}" />
<h:outputText id="col6-txt" value="#{r['bpm:status']}" />
</a:column>
<%-- Priority column --%>
<a:column id="col6" style="text-align:left">
<a:column id="col7" style="padding:2px;text-align:left">
<f:facet name="header">
<a:sortLink id="col6-sort" label="#{msg.priority}" value="bpm:priority" styleClass="header"/>
<a:sortLink id="col7-sort" label="#{msg.priority}" value="bpm:priority" styleClass="header"/>
</f:facet>
<h:outputText id="col6-txt" value="#{r['bpm:priority']}" />
<h:outputText id="col7-txt" value="#{r['bpm:priority']}" />
</a:column>
<%-- Actions column --%>
<a:column id="col7" actions="true" style="text-align:left">
<a:column id="col8" actions="true" style="padding:2px;text-align:left">
<f:facet name="header">
<h:outputText id="col7-txt" value="#{msg.actions}"/>
<h:outputText id="col8-txt" value="#{msg.actions}"/>
</f:facet>
<r:actions id="col7-actions" value="dashlet_todo_actions" context="#{r}" showLink="false"
<r:actions id="col8-actions" value="dashlet_todo_actions" context="#{r}" showLink="false"
styleClass="inlineAction" />
</a:column>

View File

@@ -31,11 +31,14 @@
<a:panel id="resources-panel" label="#{msg.resources}"
border="white" bgcolor="white" titleBorder="blue" titleBgcolor="#D3E6FE" styleClass="mainSubTitle">
<h:outputText value="#{msg.no_resources}" rendered="#{empty DialogManager.bean.resources}" />
<a:richList id="resources-list" viewMode="details" value="#{DialogManager.bean.resources}" var="r"
binding="#{DialogManager.bean.packageItemsRichList}"
styleClass="recordSet" headerStyleClass="recordSetHeader" rowStyleClass="recordSetRow"
altRowStyleClass="recordSetRowAlt" width="100%" pageSize="10"
initialSortColumn="name" initialSortDescending="true">
initialSortColumn="name" initialSortDescending="true"
rendered="#{not empty DialogManager.bean.resources}">
<%-- Name column --%>
<a:column id="col1" primary="true" width="200" style="padding:2px; text-align:left">
@@ -63,7 +66,8 @@
<f:facet name="header">
<a:sortLink id="col3-sort" label="#{msg.path}" value="path" styleClass="header"/>
</f:facet>
<r:nodePath id="col3-path" value="#{r.path}" />
<r:nodePath id="col3-path" value="#{r.path}" action="dialog:close:browse"
actionListener="#{BrowseBean.clickSpacePath}" />
</a:column>
<%-- Created Date column --%>