From 51d46140afa36fcd9e5bacc9d92446d7df1c59da Mon Sep 17 00:00:00 2001 From: Gavin Cornwell Date: Fri, 7 Mar 2008 12:28:36 +0000 Subject: [PATCH] Merged V2.2 to HEAD 8019: Merged V2.1 to V2.2 7715: Fix for AWC-1753 7725: Additional files for AWC-1753 - also fixes unreported issue of OK button not being highlighted when editing post until you press a key 7726: Additional files for AWC-1753 - also fixes unreported issue of OK button not being highlighted when editing post until you press a key 7731: Added support for Range and Content-Range headers to support Download managers and Resume features for HTTP downloads 7967: Workaround for ACT-771: Missing AVM Store system descriptor properties prevents system startup 7980: AWC-1662: Fixed NPE when searching for users 7981: Fix for AWC-1661: Can't edit details of user who has had their home space removed 7988: NodeRef + child path relative URLs support for DownloadContentServlet 8003: Fix for AWC-1795 8004: Fix http://issues.alfresco.com/browse/AR-1807 git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@8461 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261 --- .../servlet/BaseDownloadContentServlet.java | 132 ++++++++++++++++-- .../java/org/alfresco/web/bean/ErrorBean.java | 4 +- .../web/bean/forums/EditPostDialog.java | 2 +- .../alfresco/web/bean/forums/ForumsBean.java | 10 +- .../bean/generator/SeparatorGenerator.java | 2 +- .../bean/wizard/BaseInviteUsersWizard.java | 19 +-- .../web/bean/wizard/NewUserWizard.java | 2 +- .../org/alfresco/web/ui/common/Utils.java | 6 +- source/web/jsp/forums/create-post-dialog.jsp | 36 +++-- source/web/jsp/forums/create-reply-dialog.jsp | 37 +++-- 10 files changed, 189 insertions(+), 61 deletions(-) diff --git a/source/java/org/alfresco/web/app/servlet/BaseDownloadContentServlet.java b/source/java/org/alfresco/web/app/servlet/BaseDownloadContentServlet.java index fe18e0c11a..10c1244f99 100644 --- a/source/java/org/alfresco/web/app/servlet/BaseDownloadContentServlet.java +++ b/source/java/org/alfresco/web/app/servlet/BaseDownloadContentServlet.java @@ -25,10 +25,13 @@ package org.alfresco.web.app.servlet; import java.io.IOException; +import java.io.InputStream; import java.net.SocketException; import java.net.URLDecoder; import java.text.MessageFormat; +import java.util.ArrayList; import java.util.Date; +import java.util.List; import java.util.StringTokenizer; import javax.servlet.ServletException; @@ -39,6 +42,8 @@ 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.model.FileInfo; +import org.alfresco.service.cmr.model.FileNotFoundException; import org.alfresco.service.cmr.repository.ContentIOException; import org.alfresco.service.cmr.repository.ContentReader; import org.alfresco.service.cmr.repository.ContentService; @@ -125,6 +130,8 @@ public abstract class BaseDownloadContentServlet extends BaseServlet String attachToken = t.nextToken(); boolean attachment = URL_ATTACH.equals(attachToken) || URL_ATTACH_LONG.equals(attachToken); + ServiceRegistry serviceRegistry = getServiceRegistry(getServletContext()); + // get or calculate the noderef and filename to download as NodeRef nodeRef; String filename; @@ -153,8 +160,34 @@ public abstract class BaseDownloadContentServlet extends BaseServlet // build noderef from the appropriate URL elements nodeRef = new NodeRef(storeRef, id); - // filename is last remaining token - filename = t.nextToken(); + if (tokenCount > 6) + { + // found additional relative path elements i.e. noderefid/images/file.txt + // this allows a url to reference siblings nodes via a cm:name based relative path + // solves the issue with opening HTML content containing relative URLs in HREF or IMG tags etc. + List paths = new ArrayList(tokenCount - 5); + while (t.hasMoreTokens()) + { + paths.add(URLDecoder.decode(t.nextToken())); + } + filename = paths.get(paths.size() - 1); + + try + { + NodeRef parentRef = serviceRegistry.getNodeService().getPrimaryParent(nodeRef).getParentRef(); + FileInfo fileInfo = serviceRegistry.getFileFolderService().resolveNamePath(parentRef, paths); + nodeRef = fileInfo.getNodeRef(); + } + catch (FileNotFoundException e) + { + throw new AlfrescoRuntimeException("Unable to find node reference by relative path:" + uri); + } + } + else + { + // filename is last remaining token + filename = t.nextToken(); + } } // get qualified of the property to get content from - default to ContentModel.PROP_CONTENT @@ -174,7 +207,6 @@ public abstract class BaseDownloadContentServlet extends BaseServlet } // 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(); @@ -209,13 +241,15 @@ public abstract class BaseDownloadContentServlet extends BaseServlet 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; - } + // round the date to the ignore millisecond value which is not supplied by header + long modDate = (modified.getTime() / 1000L) * 1000L; + if (modDate <= modifiedSince) + { + if (logger.isDebugEnabled()) + logger.debug("Returning 304 Not Modified."); + res.setStatus(HttpServletResponse.SC_NOT_MODIFIED); + return; + } } res.setDateHeader("Last-Modified", modified.getTime()); @@ -258,9 +292,85 @@ public abstract class BaseDownloadContentServlet extends BaseServlet // 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. + res.setHeader("Accept-Ranges", "bytes"); try { - reader.getContent( res.getOutputStream() ); + boolean processedRange = false; + String range = req.getHeader("Content-Range"); + if (range == null) + { + range = req.getHeader("Range"); + } + if (range != null) + { + if (logger.isDebugEnabled()) + logger.debug("Found content range header: " + range); + // return the specific set of bytes as requested in the content-range header + /* Examples of byte-content-range-spec values, assuming that the entity contains total of 1234 bytes: + The first 500 bytes: + bytes 0-499/1234 + + The second 500 bytes: + bytes 500-999/1234 + + All except for the first 500 bytes: + bytes 500-1233/1234 */ + /* 'Range' header example: + bytes=10485760-20971519 */ + try + { + if (range.length() > 6) + { + StringTokenizer r = new StringTokenizer(range.substring(6), "-/"); + if (r.countTokens() >= 2) + { + long start = Long.parseLong(r.nextToken()); + long end = Long.parseLong(r.nextToken()); + + res.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT); + res.setHeader("Content-Range", range); + res.setHeader("Content-Length", Long.toString(((end-start)+1L))); + + InputStream is = null; + try + { + is = reader.getContentInputStream(); + if (start != 0) is.skip(start); + long span = (end-start)+1; + long total = 0; + int read = 0; + byte[] buf = new byte[((int)span) < 8192 ? (int)span : 8192]; + while ((read = is.read(buf)) != 0 && total < span) + { + total += (long)read; + res.getOutputStream().write(buf, 0, (int)read); + } + res.getOutputStream().close(); + processedRange = true; + } + finally + { + if (is != null) is.close(); + } + } + } + } + catch (NumberFormatException nerr) + { + // processedRange flag will stay false if this occurs + } + } + if (processedRange == false) + { + // As per the spec: + // If the server ignores a byte-range-spec because it is syntactically + // invalid, the server SHOULD treat the request as if the invalid Range + // header field did not exist. + long size = reader.getSize(); + res.setHeader("Content-Range", "bytes 0-" + Long.toString(size-1L) + "/" + Long.toString(size)); + res.setHeader("Content-Length", Long.toString(size)); + reader.getContent( res.getOutputStream() ); + } } catch (SocketException e1) { diff --git a/source/java/org/alfresco/web/bean/ErrorBean.java b/source/java/org/alfresco/web/bean/ErrorBean.java index bbd57a014b..e63e0ecf4d 100644 --- a/source/java/org/alfresco/web/bean/ErrorBean.java +++ b/source/java/org/alfresco/web/bean/ErrorBean.java @@ -121,7 +121,7 @@ public class ErrorBean implements Serializable // format the message for HTML display message = message.replaceAll("<", "<"); message = message.replaceAll(">", ">"); - message = message.replaceAll("\n", "
"); + message = message.replaceAll("\n", "
"); } return message; @@ -144,7 +144,7 @@ public class ErrorBean implements Serializable trace = stringWriter.toString(); trace = trace.replaceAll("<", "<"); trace = trace.replaceAll(">", ">"); - trace = trace.replaceAll("\n", "
"); + trace = trace.replaceAll("\n", "
"); } return trace; diff --git a/source/java/org/alfresco/web/bean/forums/EditPostDialog.java b/source/java/org/alfresco/web/bean/forums/EditPostDialog.java index ac62cba8c1..cf6126e807 100644 --- a/source/java/org/alfresco/web/bean/forums/EditPostDialog.java +++ b/source/java/org/alfresco/web/bean/forums/EditPostDialog.java @@ -65,7 +65,7 @@ public class EditPostDialog extends CreatePostDialog String htmlContent = reader.getContentString(); if (htmlContent != null) { - this.content = StringUtils.replace(htmlContent, "
", "\r\n"); + this.content = StringUtils.replace(htmlContent, "
", "\r\n"); } } } diff --git a/source/java/org/alfresco/web/bean/forums/ForumsBean.java b/source/java/org/alfresco/web/bean/forums/ForumsBean.java index ef701628bd..26da9b2e78 100644 --- a/source/java/org/alfresco/web/bean/forums/ForumsBean.java +++ b/source/java/org/alfresco/web/bean/forums/ForumsBean.java @@ -618,7 +618,7 @@ public class ForumsBean implements IContextListener StringBuilder replyPosterHTML = new StringBuilder(""); replyPosterHTML.append("
"); + replyPosterHTML.append("/images/icons/user_large.gif' />
"); replyPosterHTML.append((String)replyToNode.getProperties().get("creator")); replyPosterHTML.append(""); @@ -1230,7 +1230,7 @@ public class ForumsBean implements IContextListener out.write(""); out.write("

