mirror of
https://github.com/Alfresco/alfresco-community-repo.git
synced 2025-08-07 17:49:17 +00:00
ALF-10821 : WebDAV malformed URL git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@31364 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
1169 lines
42 KiB
Java
1169 lines
42 KiB
Java
/*
|
|
* Copyright (C) 2005-2010 Alfresco Software Limited.
|
|
*
|
|
* This file is part of Alfresco
|
|
*
|
|
* Alfresco is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU Lesser General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* Alfresco is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public License
|
|
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
package org.alfresco.web.ui.common;
|
|
|
|
import java.io.IOException;
|
|
import java.text.DateFormat;
|
|
import java.text.SimpleDateFormat;
|
|
import java.util.ArrayList;
|
|
import java.util.Calendar;
|
|
import java.util.Date;
|
|
import java.util.Enumeration;
|
|
import java.util.Iterator;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
import java.util.StringTokenizer;
|
|
|
|
import javax.faces.application.FacesMessage;
|
|
import javax.faces.component.NamingContainer;
|
|
import javax.faces.component.UIComponent;
|
|
import javax.faces.component.UIForm;
|
|
import javax.faces.context.FacesContext;
|
|
import javax.faces.context.ResponseWriter;
|
|
import javax.faces.el.EvaluationException;
|
|
import javax.faces.el.MethodBinding;
|
|
import javax.faces.event.AbortProcessingException;
|
|
import javax.faces.event.ActionEvent;
|
|
|
|
import org.alfresco.error.AlfrescoRuntimeException;
|
|
import org.alfresco.filesys.repo.ContentContext;
|
|
import org.alfresco.jlan.server.config.ServerConfigurationAccessor;
|
|
import org.alfresco.jlan.server.core.SharedDevice;
|
|
import org.alfresco.jlan.server.core.SharedDeviceList;
|
|
import org.alfresco.jlan.server.filesys.DiskSharedDevice;
|
|
import org.alfresco.jlan.server.filesys.FilesystemsConfigSection;
|
|
import org.alfresco.model.ApplicationModel;
|
|
import org.alfresco.model.ContentModel;
|
|
import org.alfresco.repo.model.filefolder.FileFolderServiceImpl.InvalidTypeException;
|
|
import org.alfresco.repo.search.impl.lucene.AbstractLuceneQueryParser;
|
|
import org.alfresco.repo.security.permissions.AccessDeniedException;
|
|
import org.alfresco.repo.webdav.WebDAVHelper;
|
|
import org.alfresco.repo.webdav.WebDAVServlet;
|
|
import org.alfresco.service.ServiceRegistry;
|
|
import org.alfresco.service.cmr.dictionary.DictionaryService;
|
|
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.security.PersonService;
|
|
import org.alfresco.service.namespace.NamespaceService;
|
|
import org.alfresco.service.namespace.QName;
|
|
import org.alfresco.util.Pair;
|
|
import org.alfresco.web.app.Application;
|
|
import org.alfresco.web.app.servlet.DownloadContentServlet;
|
|
import org.alfresco.web.app.servlet.ExternalAccessServlet;
|
|
import org.alfresco.web.bean.NavigationBean;
|
|
import org.alfresco.web.bean.repository.Node;
|
|
import org.alfresco.web.bean.repository.Repository;
|
|
import org.alfresco.web.data.IDataContainer;
|
|
import org.alfresco.web.ui.common.component.UIStatusMessage;
|
|
import org.apache.commons.logging.Log;
|
|
import org.apache.commons.logging.LogFactory;
|
|
import org.apache.myfaces.shared_impl.renderkit.html.HtmlFormRendererBase;
|
|
import org.springframework.extensions.config.ConfigElement;
|
|
import org.springframework.extensions.webscripts.ui.common.StringUtils;
|
|
import org.springframework.web.jsf.FacesContextUtils;
|
|
|
|
/**
|
|
* Class containing misc helper methods used by the JSF components.
|
|
*
|
|
* @author Kevin Roast
|
|
*/
|
|
public final class Utils extends StringUtils
|
|
{
|
|
public static final String USER_AGENT_FIREFOX = "Firefox";
|
|
public static final String USER_AGENT_MSIE = "MSIE";
|
|
private static final String MSG_TIME_PATTERN = "time_pattern";
|
|
private static final String MSG_DATE_PATTERN = "date_pattern";
|
|
private static final String MSG_DATE_TIME_PATTERN = "date_time_pattern";
|
|
|
|
private static final Log logger = LogFactory.getLog(Utils.class);
|
|
|
|
/**
|
|
* Private constructor
|
|
*/
|
|
private Utils()
|
|
{
|
|
}
|
|
|
|
/**
|
|
* Helper to output an attribute to the output stream
|
|
*
|
|
* @param out ResponseWriter
|
|
* @param attr attribute value object (cannot be null)
|
|
* @param mapping mapping to output as e.g. style="..."
|
|
*
|
|
* @throws IOException
|
|
*/
|
|
public static void outputAttribute(ResponseWriter out, Object attr, String mapping)
|
|
throws IOException
|
|
{
|
|
if (attr != null)
|
|
{
|
|
out.write(' ');
|
|
out.write(mapping);
|
|
out.write("=\"");
|
|
out.write(attr.toString());
|
|
out.write('"');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get the hidden field name for any action component.
|
|
*
|
|
* All components that wish to simply encode a form value with their client ID can reuse the same
|
|
* hidden field within the parent form. NOTE: components which use this method must only encode
|
|
* their client ID as the value and nothing else!
|
|
*
|
|
* Build a shared field name from the parent form name and the string "act".
|
|
*
|
|
* @return hidden field name shared by all action components within the Form.
|
|
*/
|
|
public static String getActionHiddenFieldName(FacesContext context, UIComponent component)
|
|
{
|
|
return Utils.getParentForm(context, component).getClientId(context) + NamingContainer.SEPARATOR_CHAR + "act";
|
|
}
|
|
|
|
/**
|
|
* Helper to recursively render a component and it's child components
|
|
*
|
|
* @param context FacesContext
|
|
* @param component UIComponent
|
|
*
|
|
* @throws IOException
|
|
*/
|
|
public static void encodeRecursive(FacesContext context, UIComponent component)
|
|
throws IOException
|
|
{
|
|
if (component.isRendered() == true)
|
|
{
|
|
component.encodeBegin(context);
|
|
|
|
// follow the spec for components that render their children
|
|
if (component.getRendersChildren() == true)
|
|
{
|
|
component.encodeChildren(context);
|
|
}
|
|
else
|
|
{
|
|
if (component.getChildCount() != 0)
|
|
{
|
|
for (Iterator i=component.getChildren().iterator(); i.hasNext(); /**/)
|
|
{
|
|
encodeRecursive(context, (UIComponent)i.next());
|
|
}
|
|
}
|
|
}
|
|
|
|
component.encodeEnd(context);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Generate the JavaScript to submit set the specified hidden Form field to the
|
|
* supplied value and submit the parent Form.
|
|
*
|
|
* NOTE: the supplied hidden field name is added to the Form Renderer map for output.
|
|
*
|
|
* @param context FacesContext
|
|
* @param component UIComponent to generate JavaScript for
|
|
* @param fieldId Hidden field id to set value for
|
|
* @param fieldValue Hidden field value to set hidden field too on submit
|
|
*
|
|
* @return JavaScript event code
|
|
*/
|
|
public static String generateFormSubmit(FacesContext context, UIComponent component, String fieldId, String fieldValue)
|
|
{
|
|
return generateFormSubmit(context, component, fieldId, fieldValue, false, null);
|
|
}
|
|
|
|
/**
|
|
* Generate the JavaScript to submit set the specified hidden Form field to the
|
|
* supplied value and submit the parent Form.
|
|
*
|
|
* NOTE: the supplied hidden field name is added to the Form Renderer map for output.
|
|
*
|
|
* @param context FacesContext
|
|
* @param component UIComponent to generate JavaScript for
|
|
* @param fieldId Hidden field id to set value for
|
|
* @param fieldValue Hidden field value to set hidden field too on submit
|
|
* @param params Optional map of param name/values to output
|
|
*
|
|
* @return JavaScript event code
|
|
*/
|
|
public static String generateFormSubmit(FacesContext context, UIComponent component, String fieldId,
|
|
String fieldValue, Map<String, String> params)
|
|
{
|
|
return generateFormSubmit(context, component, fieldId, fieldValue, false, params);
|
|
}
|
|
|
|
/**
|
|
* Generate the JavaScript to submit set the specified hidden Form field to the
|
|
* supplied value and submit the parent Form.
|
|
*
|
|
* NOTE: the supplied hidden field name is added to the Form Renderer map for output.
|
|
*
|
|
* @param context FacesContext
|
|
* @param component UIComponent to generate JavaScript for
|
|
* @param fieldId Hidden field id to set value for
|
|
* @param fieldValue Hidden field value to set hidden field too on submit
|
|
* @param valueIsParam Determines whether the fieldValue parameter should be treated
|
|
* as a parameter in the generated JavaScript, false will treat
|
|
* the value i.e. surround it with single quotes
|
|
* @param params Optional map of param name/values to output
|
|
*
|
|
* @return JavaScript event code
|
|
*/
|
|
public static String generateFormSubmit(FacesContext context, UIComponent component, String fieldId,
|
|
String fieldValue, boolean valueIsParam, Map<String, String> params)
|
|
{
|
|
UIForm form = Utils.getParentForm(context, component);
|
|
if (form == null)
|
|
{
|
|
throw new IllegalStateException("Must nest components inside UIForm to generate form submit!");
|
|
}
|
|
|
|
String formClientId = form.getClientId(context);
|
|
|
|
StringBuilder buf = new StringBuilder(200);
|
|
buf.append("document.forms['");
|
|
buf.append(formClientId);
|
|
buf.append("']['");
|
|
buf.append(fieldId);
|
|
buf.append("'].value=");
|
|
if (valueIsParam == false)
|
|
{
|
|
buf.append("'");
|
|
}
|
|
buf.append(Utils.encode(fieldValue));
|
|
if (valueIsParam == false)
|
|
{
|
|
buf.append("'");
|
|
}
|
|
buf.append(";");
|
|
|
|
if (params != null)
|
|
{
|
|
for (String name : params.keySet())
|
|
{
|
|
buf.append("document.forms['");
|
|
buf.append(formClientId);
|
|
buf.append("']['");
|
|
buf.append(name);
|
|
buf.append("'].value='");
|
|
String val = params.get(name);
|
|
if (val != null)
|
|
{
|
|
val = Utils.encode(val);
|
|
}
|
|
val = replace(val, "\\", "\\\\"); // encode escape character
|
|
val = replace(val, "'", "\\'"); // encode single quote as we wrap string with that
|
|
buf.append(val);
|
|
buf.append("';");
|
|
|
|
// weak, but this seems to be the way Sun RI do it...
|
|
//FormRenderer.addNeededHiddenField(context, name);
|
|
HtmlFormRendererBase.addHiddenCommandParameter(context, form, name);
|
|
}
|
|
}
|
|
|
|
buf.append("document.forms['");
|
|
buf.append(formClientId);
|
|
buf.append("'].submit();");
|
|
|
|
if (valueIsParam == false)
|
|
{
|
|
buf.append("return false;");
|
|
}
|
|
|
|
// weak, but this seems to be the way Sun RI do it...
|
|
//FormRenderer.addNeededHiddenField(context, fieldId);
|
|
HtmlFormRendererBase.addHiddenCommandParameter(context, form, fieldId);
|
|
|
|
return buf.toString();
|
|
}
|
|
|
|
/**
|
|
* Generate the JavaScript to submit the parent Form.
|
|
*
|
|
* @param context FacesContext
|
|
* @param component UIComponent to generate JavaScript for
|
|
*
|
|
* @return JavaScript event code
|
|
*/
|
|
public static String generateFormSubmit(FacesContext context, UIComponent component)
|
|
{
|
|
UIForm form = Utils.getParentForm(context, component);
|
|
if (form == null)
|
|
{
|
|
throw new IllegalStateException("Must nest components inside UIForm to generate form submit!");
|
|
}
|
|
|
|
String formClientId = form.getClientId(context);
|
|
|
|
StringBuilder buf = new StringBuilder(48);
|
|
|
|
buf.append("document.forms['");
|
|
buf.append(formClientId);
|
|
buf.append("'].submit()");
|
|
|
|
buf.append(";return false;");
|
|
|
|
return buf.toString();
|
|
}
|
|
|
|
/**
|
|
* Enum representing the client URL type to generate
|
|
*/
|
|
public enum URLMode {HTTP_DOWNLOAD, HTTP_INLINE, WEBDAV, CIFS, SHOW_DETAILS, BROWSE, FTP}
|
|
|
|
/**
|
|
* Generates a URL for the given usage for the given node. If the URL cannot be generated
|
|
* then null is returned.
|
|
*
|
|
* The supported values for the usage parameter are of URLMode enum type
|
|
* @see URLMode
|
|
*
|
|
* @param context Faces context
|
|
* @param node The node to generate the URL for
|
|
* @param name Name to use for the download file part of the link if any
|
|
* @param usage What the URL is going to be used for
|
|
*
|
|
* @return The URL for the requested usage without the context path
|
|
*/
|
|
public static String generateURL(FacesContext context, Node node, String name, URLMode usage)
|
|
{
|
|
String url = null;
|
|
|
|
switch (usage)
|
|
{
|
|
case WEBDAV:
|
|
{
|
|
// calculate a WebDAV URL for the given node
|
|
FileFolderService fileFolderService = Repository.getServiceRegistry(
|
|
context).getFileFolderService();
|
|
try
|
|
{
|
|
NodeRef rootNode = WebDAVServlet.getWebdavRootNode();
|
|
if (rootNode != null)
|
|
{
|
|
// build up the webdav url
|
|
StringBuilder path = new StringBuilder("/").append(WebDAVServlet.WEBDAV_PREFIX);
|
|
|
|
if (!rootNode.equals(node.getNodeRef()))
|
|
{
|
|
|
|
List<FileInfo> paths = fileFolderService.getNamePath(rootNode, node.getNodeRef());
|
|
|
|
// build up the path skipping the first path as it is the root folder
|
|
for (int x = 0; x < paths.size(); x++)
|
|
{
|
|
path.append("/").append(WebDAVHelper.encodeURL(paths.get(x).getName(), getUserAgent(context)));
|
|
}
|
|
}
|
|
url = path.toString();
|
|
}
|
|
}
|
|
catch (AccessDeniedException e)
|
|
{
|
|
// cannot build path if user don't have access all the way up
|
|
}
|
|
catch (FileNotFoundException nodeErr)
|
|
{
|
|
// cannot build path if file no longer exists
|
|
}
|
|
catch (InvalidTypeException e)
|
|
{
|
|
// primary path does not translate to a file/folder path.
|
|
}
|
|
break;
|
|
}
|
|
|
|
case CIFS:
|
|
{
|
|
// calculate a CIFS path for the given node
|
|
|
|
// get hold of the node service, cifsServer and navigation bean
|
|
ServiceRegistry serviceRegistry = Repository.getServiceRegistry(context);
|
|
NodeService nodeService = serviceRegistry.getNodeService();
|
|
FileFolderService fileFolderService = serviceRegistry.getFileFolderService();
|
|
NavigationBean navBean = (NavigationBean)context.getExternalContext().
|
|
getSessionMap().get(NavigationBean.BEAN_NAME);
|
|
ServerConfigurationAccessor serverConfiguration = (ServerConfigurationAccessor)FacesContextUtils.getRequiredWebApplicationContext(
|
|
context).getBean("fileServerConfiguration");
|
|
|
|
if (nodeService != null && fileFolderService != null && navBean != null && serverConfiguration != null)
|
|
{
|
|
// Resolve CIFS network folder location for this node
|
|
FilesystemsConfigSection filesysConfig = (FilesystemsConfigSection)serverConfiguration.getConfigSection(FilesystemsConfigSection.SectionName);
|
|
DiskSharedDevice diskShare = null;
|
|
|
|
SharedDeviceList shares = filesysConfig.getShares();
|
|
Enumeration<SharedDevice> shareEnum = shares.enumerateShares();
|
|
|
|
while (shareEnum.hasMoreElements() && diskShare == null)
|
|
{
|
|
SharedDevice curShare = shareEnum.nextElement();
|
|
if (curShare.getContext() instanceof ContentContext)
|
|
{
|
|
// ALF-6863: Check if the node has a path beneath contentContext.getRootNode()
|
|
ContentContext contentContext = (ContentContext) curShare.getContext();
|
|
NodeRef rootNode = contentContext.getRootNode();
|
|
if (node.getNodeRef().equals(rootNode))
|
|
{
|
|
diskShare = (DiskSharedDevice) curShare;
|
|
break;
|
|
}
|
|
try
|
|
{
|
|
fileFolderService.getNamePath(rootNode, node.getNodeRef());
|
|
if (logger.isDebugEnabled())
|
|
{
|
|
logger.debug(" Node " + node.getName() + " HAS been found on " + contentContext.getDeviceName());
|
|
}
|
|
diskShare = (DiskSharedDevice) curShare;
|
|
break;
|
|
}
|
|
catch (FileNotFoundException ex)
|
|
{
|
|
if (logger.isDebugEnabled())
|
|
{
|
|
logger.debug(" Node " + node.getName() + " HAS NOT been found on " + contentContext.getDeviceName());
|
|
}
|
|
// There is no such node on this SharedDevice, continue
|
|
continue;
|
|
}
|
|
catch (InvalidTypeException e)
|
|
{
|
|
if (logger.isDebugEnabled())
|
|
{
|
|
logger.debug(" Node " + node.getName() + " HAS NOT been found on " + contentContext.getDeviceName());
|
|
}
|
|
// primary path does not translate to a file/folder path.
|
|
}
|
|
}
|
|
}
|
|
|
|
if (diskShare != null)
|
|
{
|
|
ContentContext contentCtx = (ContentContext)diskShare.getContext();
|
|
NodeRef rootNode = contentCtx.getRootNode();
|
|
try
|
|
{
|
|
url = Repository.getNamePathEx(context, node.getNodePath(), rootNode, "\\",
|
|
"file:///" + navBean.getCIFSServerPath(diskShare));
|
|
}
|
|
catch (AccessDeniedException e)
|
|
{
|
|
// cannot build path if user don't have access all the way up
|
|
}
|
|
catch (InvalidNodeRefException nodeErr)
|
|
{
|
|
// cannot build path if node no longer exists
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
case HTTP_DOWNLOAD:
|
|
{
|
|
url = DownloadContentServlet.generateDownloadURL(node.getNodeRef(), name);
|
|
break;
|
|
}
|
|
|
|
case HTTP_INLINE:
|
|
{
|
|
url = DownloadContentServlet.generateBrowserURL(node.getNodeRef(), name);
|
|
break;
|
|
}
|
|
|
|
case SHOW_DETAILS:
|
|
{
|
|
DictionaryService dd = Repository.getServiceRegistry(context).getDictionaryService();
|
|
|
|
// default to showing details of content
|
|
String outcome = ExternalAccessServlet.OUTCOME_DOCDETAILS;
|
|
|
|
// if the node is a type of folder then make the outcome to show space details
|
|
if ((dd.isSubClass(node.getType(), ContentModel.TYPE_FOLDER)) ||
|
|
(dd.isSubClass(node.getType(), ApplicationModel.TYPE_FOLDERLINK)))
|
|
{
|
|
outcome = ExternalAccessServlet.OUTCOME_SPACEDETAILS;
|
|
}
|
|
|
|
// build the url
|
|
url = ExternalAccessServlet.generateExternalURL(outcome,
|
|
Repository.getStoreRef().getProtocol() + "/" +
|
|
Repository.getStoreRef().getIdentifier() + "/" + node.getId());
|
|
break;
|
|
}
|
|
|
|
case BROWSE:
|
|
{
|
|
url = ExternalAccessServlet.generateExternalURL(ExternalAccessServlet.OUTCOME_BROWSE,
|
|
Repository.getStoreRef().getProtocol() + "/" +
|
|
Repository.getStoreRef().getIdentifier() + "/" + node.getId());
|
|
}
|
|
|
|
case FTP:
|
|
{
|
|
// not implemented yet!
|
|
break;
|
|
}
|
|
}
|
|
|
|
return url;
|
|
}
|
|
|
|
/**
|
|
* Generates a URL for the given usage for the given node.
|
|
*
|
|
* The supported values for the usage parameter are of URLMode enum type
|
|
* @see URLMode
|
|
*
|
|
* @param context Faces context
|
|
* @param node The node to generate the URL for
|
|
* @param usage What the URL is going to be used for
|
|
*
|
|
* @return The URL for the requested usage without the context path
|
|
*/
|
|
public static String generateURL(FacesContext context, Node node, URLMode usage)
|
|
{
|
|
return generateURL(context, node, node.getName(), usage);
|
|
}
|
|
|
|
/**
|
|
* Build a context path safe image tag for the supplied image path.
|
|
* Image path should be supplied with a leading slash '/'.
|
|
*
|
|
* @param context FacesContext
|
|
* @param image The local image path from the web folder with leading slash '/'
|
|
* @param width Width in pixels
|
|
* @param height Height in pixels
|
|
* @param alt Optional alt/title text
|
|
* @param onclick JavaScript onclick event handler code
|
|
*
|
|
* @return Populated <code>img</code> tag
|
|
*/
|
|
public static String buildImageTag(FacesContext context, String image, int width, int height,
|
|
String alt, String onclick)
|
|
{
|
|
return buildImageTag(context, image, width, height, alt, onclick, null);
|
|
}
|
|
|
|
/**
|
|
* Build a context path safe image tag for the supplied image path.
|
|
* Image path should be supplied with a leading slash '/'.
|
|
*
|
|
* @param context FacesContext
|
|
* @param image The local image path from the web folder with leading slash '/'
|
|
* @param width Width in pixels
|
|
* @param height Height in pixels
|
|
* @param alt Optional alt/title text
|
|
* @param onclick JavaScript onclick event handler code
|
|
* @param verticalAlign Optional HTML alignment value
|
|
*
|
|
* @return Populated <code>img</code> tag
|
|
*/
|
|
public static String buildImageTag(FacesContext context, String image, int width, int height,
|
|
String alt, String onclick, String verticalAlign)
|
|
{
|
|
return buildImageTag(context, image, width, height, alt, onclick, verticalAlign, null);
|
|
}
|
|
|
|
/**
|
|
* Build a context path safe image tag for the supplied image path.
|
|
* Image path should be supplied with a leading slash '/'.
|
|
*
|
|
* @param context FacesContext
|
|
* @param image The local image path from the web folder with leading slash '/'
|
|
* @param width Width in pixels
|
|
* @param height Height in pixels
|
|
* @param alt Optional alt/title text
|
|
* @param onclick JavaScript onclick event handler code
|
|
* @param verticalAlign Optional HTML alignment value
|
|
* @param style Optional inline CSS styling
|
|
*
|
|
* @return Populated <code>img</code> tag
|
|
*/
|
|
public static String buildImageTag(FacesContext context, String image, int width, int height,
|
|
String alt, String onclick, String verticalAlign, String style)
|
|
{
|
|
StringBuilder buf = new StringBuilder(200);
|
|
|
|
style = style != null ? "border-width:0px; " + style : "border-width:0px;";
|
|
buf.append("<img src='")
|
|
.append(context.getExternalContext().getRequestContextPath())
|
|
.append(image)
|
|
.append("' width='")
|
|
.append(width)
|
|
.append("' height='")
|
|
.append(height)
|
|
.append("'");
|
|
|
|
if (alt != null)
|
|
{
|
|
alt = Utils.encode(alt);
|
|
buf.append(" alt=\"")
|
|
.append(alt)
|
|
.append("\" title=\"")
|
|
.append(alt)
|
|
.append("\"");
|
|
}
|
|
else
|
|
{
|
|
buf.append(" alt=''");
|
|
}
|
|
|
|
if (verticalAlign != null)
|
|
{
|
|
StringBuilder styleBuf = new StringBuilder(40);
|
|
styleBuf.append(style).append("vertical-align:").append(verticalAlign).append(";");
|
|
style = styleBuf.toString();
|
|
}
|
|
|
|
if (onclick != null)
|
|
{
|
|
buf.append(" onclick=\"").append(onclick).append('"');
|
|
StringBuilder styleBuf = new StringBuilder(style.length() + 16);
|
|
styleBuf.append(style).append("cursor:pointer;");
|
|
style = styleBuf.toString();
|
|
}
|
|
buf.append(" style='").append(style).append("'/>");
|
|
|
|
return buf.toString();
|
|
}
|
|
|
|
/**
|
|
* Build a context path safe image tag for the supplied image path.
|
|
* Image path should be supplied with a leading slash '/'.
|
|
*
|
|
* @param context FacesContext
|
|
* @param image The local image path from the web folder with leading slash '/'
|
|
* @param width Width in pixels
|
|
* @param height Height in pixels
|
|
* @param alt Optional alt/title text
|
|
*
|
|
* @return Populated <code>img</code> tag
|
|
*/
|
|
public static String buildImageTag(FacesContext context, String image, int width, int height, String alt)
|
|
{
|
|
return buildImageTag(context, image, width, height, alt, null);
|
|
}
|
|
|
|
/**
|
|
* Build a context path safe image tag for the supplied image path.
|
|
* Image path should be supplied with a leading slash '/'.
|
|
*
|
|
* @param context FacesContext
|
|
* @param image The local image path from the web folder with leading slash '/'
|
|
* @param alt Optional alt/title text
|
|
*
|
|
* @return Populated <code>img</code> tag
|
|
*/
|
|
public static String buildImageTag(FacesContext context, String image, String alt)
|
|
{
|
|
return buildImageTag(context, image, alt, null);
|
|
}
|
|
|
|
/**
|
|
* Build a context path safe image tag for the supplied image path.
|
|
* Image path should be supplied with a leading slash '/'.
|
|
*
|
|
* @param context FacesContext
|
|
* @param image The local image path from the web folder with leading slash '/'
|
|
* @param alt Optional alt/title text
|
|
* @param verticalAlign Optional HTML alignment value
|
|
*
|
|
* @return Populated <code>img</code> tag
|
|
*/
|
|
public static String buildImageTag(FacesContext context, String image, String alt, String verticalAlign)
|
|
{
|
|
StringBuilder buf = new StringBuilder(128);
|
|
buf.append("<img src='")
|
|
.append(context.getExternalContext().getRequestContextPath())
|
|
.append(image)
|
|
.append("' ");
|
|
|
|
String style = "border-width:0px;";
|
|
if (alt != null)
|
|
{
|
|
alt = Utils.encode(alt);
|
|
buf.append(" alt=\"")
|
|
.append(alt)
|
|
.append("\" title=\"")
|
|
.append(alt)
|
|
.append('"');
|
|
}
|
|
else
|
|
{
|
|
buf.append(" alt=''");
|
|
}
|
|
|
|
if (verticalAlign != null)
|
|
{
|
|
StringBuilder styleBuf = new StringBuilder(40);
|
|
styleBuf.append(style).append("vertical-align:").append(verticalAlign).append(";");
|
|
style = styleBuf.toString();
|
|
}
|
|
|
|
buf.append(" style='").append(style).append("'/>");
|
|
|
|
return buf.toString();
|
|
}
|
|
|
|
/**
|
|
* Return the parent UIForm component for the specified UIComponent
|
|
*
|
|
* @param context FaceContext
|
|
* @param component The UIComponent to find parent Form for
|
|
*
|
|
* @return UIForm parent or null if none found in hiearachy
|
|
*/
|
|
public static UIForm getParentForm(FacesContext context, UIComponent component)
|
|
{
|
|
UIComponent parent = component.getParent();
|
|
while (parent != null)
|
|
{
|
|
if (parent instanceof UIForm)
|
|
{
|
|
break;
|
|
}
|
|
parent = parent.getParent();
|
|
}
|
|
return (UIForm)parent;
|
|
}
|
|
|
|
/**
|
|
* Return the parent UIComponent implementing the NamingContainer interface for
|
|
* the specified UIComponent.
|
|
*
|
|
* @param context FaceContext
|
|
* @param component The UIComponent to find parent Form for
|
|
*
|
|
* @return NamingContainer parent or null if none found in hiearachy
|
|
*/
|
|
public static UIComponent getParentNamingContainer(FacesContext context, UIComponent component)
|
|
{
|
|
UIComponent parent = component.getParent();
|
|
while (parent != null)
|
|
{
|
|
if (parent instanceof NamingContainer)
|
|
{
|
|
break;
|
|
}
|
|
parent = parent.getParent();
|
|
}
|
|
return (UIComponent)parent;
|
|
}
|
|
|
|
/**
|
|
* Return the parent UIComponent implementing the IDataContainer interface for
|
|
* the specified UIComponent.
|
|
*
|
|
* @param context FaceContext
|
|
* @param component The UIComponent to find parent IDataContainer for
|
|
*
|
|
* @return IDataContainer parent or null if none found in hiearachy
|
|
*/
|
|
public static IDataContainer getParentDataContainer(FacesContext context, UIComponent component)
|
|
{
|
|
UIComponent parent = component.getParent();
|
|
while (parent != null)
|
|
{
|
|
if (parent instanceof IDataContainer)
|
|
{
|
|
break;
|
|
}
|
|
parent = parent.getParent();
|
|
}
|
|
return (IDataContainer)parent;
|
|
}
|
|
|
|
/**
|
|
* Determines whether the given component is disabled or readonly
|
|
*
|
|
* @param component The component to test
|
|
* @return true if the component is either disabled or set to readonly
|
|
*/
|
|
public static boolean isComponentDisabledOrReadOnly(UIComponent component)
|
|
{
|
|
boolean disabled = false;
|
|
boolean readOnly = false;
|
|
|
|
Object disabledAttr = component.getAttributes().get("disabled");
|
|
if (disabledAttr != null)
|
|
{
|
|
disabled = disabledAttr.equals(Boolean.TRUE);
|
|
}
|
|
|
|
if (disabled == false)
|
|
{
|
|
Object readOnlyAttr = component.getAttributes().get("readonly");
|
|
if (readOnlyAttr != null)
|
|
{
|
|
readOnly = readOnlyAttr.equals(Boolean.TRUE);
|
|
}
|
|
}
|
|
|
|
return disabled || readOnly;
|
|
}
|
|
|
|
/**
|
|
* Invoke the method encapsulated by the supplied MethodBinding
|
|
*
|
|
* @param context FacesContext
|
|
* @param method MethodBinding to invoke
|
|
* @param event ActionEvent to pass to the method of signature:
|
|
* public void myMethodName(ActionEvent event)
|
|
*/
|
|
public static void processActionMethod(FacesContext context, MethodBinding method, ActionEvent event)
|
|
{
|
|
try
|
|
{
|
|
method.invoke(context, new Object[] {event});
|
|
}
|
|
catch (EvaluationException e)
|
|
{
|
|
Throwable cause = e.getCause();
|
|
if (cause instanceof AbortProcessingException)
|
|
{
|
|
throw (AbortProcessingException)cause;
|
|
}
|
|
else
|
|
{
|
|
throw e;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Adds a global error message
|
|
*
|
|
* @param msg The error message
|
|
*/
|
|
public static void addErrorMessage(String msg)
|
|
{
|
|
addErrorMessage(msg, null);
|
|
}
|
|
|
|
/**
|
|
* Adds a global error message and logs exception details
|
|
*
|
|
* @param msg The error message
|
|
* @param err The exception to log
|
|
*/
|
|
public static void addErrorMessage(String msg, Throwable err)
|
|
{
|
|
FacesContext context = FacesContext.getCurrentInstance( );
|
|
FacesMessage facesMsg = new FacesMessage(FacesMessage.SEVERITY_ERROR, msg, msg);
|
|
context.addMessage(null, facesMsg);
|
|
if (err != null)
|
|
{
|
|
if ((err instanceof InvalidNodeRefException == false &&
|
|
err instanceof AccessDeniedException == false &&
|
|
err instanceof NoTransformerException == false) || logger.isDebugEnabled())
|
|
{
|
|
logger.error(msg, err);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Adds a global status message that will be displayed by a Status Message UI component
|
|
*
|
|
* @param severity Severity of the message
|
|
* @param msg Text of the message
|
|
*/
|
|
public static void addStatusMessage(FacesMessage.Severity severity, String msg)
|
|
{
|
|
FacesContext fc = FacesContext.getCurrentInstance();
|
|
String time = getTimeFormat(fc).format(new Date(System.currentTimeMillis()));
|
|
FacesMessage fm = new FacesMessage(severity, time, msg);
|
|
fc.addMessage(UIStatusMessage.STATUS_MESSAGE, fm);
|
|
}
|
|
|
|
/**
|
|
* @return the formatter for locale sensitive Time formatting
|
|
*/
|
|
public static DateFormat getTimeFormat(FacesContext fc)
|
|
{
|
|
return getDateFormatFromPattern(fc, Application.getMessage(fc, MSG_TIME_PATTERN));
|
|
}
|
|
|
|
/**
|
|
* @return the formatter for locale sensitive Date formatting
|
|
*/
|
|
public static DateFormat getDateFormat(FacesContext fc)
|
|
{
|
|
return getDateFormatFromPattern(fc, Application.getMessage(fc, MSG_DATE_PATTERN));
|
|
}
|
|
|
|
/**
|
|
* @return the formatter for locale sensitive Date & Time formatting
|
|
*/
|
|
public static DateFormat getDateTimeFormat(FacesContext fc)
|
|
{
|
|
return getDateFormatFromPattern(fc, Application.getMessage(fc, MSG_DATE_TIME_PATTERN));
|
|
}
|
|
|
|
/**
|
|
* @return DataFormat object for the specified pattern
|
|
*/
|
|
private static DateFormat getDateFormatFromPattern(FacesContext fc, String pattern)
|
|
{
|
|
if (pattern == null)
|
|
{
|
|
throw new IllegalArgumentException("DateTime pattern is mandatory.");
|
|
}
|
|
try
|
|
{
|
|
return new SimpleDateFormat(pattern, Application.getLanguage(fc));
|
|
}
|
|
catch (IllegalArgumentException err)
|
|
{
|
|
throw new AlfrescoRuntimeException("Invalid DateTime pattern", err);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Parse XML format date YYYY-MM-DDTHH:MM:SS
|
|
* @param isoDate
|
|
* @return Date or null if failed to parse
|
|
*/
|
|
public static Date parseXMLDateFormat(String isoDate)
|
|
{
|
|
Date parsed = null;
|
|
|
|
try
|
|
{
|
|
int offset = 0;
|
|
|
|
// extract year
|
|
int year = Integer.parseInt(isoDate.substring(offset, offset += 4));
|
|
if (isoDate.charAt(offset) != '-')
|
|
{
|
|
throw new IndexOutOfBoundsException("Expected - character but found " + isoDate.charAt(offset));
|
|
}
|
|
|
|
// extract month
|
|
int month = Integer.parseInt(isoDate.substring(offset += 1, offset += 2));
|
|
if (isoDate.charAt(offset) != '-')
|
|
{
|
|
throw new IndexOutOfBoundsException("Expected - character but found " + isoDate.charAt(offset));
|
|
}
|
|
|
|
// extract day
|
|
int day = Integer.parseInt(isoDate.substring(offset += 1, offset += 2));
|
|
if (isoDate.charAt(offset) != 'T')
|
|
{
|
|
throw new IndexOutOfBoundsException("Expected T character but found " + isoDate.charAt(offset));
|
|
}
|
|
|
|
// extract hours, minutes, seconds and milliseconds
|
|
int hour = Integer.parseInt(isoDate.substring(offset += 1, offset += 2));
|
|
if (isoDate.charAt(offset) != ':')
|
|
{
|
|
throw new IndexOutOfBoundsException("Expected : character but found " + isoDate.charAt(offset));
|
|
}
|
|
int minutes = Integer.parseInt(isoDate.substring(offset += 1, offset += 2));
|
|
if (isoDate.charAt(offset) != ':')
|
|
{
|
|
throw new IndexOutOfBoundsException("Expected : character but found " + isoDate.charAt(offset));
|
|
}
|
|
int seconds = Integer.parseInt(isoDate.substring(offset += 1 , offset += 2));
|
|
|
|
// initialize Calendar object
|
|
Calendar calendar = Calendar.getInstance();
|
|
calendar.setLenient(false);
|
|
calendar.set(Calendar.YEAR, year);
|
|
calendar.set(Calendar.MONTH, month - 1);
|
|
calendar.set(Calendar.DAY_OF_MONTH, day);
|
|
calendar.set(Calendar.HOUR_OF_DAY, hour);
|
|
calendar.set(Calendar.MINUTE, minutes);
|
|
calendar.set(Calendar.SECOND, seconds);
|
|
|
|
// extract the date
|
|
parsed = calendar.getTime();
|
|
}
|
|
catch(IndexOutOfBoundsException e)
|
|
{
|
|
}
|
|
catch(NumberFormatException e)
|
|
{
|
|
}
|
|
catch(IllegalArgumentException e)
|
|
{
|
|
}
|
|
|
|
return parsed;
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* Given a ConfigElement instance retrieve the display label, this could be
|
|
* dervied from a message bundle key or a literal string
|
|
*
|
|
* @param context FacesContext
|
|
* @param configElement The ConfigElement to test
|
|
* @return The resolved display label
|
|
*/
|
|
public static String getDisplayLabel(FacesContext context, ConfigElement configElement)
|
|
{
|
|
String label = null;
|
|
|
|
// look for a localized string
|
|
String msgId = configElement.getAttribute("display-label-id");
|
|
if (msgId != null)
|
|
{
|
|
label = Application.getMessage(context, msgId);
|
|
}
|
|
|
|
// if there wasn't an externalized string look for a literal string
|
|
if (label == null)
|
|
{
|
|
label = configElement.getAttribute("display-label");
|
|
}
|
|
|
|
return label;
|
|
}
|
|
|
|
/**
|
|
* Given a ConfigElement instance retrieve the description, this could be
|
|
* dervied from a message bundle key or a literal string
|
|
*
|
|
* @param context FacesContext
|
|
* @param configElement The ConfigElement to test
|
|
* @return The resolved description
|
|
*/
|
|
public static String getDescription(FacesContext context, ConfigElement configElement)
|
|
{
|
|
String description = null;
|
|
|
|
// look for a localized string
|
|
String msgId = configElement.getAttribute("description-id");
|
|
if (msgId != null)
|
|
{
|
|
description = Application.getMessage(context, msgId);
|
|
}
|
|
|
|
// if there wasn't an externalized string look for a literal string
|
|
if (description == null)
|
|
{
|
|
description = configElement.getAttribute("description");
|
|
}
|
|
|
|
return description;
|
|
}
|
|
|
|
/**
|
|
* @return the browser User-Agent header value trimmed to either "Firefox" or "MSIE" as appropriate.
|
|
*/
|
|
public static String getUserAgent(FacesContext context)
|
|
{
|
|
Object userAgent = context.getExternalContext().getRequestHeaderMap().get("User-Agent");
|
|
if (userAgent != null)
|
|
{
|
|
if (userAgent.toString().indexOf("Firefox/") != -1)
|
|
{
|
|
return USER_AGENT_FIREFOX;
|
|
}
|
|
else if (userAgent.toString().indexOf("MSIE") != -1)
|
|
{
|
|
return USER_AGENT_MSIE;
|
|
}
|
|
else
|
|
{
|
|
return userAgent.toString();
|
|
}
|
|
}
|
|
return "";
|
|
}
|
|
|
|
/**
|
|
* Generate the QName sort for a standard Person lookup. The filter is
|
|
* standardised across multiple JSF components and beans, and used with
|
|
* {@link PersonService#getPeople(List, boolean, List, org.alfresco.query.PagingRequest)}
|
|
*/
|
|
public static List<Pair<QName,Boolean>> generatePersonSort()
|
|
{
|
|
List<Pair<QName,Boolean>> sort = new ArrayList<Pair<QName,Boolean>>();
|
|
sort.add(new Pair<QName, Boolean>(ContentModel.PROP_FIRSTNAME, true));
|
|
sort.add(new Pair<QName, Boolean>(ContentModel.PROP_LASTNAME, true));
|
|
sort.add(new Pair<QName, Boolean>(ContentModel.PROP_USERNAME, true));
|
|
return sort;
|
|
}
|
|
|
|
/**
|
|
* Generate the QName filter for a standard Person lookup. The filter is
|
|
* standardised across multiple JSF components and beans, and used with
|
|
* {@link PersonService#getPeople(List, boolean, List, org.alfresco.query.PagingRequest)}
|
|
*
|
|
* @param term Search term
|
|
*/
|
|
public static List<Pair<QName,String>> generatePersonFilter(String term)
|
|
{
|
|
List<Pair<QName,String>> filter = new ArrayList<Pair<QName,String>>();
|
|
filter.add(new Pair<QName, String>(ContentModel.PROP_FIRSTNAME, term));
|
|
filter.add(new Pair<QName, String>(ContentModel.PROP_LASTNAME, term));
|
|
filter.add(new Pair<QName, String>(ContentModel.PROP_USERNAME, term));
|
|
return filter;
|
|
}
|
|
|
|
/**
|
|
* How many results should a person search return up to? This is needed
|
|
* because the JSF components do paging differently.
|
|
* For now, hard coded at 1000, may be configurable later
|
|
*/
|
|
public static int getPersonMaxResults()
|
|
{
|
|
return 1000;
|
|
}
|
|
|
|
/**
|
|
* Generate the Lucene query for a standard Person search. The query used is standardised
|
|
* across multiple JSF components and beans.
|
|
*
|
|
* @param query Buffer for the query
|
|
* @param term Search term
|
|
* @deprecated Use {@link #generatePersonFilter(String)} and {@link PersonService#getPeople(List, boolean, List, org.alfresco.query.PagingRequest)} instead
|
|
*/
|
|
public static void generatePersonSearch(StringBuilder query, String term)
|
|
{
|
|
// define the query to find people by their first or last name
|
|
for (StringTokenizer t = new StringTokenizer(term.trim(), " "); t.hasMoreTokens(); /**/)
|
|
{
|
|
String token = AbstractLuceneQueryParser.escape(t.nextToken());
|
|
query.append("+TYPE:\"").append(ContentModel.TYPE_PERSON).append("\" ");
|
|
query.append("+(@").append(NamespaceService.CONTENT_MODEL_PREFIX).append("\\:firstName:\"*");
|
|
query.append(token);
|
|
query.append("*\" @").append(NamespaceService.CONTENT_MODEL_PREFIX).append("\\:lastName:\"*");
|
|
query.append(token);
|
|
query.append("*\" @").append(NamespaceService.CONTENT_MODEL_PREFIX).append("\\:userName:");
|
|
query.append(token);
|
|
query.append("*) ");
|
|
}
|
|
}
|
|
}
|