"); + out.write("/images/icons/user_large.gif'/>
"); out.write((String)node.getProperties().get("creator")); out.write("
"); @@ -1240,7 +1240,7 @@ public class ForumsBean implements IContextListener renderBodyContents(context, primaryColumn); renderBubbleBottom(out, contextPath, colour); - out.write("
"); + out.write("
"); } /** @@ -1260,7 +1260,7 @@ public class ForumsBean implements IContextListener String colour = "yellow"; out.write(""); - out.write("
"); + out.write("
"); renderBubbleTop(out, contextPath, colour, "#FFF5A3"); renderHeaderContents(context, out, primaryColumn, actionsColumn, columns); @@ -1270,7 +1270,7 @@ public class ForumsBean implements IContextListener out.write("
"); + out.write("/images/icons/user_large.gif'/>
"); out.write((String)node.getProperties().get("creator")); out.write("
"); } diff --git a/source/java/org/alfresco/web/bean/generator/SeparatorGenerator.java b/source/java/org/alfresco/web/bean/generator/SeparatorGenerator.java index 0ff195317a..3884faa288 100644 --- a/source/java/org/alfresco/web/bean/generator/SeparatorGenerator.java +++ b/source/java/org/alfresco/web/bean/generator/SeparatorGenerator.java @@ -35,6 +35,6 @@ public class SeparatorGenerator extends HtmlSeparatorGenerator { // For the standard separator just show a
element - this.html = "

"; + this.html = "

"; } } \ No newline at end of file diff --git a/source/java/org/alfresco/web/bean/wizard/BaseInviteUsersWizard.java b/source/java/org/alfresco/web/bean/wizard/BaseInviteUsersWizard.java index 1c6ca626f3..eb6f31878d 100644 --- a/source/java/org/alfresco/web/bean/wizard/BaseInviteUsersWizard.java +++ b/source/java/org/alfresco/web/bean/wizard/BaseInviteUsersWizard.java @@ -341,6 +341,8 @@ public abstract class BaseInviteUsersWizard extends BaseWizardBean tx = Repository.getUserTransaction(context, true); tx.begin(); + List results = new ArrayList(); + if (filterIndex == 0) { // Use lucene search to retrieve user details @@ -358,16 +360,18 @@ public abstract class BaseInviteUsersWizard extends BaseWizardBean SearchService.LANGUAGE_LUCENE, query.toString()); List nodes = resultSet.getNodeRefs(); - - items = new SelectItem[nodes.size()]; + for (int index=0; index groups = getAuthorityService().getAllAuthorities(AuthorityType.GROUP); groups.addAll(getAuthorityService().getAllAuthorities(AuthorityType.EVERYONE)); - - List results = new ArrayList(groups.size()); + String containsLower = contains.trim().toLowerCase(); int offset = PermissionService.GROUP_PREFIX.length(); for (String group : groups) @@ -386,10 +389,10 @@ public abstract class BaseInviteUsersWizard extends BaseWizardBean results.add(new SortableSelectItem(group, group.substring(offset), group)); } } - items = new SelectItem[results.size()]; - results.toArray(items); } + items = new SelectItem[results.size()]; + results.toArray(items); Arrays.sort(items); // commit the transaction diff --git a/source/java/org/alfresco/web/bean/wizard/NewUserWizard.java b/source/java/org/alfresco/web/bean/wizard/NewUserWizard.java index beb2d76ce6..d9aa08eab6 100644 --- a/source/java/org/alfresco/web/bean/wizard/NewUserWizard.java +++ b/source/java/org/alfresco/web/bean/wizard/NewUserWizard.java @@ -257,7 +257,7 @@ public class NewUserWizard extends AbstractWizardBean this.homeSpaceLocation = null; // default to Company root space this.homeSpaceName = ""; // default to none set below root NodeRef homeFolderRef = (NodeRef) props.get("homeFolder"); - if (this.getNodeService().exists(homeFolderRef) == true) + if (homeFolderRef != null && this.getNodeService().exists(homeFolderRef) == true) { ChildAssociationRef childAssocRef = this.getNodeService().getPrimaryParent(homeFolderRef); NodeRef parentRef = childAssocRef.getParentRef(); diff --git a/source/java/org/alfresco/web/ui/common/Utils.java b/source/java/org/alfresco/web/ui/common/Utils.java index 14ee9b7ba8..3121247755 100644 --- a/source/java/org/alfresco/web/ui/common/Utils.java +++ b/source/java/org/alfresco/web/ui/common/Utils.java @@ -350,13 +350,13 @@ public final class Utils if (endTagIndex != -1) { // found end of the tag to match - String tag = s.substring(i + 1, endTagIndex).toLowerCase(); + String tag = s.substring(i + 1, endTagIndex); String matchTag = tag; if (endMatchIndex != -1) { - matchTag = s.substring(i + 1, endMatchIndex).toLowerCase(); + matchTag = s.substring(i + 1, endMatchIndex); } - if (safeTags.contains(matchTag)) + if (safeTags.contains(matchTag.toLowerCase())) { // safe tag - append to buffer buf.append('<').append(tag).append('>'); diff --git a/source/web/jsp/forums/create-post-dialog.jsp b/source/web/jsp/forums/create-post-dialog.jsp index e9b666db1f..d921a0fca3 100644 --- a/source/web/jsp/forums/create-post-dialog.jsp +++ b/source/web/jsp/forums/create-post-dialog.jsp @@ -27,20 +27,15 @@ <%@ taglib uri="/WEB-INF/alfresco.tld" prefix="a" %> <%@ taglib uri="/WEB-INF/repo.tld" prefix="r" %> - - - - - - - - - + \ No newline at end of file + + + + + + + + + + + \ No newline at end of file diff --git a/source/web/jsp/forums/create-reply-dialog.jsp b/source/web/jsp/forums/create-reply-dialog.jsp index 2470d36b93..3a01adc6c2 100644 --- a/source/web/jsp/forums/create-reply-dialog.jsp +++ b/source/web/jsp/forums/create-reply-dialog.jsp @@ -27,22 +27,15 @@ <%@ taglib uri="/WEB-INF/alfresco.tld" prefix="a" %> <%@ taglib uri="/WEB-INF/repo.tld" prefix="r" %> - - - - - - - - - - - + + + + + + + + + + + + \ No newline at end of file