Return-Path: Delivered-To: apmail-incubator-sling-commits-archive@locus.apache.org Received: (qmail 72663 invoked from network); 4 Feb 2008 19:44:14 -0000 Received: from hermes.apache.org (HELO mail.apache.org) (140.211.11.2) by minotaur.apache.org with SMTP; 4 Feb 2008 19:44:14 -0000 Received: (qmail 46932 invoked by uid 500); 4 Feb 2008 19:44:06 -0000 Delivered-To: apmail-incubator-sling-commits-archive@incubator.apache.org Received: (qmail 46889 invoked by uid 500); 4 Feb 2008 19:44:06 -0000 Mailing-List: contact sling-commits-help@incubator.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: sling-dev@incubator.apache.org Delivered-To: mailing list sling-commits@incubator.apache.org Received: (qmail 46880 invoked by uid 99); 4 Feb 2008 19:44:06 -0000 Received: from athena.apache.org (HELO athena.apache.org) (140.211.11.136) by apache.org (qpsmtpd/0.29) with ESMTP; Mon, 04 Feb 2008 11:44:06 -0800 X-ASF-Spam-Status: No, hits=-100.0 required=10.0 tests=ALL_TRUSTED X-Spam-Check-By: apache.org Received: from [140.211.11.3] (HELO eris.apache.org) (140.211.11.3) by apache.org (qpsmtpd/0.29) with ESMTP; Mon, 04 Feb 2008 19:43:44 +0000 Received: by eris.apache.org (Postfix, from userid 65534) id 47AC91A983A; Mon, 4 Feb 2008 11:43:52 -0800 (PST) Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit Subject: svn commit: r618397 - in /incubator/sling/trunk/launchpad/launchpad-servlets/src/main: java/org/apache/sling/ujax/ resources/org/ resources/org/apache/ resources/org/apache/sling/ resources/org/apache/sling/ujax/ Date: Mon, 04 Feb 2008 19:43:49 -0000 To: sling-commits@incubator.apache.org From: fmeschbe@apache.org X-Mailer: svnmailer-1.0.8 Message-Id: <20080204194352.47AC91A983A@eris.apache.org> X-Virus-Checked: Checked by ClamAV on apache.org Author: fmeschbe Date: Mon Feb 4 11:43:47 2008 New Revision: 618397 URL: http://svn.apache.org/viewvc?rev=618397&view=rev Log: SLING-213 Rewrite of ujax POST support: - Default response is OK (200) with HTML response and elements with well-known ID tags - modified ujax:redirect support. Added: incubator/sling/trunk/launchpad/launchpad-servlets/src/main/java/org/apache/sling/ujax/Change.java incubator/sling/trunk/launchpad/launchpad-servlets/src/main/java/org/apache/sling/ujax/ChangeLog.java incubator/sling/trunk/launchpad/launchpad-servlets/src/main/java/org/apache/sling/ujax/UjaxHtmlResponse.java incubator/sling/trunk/launchpad/launchpad-servlets/src/main/java/org/apache/sling/ujax/UjaxPostProcessor.java incubator/sling/trunk/launchpad/launchpad-servlets/src/main/java/org/apache/sling/ujax/UjaxPropertyValueHandler.java incubator/sling/trunk/launchpad/launchpad-servlets/src/main/resources/org/ incubator/sling/trunk/launchpad/launchpad-servlets/src/main/resources/org/apache/ incubator/sling/trunk/launchpad/launchpad-servlets/src/main/resources/org/apache/sling/ incubator/sling/trunk/launchpad/launchpad-servlets/src/main/resources/org/apache/sling/ujax/ incubator/sling/trunk/launchpad/launchpad-servlets/src/main/resources/org/apache/sling/ujax/UjaxHtmlResponse.html Removed: incubator/sling/trunk/launchpad/launchpad-servlets/src/main/java/org/apache/sling/ujax/UjaxPropertyValueSetter.java Modified: incubator/sling/trunk/launchpad/launchpad-servlets/src/main/java/org/apache/sling/ujax/NodeNameGenerator.java incubator/sling/trunk/launchpad/launchpad-servlets/src/main/java/org/apache/sling/ujax/RequestProperty.java incubator/sling/trunk/launchpad/launchpad-servlets/src/main/java/org/apache/sling/ujax/UjaxFileUploadHandler.java incubator/sling/trunk/launchpad/launchpad-servlets/src/main/java/org/apache/sling/ujax/UjaxPostServlet.java Added: incubator/sling/trunk/launchpad/launchpad-servlets/src/main/java/org/apache/sling/ujax/Change.java URL: http://svn.apache.org/viewvc/incubator/sling/trunk/launchpad/launchpad-servlets/src/main/java/org/apache/sling/ujax/Change.java?rev=618397&view=auto ============================================================================== --- incubator/sling/trunk/launchpad/launchpad-servlets/src/main/java/org/apache/sling/ujax/Change.java (added) +++ incubator/sling/trunk/launchpad/launchpad-servlets/src/main/java/org/apache/sling/ujax/Change.java Mon Feb 4 11:43:47 2008 @@ -0,0 +1,86 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.sling.ujax; + +/** + * Records a change that is used by the changelog + */ +public class Change { + + /** + * available change types + */ + public enum Type { + CREATED, + MODIFIED, + DELETED, + MOVED + } + + /** + * type of the change + */ + private final Type type; + + /** + * arguments + */ + private final String[] arguments; + + /** + * Creates a new change with the given type and arguments + * @param type change type + * @param arguments arguments of the change + */ + public Change(Type type, String ... arguments) { + this.type = type; + this.arguments = arguments; + } + + /** + * Returns the type + * @return the type + */ + public Type getType() { + return type; + } + + /** + * Returns the arguments + * @return the arguments + */ + public String[] getArguments() { + return arguments; + } + + /** + * {@inheritDoc} + */ + public String toString() { + StringBuffer ret = new StringBuffer(type.name().toLowerCase()); + String delim = "("; + for (String a: arguments) { + ret.append(delim); + ret.append('\"'); + ret.append(a); + ret.append('\"'); + delim = ", "; + } + ret.append(");"); + return ret.toString(); + } +} \ No newline at end of file Added: incubator/sling/trunk/launchpad/launchpad-servlets/src/main/java/org/apache/sling/ujax/ChangeLog.java URL: http://svn.apache.org/viewvc/incubator/sling/trunk/launchpad/launchpad-servlets/src/main/java/org/apache/sling/ujax/ChangeLog.java?rev=618397&view=auto ============================================================================== --- incubator/sling/trunk/launchpad/launchpad-servlets/src/main/java/org/apache/sling/ujax/ChangeLog.java (added) +++ incubator/sling/trunk/launchpad/launchpad-servlets/src/main/java/org/apache/sling/ujax/ChangeLog.java Mon Feb 4 11:43:47 2008 @@ -0,0 +1,83 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.sling.ujax; + +import java.util.List; +import java.util.LinkedList; + +/** + * Implements a log that records the changes made to the repository during + * a ujax post request. It can be used to generate a status response. + */ +public class ChangeLog { + + /** + * list of changes + */ + private final List changes = new LinkedList(); + + /** + * Records a 'modified' change + * @param path path of the item that was modified + */ + public void onModified(String path) { + changes.add(new Change(Change.Type.MODIFIED, path)); + } + + /** + * Records a 'created' change + * @param path path of the item that was created + */ + public void onCreated(String path) { + changes.add(new Change(Change.Type.CREATED, path)); + } + + /** + * Records a 'deleted' change + * @param path path of the item that was deleted + */ + public void onDeleted(String path) { + if (path != null) { + changes.add(new Change(Change.Type.DELETED, path)); + } + } + + /** + * Records a 'moved' change. + *

+ * Note: the moved change only records the basic move command. the implied + * changes on the moved properties and sub nodes are not recorded. + * + * @param srcPath source path of the node that was moved + * @param dstPath destination path of the node that was moved. + */ + public void onMoved(String srcPath, String dstPath) { + changes.add(new Change(Change.Type.MOVED, srcPath, dstPath)); + } + + /** + * Dumps the changelog to the given buffer + * @param out the string buffer + * @param lf linefeed string. eg.
or "\n" + */ + public void dump(StringBuffer out, String lf) { + for (Change c: changes) { + out.append(c.toString()); + out.append(lf); + } + } +} \ No newline at end of file Modified: incubator/sling/trunk/launchpad/launchpad-servlets/src/main/java/org/apache/sling/ujax/NodeNameGenerator.java URL: http://svn.apache.org/viewvc/incubator/sling/trunk/launchpad/launchpad-servlets/src/main/java/org/apache/sling/ujax/NodeNameGenerator.java?rev=618397&r1=618396&r2=618397&view=diff ============================================================================== --- incubator/sling/trunk/launchpad/launchpad-servlets/src/main/java/org/apache/sling/ujax/NodeNameGenerator.java (original) +++ incubator/sling/trunk/launchpad/launchpad-servlets/src/main/java/org/apache/sling/ujax/NodeNameGenerator.java Mon Feb 4 11:43:47 2008 @@ -50,16 +50,19 @@ } - /** Get a "nice" node name, if possible, based on given request - * @param prefix if provided, added in front of our parameterNames - * when looking for request parameters + /** + * Get a "nice" node name, if possible, based on given request + * + * @param parameters the request parameters + * @param prefix if provided, added in front of our parameterNames + * when looking for request parameters + * @return a nice node name */ public String getNodeName(RequestParameterMap parameters, String prefix) { - String result = null; - if(prefix==null) { + if (prefix==null) { prefix = ""; } - + // find the first request parameter that matches one of // our parameterNames, in order, and has a value String valueToUse = null; @@ -68,7 +71,6 @@ if(valueToUse != null) { break; } - final RequestParameter[] pp = parameters.get(prefix + param); if(pp!=null) { for(RequestParameter p : pp) { @@ -82,15 +84,13 @@ } } } - - // default value if none provided - if(result==null) { - result = (++counter) + "_" + System.currentTimeMillis(); - } - - // filter value so that it works as a node name + String result; if(valueToUse != null) { + // filter value so that it works as a node name result = filter.filter(valueToUse); + } else { + // default value if none provided + result = nextCounter() + "_" + System.currentTimeMillis(); } // max length @@ -107,5 +107,9 @@ public void setMaxLength(int maxLength) { this.maxLength = maxLength; + } + + public synchronized int nextCounter() { + return ++counter; } } Modified: incubator/sling/trunk/launchpad/launchpad-servlets/src/main/java/org/apache/sling/ujax/RequestProperty.java URL: http://svn.apache.org/viewvc/incubator/sling/trunk/launchpad/launchpad-servlets/src/main/java/org/apache/sling/ujax/RequestProperty.java?rev=618397&r1=618396&r2=618397&view=diff ============================================================================== --- incubator/sling/trunk/launchpad/launchpad-servlets/src/main/java/org/apache/sling/ujax/RequestProperty.java (original) +++ incubator/sling/trunk/launchpad/launchpad-servlets/src/main/java/org/apache/sling/ujax/RequestProperty.java Mon Feb 4 11:43:47 2008 @@ -16,7 +16,6 @@ */ package org.apache.sling.ujax; -import org.apache.sling.api.SlingHttpServletRequest; import org.apache.sling.api.request.RequestParameter; /** @@ -33,7 +32,7 @@ private final String typeHint; - private final String keyName; + private final String relPath; private final String propName; @@ -43,34 +42,31 @@ private RequestParameter[] defaultValues; - public RequestProperty(SlingHttpServletRequest req, String savePrefix, - String keyName, RequestParameter[] values) { - this.keyName = keyName; + public RequestProperty(UjaxPostProcessor ctx, String relPath, + RequestParameter[] values) { + this.relPath = relPath; this.values = values; - if (savePrefix == null) { - savePrefix = ""; - } // split the relative path identifying the property to be saved - if (keyName.indexOf("/")>=0) { - parentPath = keyName.substring(0, keyName.lastIndexOf("/")); - propName = keyName.substring(keyName.lastIndexOf("/") + 1); + if (this.relPath.indexOf("/")>=0) { + parentPath = this.relPath.substring(0, this.relPath.lastIndexOf("/")); + propName = this.relPath.substring(this.relPath.lastIndexOf("/") + 1); } else { parentPath = ""; - propName = keyName; + propName = this.relPath; } // @TypeHint example // // // causes the setProperty using the 'long' property type - final String thName = savePrefix + keyName + UjaxPostServlet.TYPE_HINT_SUFFIX; - final RequestParameter rp = req.getRequestParameter(thName); + final String thName = ctx.getSavePrefix() + this.relPath + UjaxPostServlet.TYPE_HINT_SUFFIX; + final RequestParameter rp = ctx.getRequest().getRequestParameter(thName); typeHint = rp == null ? null : rp.getString(); // @DefaultValue - final String dvName = savePrefix + keyName + UjaxPostServlet.DEFAULT_VALUE_SUFFIX; - defaultValues = req.getRequestParameters(dvName); + final String dvName = ctx.getSavePrefix() + this.relPath + UjaxPostServlet.DEFAULT_VALUE_SUFFIX; + defaultValues = ctx.getRequest().getRequestParameters(dvName); if (defaultValues == null) { defaultValues = EMPTY_PARAM_ARRAY; } @@ -81,8 +77,8 @@ return typeHint; } - public String getKeyName() { - return keyName; + public String getRelPath() { + return relPath; } public String getName() { Modified: incubator/sling/trunk/launchpad/launchpad-servlets/src/main/java/org/apache/sling/ujax/UjaxFileUploadHandler.java URL: http://svn.apache.org/viewvc/incubator/sling/trunk/launchpad/launchpad-servlets/src/main/java/org/apache/sling/ujax/UjaxFileUploadHandler.java?rev=618397&r1=618396&r2=618397&view=diff ============================================================================== --- incubator/sling/trunk/launchpad/launchpad-servlets/src/main/java/org/apache/sling/ujax/UjaxFileUploadHandler.java (original) +++ incubator/sling/trunk/launchpad/launchpad-servlets/src/main/java/org/apache/sling/ujax/UjaxFileUploadHandler.java Mon Feb 4 11:43:47 2008 @@ -16,18 +16,17 @@ */ package org.apache.sling.ujax; +import org.apache.jackrabbit.util.Text; import org.apache.sling.api.request.RequestParameter; -import org.apache.sling.api.SlingHttpServletRequest; import org.apache.sling.commons.mime.MimeTypeService; -import org.apache.jackrabbit.util.Text; -import java.util.Calendar; import java.io.IOException; +import java.util.Calendar; import javax.jcr.Node; import javax.jcr.RepositoryException; -import javax.jcr.nodetype.NodeTypeManager; import javax.jcr.nodetype.NodeType; +import javax.jcr.nodetype.NodeTypeManager; /** * Handles file uploads. @@ -89,17 +88,29 @@ public static final String JCR_DATA = "jcr:data"; /** + * the post processor + */ + private final UjaxPostProcessor ctx; + + /** + * Constructs file upload handler + * @param ctx the post processor + */ + public UjaxFileUploadHandler(UjaxPostProcessor ctx) { + this.ctx = ctx; + } + + /** * Uses the file(s) in the request parameter for creation of new nodes. * if the parent node is a nt:folder a new nt:file is created. otherwise * just a nt:resource. if the name is '*', the filename of * the uploaded file is used. * - * @param request the servlet request * @param parent the parent node * @param prop the assembled property info * @throws RepositoryException if an error occurs */ - void setFile(SlingHttpServletRequest request, Node parent, RequestProperty prop) + void setFile(Node parent, RequestProperty prop) throws RepositoryException { RequestParameter value = prop.getValues()[0]; assert !value.isFormField(); @@ -149,12 +160,14 @@ if (createNtFile) { // create nt:file parent = parent.addNode(name, typeHint); + ctx.getChangeLog().onCreated(parent.getPath()); name = JCR_CONTENT; typeHint = NT_RESOURCE; } // create resource node Node res = parent.addNode(name, typeHint); + ctx.getChangeLog().onCreated(res.getPath()); // get content type String contentType = value.getContentType(); @@ -166,7 +179,7 @@ } if (contentType == null || contentType.equals("application/octet-stream")) { // try to find a better content type - MimeTypeService svc = request.getServiceLocator().getService(MimeTypeService.class); + MimeTypeService svc = ctx.getRequest().getServiceLocator().getService(MimeTypeService.class); if (svc != null) { contentType = svc.getMimeType(value.getFileName()); } @@ -176,10 +189,16 @@ } // set properties - res.setProperty(JCR_LASTMODIFIED, Calendar.getInstance()); - res.setProperty(JCR_MIMETYPE, contentType); + ctx.getChangeLog().onModified( + res.setProperty(JCR_LASTMODIFIED, Calendar.getInstance()).getPath() + ); + ctx.getChangeLog().onModified( + res.setProperty(JCR_MIMETYPE, contentType).getPath() + ); try { - res.setProperty(JCR_DATA, value.getInputStream()); + ctx.getChangeLog().onModified( + res.setProperty(JCR_DATA, value.getInputStream()).getPath() + ); } catch (IOException e) { throw new RepositoryException("Error while retrieving inputstream from parameter value.", e); } Added: incubator/sling/trunk/launchpad/launchpad-servlets/src/main/java/org/apache/sling/ujax/UjaxHtmlResponse.java URL: http://svn.apache.org/viewvc/incubator/sling/trunk/launchpad/launchpad-servlets/src/main/java/org/apache/sling/ujax/UjaxHtmlResponse.java?rev=618397&view=auto ============================================================================== --- incubator/sling/trunk/launchpad/launchpad-servlets/src/main/java/org/apache/sling/ujax/UjaxHtmlResponse.java (added) +++ incubator/sling/trunk/launchpad/launchpad-servlets/src/main/java/org/apache/sling/ujax/UjaxHtmlResponse.java Mon Feb 4 11:43:47 2008 @@ -0,0 +1,231 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.sling.ujax; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Reader; +import java.io.Writer; +import java.util.HashMap; +import java.util.Map; + +import javax.servlet.http.HttpServletResponse; + +/** + * Generator for a HTML status response that displays the changes made in a + * ujax post request. + * + * see UjaxHtmlResponse.html for the format. + */ +public class UjaxHtmlResponse { + + /** + * some human readable title like: 200 Created /foo/bar + */ + public static final String PN_TITLE = "title"; + + /** + * status code. more or less http response status codes + */ + public static final String PN_STATUS_CODE = "status.code"; + + /** + * some human readable status message + */ + public static final String PN_STATUS_MESSAGE = "status.message"; + + /** + * externaly mapped location url of the modified path + */ + public static final String PN_LOCATION = "location"; + + /** + * externaly mapped location url of the parent of the modified path + */ + public static final String PN_PARENT_LOCATION = "parentLocation"; + + /** + * the path of the modified item. this is usually the addressed resource + * or in case of a creation request (eg: /foo/*) the path of the newly + * created node. + */ + public static final String PN_PATH = "path"; + + /** + * the referrer of the request + */ + public static final String PN_REFERER = "referer"; + + /** + * human readable changelog + */ + public static final String PN_CHANGE_LOG = "changeLog"; + + /** + * name of the html template + */ + private static final String TEMPLATE_NAME = "UjaxHtmlResponse.html"; + + /** + * Properties of the response + */ + private final Map properties = new HashMap(); + + /** + * the post processor that contains the change log and all infos + */ + private final UjaxPostProcessor ctx; + + /** + * the servlet response + */ + private final HttpServletResponse response; + + /** + * Creates a new ujax html response + * @param ctx the request processor + * @param response the response + */ + public UjaxHtmlResponse(UjaxPostProcessor ctx, HttpServletResponse response) { + this.ctx = ctx; + this.response = response; + prepare(); + } + + /** + * prepares the response properties + */ + private void prepare() { + String path = ctx.getCurrentPath(); + if (path == null) { + path = ctx.getRootPath(); + } + setProperty(PN_PATH, path); + + if (ctx.getError() != null) { + setStatus(500, ctx.getError().toString()); + setTitle("Error while processing " + path); + } else { + if (ctx.isCreateRequest()) { + setStatus(201, "Created"); + setTitle("Content created " + path); + } else { + setStatus(200, "OK"); + setTitle("Content modified " + path); + } + } + setProperty(PN_LOCATION, ctx.getLocation()); + setProperty(PN_PARENT_LOCATION, ctx.getParentLocation()); + String referer = ctx.getRequest().getHeader("referer"); + if (referer == null) { + referer = ""; + } + setProperty(PN_REFERER, referer); + // get changelog + StringBuffer cl = new StringBuffer("

");
+        ctx.getChangeLog().dump(cl, "
"); + cl.append("
"); + setProperty(PN_CHANGE_LOG, cl.toString()); + } + + /** + * sets the response status code properties + * @param code the code + * @param message the message + */ + public void setStatus(int code, String message) { + setProperty(PN_STATUS_CODE, String.valueOf(code)); + setProperty(PN_STATUS_MESSAGE, message); + } + + /** + * Sets the title of the respose message + * @param title the title + */ + public void setTitle(String title) { + setProperty(PN_TITLE, title); + } + + /** + * Sets a generic response property with the given + * @param name name of the property + * @param value value of the property + */ + public void setProperty(String name, String value) { + properties.put(name, value); + } + + /** + * Writes the response to the given writer and replaces all ${var} patterns + * by the value of the respective property. if the property is not defined + * the pattern is not modified. + * + * @throws IOException if an i/o exception occurs + */ + public void send() throws IOException { + response.setContentType("text/html"); + Writer out = response.getWriter(); + InputStream template = getClass().getResourceAsStream(TEMPLATE_NAME); + Reader in = new BufferedReader(new InputStreamReader(template)); + StringBuffer varBuffer = new StringBuffer(); + int state = 0; + int read; + while ((read = in.read()) >= 0) { + char c = (char) read; + switch (state) { + // initial + case 0: + if (c=='$') { + state = 1; + } else { + out.write(c); + } + break; + // $ read + case 1: + if (c=='{') { + state = 2; + } else { + state = 0; + out.write('$'); + out.write(c); + } + break; + // { read + case 2: + if (c=='}') { + state = 0; + String prop = properties.get(varBuffer.toString()); + if (prop == null) { + out.write("${"); + out.write(varBuffer.toString()); + out.write("}"); + } else { + out.write(prop); + } + varBuffer.setLength(0); + } else { + varBuffer.append(c); + } + } + } + in.close(); + out.flush(); + } +} \ No newline at end of file Added: incubator/sling/trunk/launchpad/launchpad-servlets/src/main/java/org/apache/sling/ujax/UjaxPostProcessor.java URL: http://svn.apache.org/viewvc/incubator/sling/trunk/launchpad/launchpad-servlets/src/main/java/org/apache/sling/ujax/UjaxPostProcessor.java?rev=618397&view=auto ============================================================================== --- incubator/sling/trunk/launchpad/launchpad-servlets/src/main/java/org/apache/sling/ujax/UjaxPostProcessor.java (added) +++ incubator/sling/trunk/launchpad/launchpad-servlets/src/main/java/org/apache/sling/ujax/UjaxPostProcessor.java Mon Feb 4 11:43:47 2008 @@ -0,0 +1,553 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.sling.ujax; + +import org.apache.sling.api.SlingHttpServletRequest; +import org.apache.sling.api.resource.NonExistingResource; +import org.apache.sling.api.resource.Resource; +import org.apache.sling.api.wrappers.SlingRequestPaths; +import org.apache.sling.api.request.RequestParameter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Map; + +import javax.jcr.Session; +import javax.jcr.RepositoryException; +import javax.jcr.Node; +import javax.servlet.ServletException; +import javax.swing.RootPaneContainer; + +/** + * Holds various states and encapsulates method that are neede to handle a + * ujax post request. + */ +public class UjaxPostProcessor { + + /** + * default log + */ + private static final Logger log = LoggerFactory.getLogger(UjaxPostProcessor.class); + + + /** + * log that records the changes applied during the processing of the + * post request. + */ + private final ChangeLog changeLog = new ChangeLog(); + + /** + * handler that deals with properties + */ + private final UjaxPropertyValueHandler propHandler; + + /** + * handler that deals with file upload + */ + private final UjaxFileUploadHandler uploadHandler; + + /** + * utility class for generating node names + */ + private final NodeNameGenerator nodeNameGenerator; + + /** + * the sling http servlet request + */ + private final SlingHttpServletRequest request; + + /** + * the jcr session to operate on + */ + private final Session session; + + /** + * the root path of this processor. + */ + private final String rootPath; + + /** + * path of the node that was targeted or created. + */ + private String currentPath; + + /** + * prefix of which the names of request properties must start with + * in order to be regardes as input values. + */ + private String savePrefix; + + /** + * indicates if the request contains a 'star suffix' + */ + private boolean isCreateRequest; + + /** + * records any error + */ + private Exception error; + + /** + * Creates a new post processor + * @param request the request to operate on + * @param session jcr session to operate on + * @param nodeNameGenerator the node name generator. use a servlet scoped one, + * so that it can hold states. + */ + public UjaxPostProcessor(SlingHttpServletRequest request, Session session, + NodeNameGenerator nodeNameGenerator) { + this.request = request; + this.session = session; + + // default to non-creating request (trailing DEFAULT_CREATE_SUFFIX) + isCreateRequest = false; + + // calculate the paths + StringBuffer rootPathBuf = new StringBuffer(); + String suffix; + Resource currentResource = request.getResource(); + if (Resource.RESOURCE_TYPE_NON_EXISTING.equals(currentResource.getResourceType())) { + + // no resource, treat the missing resource path as suffix + suffix = currentResource.getPath(); + + } else { + + // resource for part of the path, use request suffix + suffix = request.getRequestPathInfo().getSuffix(); + + // and preset the path buffer with the resource path + rootPathBuf.append(currentResource.getPath()); + + } + + // check for extensions or create suffix in the suffix + if (suffix != null) { + + // cut off any selectors/extension from the suffix + int dotPos = suffix.indexOf('.'); + if (dotPos > 0) { + suffix = suffix.substring(0, dotPos); + + // otherwise check whether it is a create request (trailing /*) + } else if (suffix.endsWith(UjaxPostServlet.DEFAULT_CREATE_SUFFIX)) { + suffix = suffix.substring(0, suffix.length() + - UjaxPostServlet.DEFAULT_CREATE_SUFFIX.length()); + isCreateRequest = true; + } + + // append the remains of the suffix to the path buffer + rootPathBuf.append(suffix); + + } + + rootPath = rootPathBuf.toString(); + + this.nodeNameGenerator = nodeNameGenerator; + propHandler = new UjaxPropertyValueHandler(this); + uploadHandler = new UjaxFileUploadHandler(this); + } + + /** + * Returns the change log. + * @return the change log. + */ + public ChangeLog getChangeLog() { + return changeLog; + } + + /** + * Returns the jcr session + * @return the jcr session + */ + public Session getSession() { + return session; + } + + /** + * Returns the root path of this processor. + * @return the root path of this processor. + */ + public String getRootPath() { + return rootPath; + } + + /** + * Returns the path of the node that was the parent of the property + * modifications. + * @return the path of the 'current' node. + */ + public String getCurrentPath() { + return currentPath; + } + + /** + * Returns true if this was a create request. + * @return true if this was a create request. + */ + public boolean isCreateRequest() { + return isCreateRequest; + } + + /** + * Returns the location of the modification. this is the externalized form + * of the current path. + * @return the location of the modification. + */ + public String getLocation() { + if (currentPath == null) { + return externalizePath(rootPath); + } else { + return externalizePath(currentPath); + } + } + + /** + * Returns the parent location of the modification. this is the externalized + * form of the parent node of the current path. + * @return the location of the modification. + */ + public String getParentLocation() { + String path = currentPath == null ? rootPath : currentPath; + path = path.substring(0, path.lastIndexOf('/')); + return externalizePath(path); + } + + /** + * Returns an external form of the given path prepeding the context path + * and appending a display extension. + * @param path the path to externalize + * @return the url + */ + private String externalizePath(String path) { + StringBuffer ret = new StringBuffer(); + ret.append(SlingRequestPaths.getContextPath(request)); + ret.append(request.getResourceResolver().map(path)); + + // append optional extension + String ext = request.getParameter(UjaxPostServlet.RP_DISPLAY_EXTENSION); + if (ext != null && ext.length() > 0) { + if (ext.charAt(0) != '.') { + ret.append('.'); + } + ret.append(ext); + } + + return ret.toString(); + } + + + + /** + * Returns any recorded error or null + * @return an error or null + */ + public Exception getError() { + return error; + } + + /** + * Returns the request of this processor + * @return the sling servlet request + */ + public SlingHttpServletRequest getRequest() { + return request; + } + + /** + * Processes the actions defined by the request + */ + public void run() { + try { + processDeletes(); + processMoves(); + processContent(); + if (session.hasPendingChanges()) { + session.save(); + } + } catch (Exception e) { + error = e; + } finally { + try { + if (session.hasPendingChanges()) { + session.refresh(false); + } + } catch (RepositoryException e) { + log.warn("RepositoryException in finally block: {}", e.getMessage(), e); + } + } + } + + /** + * Resolves the given path with respect to the current root path. + * + * @param path the path to resolve + * @return the given path if it starts with a '/'; + * a resolved path otherwise. + */ + public String resolvePath(String path) { + if (path.startsWith("/")) { + return path; + } else { + return rootPath + "/" + path; + } + } + + /** + * Delete Items at the provided paths + * + * @throws RepositoryException if a repository error occurs + */ + private void processDeletes() throws RepositoryException { + final String [] paths = request.getParameterValues(UjaxPostServlet.RP_DELETE_PATH); + if (paths != null) { + for (String path : paths) { + if (!path.equals("")) { + path = resolvePath(path); + if (session.itemExists(path)) { + session.getItem(path).remove(); + changeLog.onDeleted(path); + log.debug("Deleted item {}", path); + } else { + log.debug("Item at {} not found for deletion, ignored", path); + } + } + } + } + } + + /** + * Move nodes at the provided paths + * + * @throws RepositoryException if a repository error occurs + * @throws IllegalArgumentException if the move arguments are incorrect + */ + private void processMoves() throws RepositoryException, + IllegalArgumentException { + final String [] moveSrc = request.getParameterValues(UjaxPostServlet.RP_MOVE_SRC); + final String [] moveDest = request.getParameterValues(UjaxPostServlet.RP_MOVE_DEST); + if (moveSrc == null || moveDest == null) { + return; + } + if (moveSrc.length != moveDest.length) { + throw new IllegalArgumentException("Unable to process move. there " + + "must be the same amount of source and destination parameters."); + } + for (int i=0; i e: request.getRequestParameterMap().entrySet()) { + final String paramName = e.getKey(); + + // do not store parameters with names starting with ujax: + if(paramName.startsWith(UjaxPostServlet.RP_PREFIX)) { + continue; + } + // ignore field with a '@TypeHint' suffix. this is dealt in RequestProperty + if (paramName.endsWith(UjaxPostServlet.TYPE_HINT_SUFFIX)) { + continue; + } + // ignore field with a '@DefaultValue' suffix. this is dealt in RequestProperty + if (paramName.endsWith(UjaxPostServlet.DEFAULT_VALUE_SUFFIX)) { + continue; + } + // skip parameters that do not start with the save prefix + if(!paramName.startsWith(getSavePrefix())) { + continue; + } + String propertyName = paramName.substring(getSavePrefix().length()); + if (propertyName.length() == 0) { + continue; + } + // SLING-130: VALUE_FROM_SUFFIX means take the value of this + // property from a different field + RequestParameter[] values = e.getValue(); + final int vfIndex = propertyName.indexOf(UjaxPostServlet.VALUE_FROM_SUFFIX); + if (vfIndex >= 0) { + // @ValueFrom example: + // + // causes the JCR Text property to be set to the value of the fulltext form field. + propertyName = propertyName.substring(0, vfIndex); + final RequestParameter[] rp = request.getRequestParameterMap().get(paramName); + if(rp == null || rp.length > 1) { + // @ValueFrom params must have exactly one value, else ignored + continue; + } + String mappedName = rp[0].getString(); + values = request.getRequestParameterMap().get(mappedName); + if(values==null) { + // no value for "fulltext" in our example, ignore parameter + continue; + } + } + // create property helper and get parent node + RequestProperty prop = new RequestProperty(this, propertyName, values); + Node parent; + if(prop.getRelPath().startsWith("/")) { + parent = deepCreateNode(prop.getParentPath()); + } else if (!prop.getParentPath().equals("")) { + parent = currentNode.getNode(prop.getParentPath()); + } else { + parent = currentNode; + } + // call handler + if (prop.isFileUpload()) { + uploadHandler.setFile(parent, prop); + } else { + propHandler.setProperty(parent, prop); + } + } + } + + /** + * Deep creates a node, parent-padding with nt:unstructured nodes + * + * @param path absolute path to node that needs to be deep-created + * @return node at path + * @throws RepositoryException if an error occurs + */ + private Node deepCreateNode(String path) throws RepositoryException { + if(log.isDebugEnabled()) { + log.debug("Deep-creating Node '{}'", path); + } + + String[] pathelems = path.substring(1).split("/"); + Node node = session.getRootNode(); + for (String name: pathelems) { + if (node.hasNode(name)) { + node = node.getNode(name); + } else { + node = node.addNode(name); + changeLog.onCreated(node.getPath()); + } + } + return node; + } + + /** + * Return the "save prefix" to use. the names of request properties must + * start with that prefix in order to be regarded as input values. + * + * @return the save prefix + */ + public String getSavePrefix() { + if (savePrefix == null) { + savePrefix = request.getParameter(UjaxPostServlet.RP_SAVE_PARAM_PREFIX); + if (savePrefix == null) { + savePrefix = UjaxPostServlet.DEFAULT_SAVE_PARAM_PREFIX; + } + if (savePrefix.length() > 0) { + String prefix = ""; + // if no parameters start with this prefix, it is not used + for (String name: request.getRequestParameterMap().keySet()) { + if (name.startsWith(savePrefix)) { + prefix = savePrefix; + break; + } + } + savePrefix = prefix; + } + } + return savePrefix; + } + + /** + * If orderCode is ORDER_ZERO, move n so that it is the first child of its + * parent + * @param n th node to order + * @param orderCode code that specifies how to order + * @throws RepositoryException if a repository error occurs + */ + private void processNodeOrder(Node n, String orderCode) + throws RepositoryException { + if (UjaxPostServlet.ORDER_ZERO.equals(orderCode)) { + final Node parent = n.getParent(); + final String beforename=parent.getNodes().nextNode().getName(); + parent.orderBefore(n.getName(), beforename); + if(log.isDebugEnabled()) { + log.debug("Node {} moved to be first child of its parent, " + + "due to orderCode=" + orderCode, n.getPath()); + } + } else { + if(log.isDebugEnabled()) { + log.debug("orderCode '{}' invalid, ignored", orderCode); + } + } + } + +} \ No newline at end of file Modified: incubator/sling/trunk/launchpad/launchpad-servlets/src/main/java/org/apache/sling/ujax/UjaxPostServlet.java URL: http://svn.apache.org/viewvc/incubator/sling/trunk/launchpad/launchpad-servlets/src/main/java/org/apache/sling/ujax/UjaxPostServlet.java?rev=618397&r1=618396&r2=618397&view=diff ============================================================================== --- incubator/sling/trunk/launchpad/launchpad-servlets/src/main/java/org/apache/sling/ujax/UjaxPostServlet.java (original) +++ incubator/sling/trunk/launchpad/launchpad-servlets/src/main/java/org/apache/sling/ujax/UjaxPostServlet.java Mon Feb 4 11:43:47 2008 @@ -16,479 +16,196 @@ */ package org.apache.sling.ujax; -import org.apache.sling.api.SlingException; import org.apache.sling.api.SlingHttpServletRequest; import org.apache.sling.api.SlingHttpServletResponse; -import org.apache.sling.api.request.RequestParameter; import org.apache.sling.api.resource.Resource; import org.apache.sling.api.servlets.SlingAllMethodsServlet; import org.apache.sling.api.wrappers.SlingRequestPaths; import org.apache.sling.core.CoreConstants; +import org.apache.sling.jcr.resource.JcrResourceUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; -import javax.jcr.Item; import javax.jcr.Node; import javax.jcr.RepositoryException; import javax.jcr.Session; import javax.servlet.ServletException; -import javax.servlet.http.HttpServletResponse; -/** POST servlet that implements the ujax "protocol" */ +/** + * POST servlet that implements the ujax "protocol" + */ public class UjaxPostServlet extends SlingAllMethodsServlet { + private static final long serialVersionUID = 1837674988291697074L; + /** + * default log + */ private static final Logger log = LoggerFactory.getLogger(UjaxPostServlet.class); - private final UjaxPropertyValueSetter propertyValueSetter = new UjaxPropertyValueSetter(); - private final UjaxFileUploadHandler uploadHandler = new UjaxFileUploadHandler(); - private final NodeNameGenerator nodeNameGenerator = new NodeNameGenerator(); - /** Prefix for parameter names which control this POST - * (ujax stands for "microjax", RP_ stands for "request param") + /** + * Prefix for parameter names which control this POST + * (ujax stands for "microjax", RP_ stands for "request param") */ public static final String RP_PREFIX = "ujax:"; - /** suffix that indicates node creation */ + /** + * suffix that indicates node creation + */ public static final String DEFAULT_CREATE_SUFFIX = "/*"; - /** Optional request parameter: redirect to the specified URL after POST */ - public static final String RP_REDIRECT_TO = RP_PREFIX + "redirect"; - - /** Optional request parameter: delete the specified content paths */ + /** + * Optional request parameter: delete the specified content paths + */ public static final String RP_DELETE_PATH = RP_PREFIX + "delete"; - /** Optional request parameter: move the specified content paths */ + /** + * Optional request parameter: move the specified content paths + */ public static final String RP_MOVE_SRC = RP_PREFIX + "moveSrc"; + + /** + * Optional request parameter: move the specified content paths to this + * destination + */ public static final String RP_MOVE_DEST = RP_PREFIX + "moveDest"; - /** Optional request parameter: only request parameters starting with this prefix are - * saved as Properties when creating a Node. Active only if at least one parameter - * starts with this prefix, and defaults to {@link #DEFAULT_SAVE_PARAM_PREFIX}. + /** + * Optional request parameter: only request parameters starting with this prefix are + * saved as Properties when creating a Node. Active only if at least one parameter + * starts with this prefix, and defaults to {@link #DEFAULT_SAVE_PARAM_PREFIX}. */ public static final String RP_SAVE_PARAM_PREFIX = RP_PREFIX + "saveParamPrefix"; - /** Default value for {@link #RP_SAVE_PARAM_PREFIX} */ + /** + * Default value for {@link #RP_SAVE_PARAM_PREFIX} + */ public static final String DEFAULT_SAVE_PARAM_PREFIX = "./"; - /** Optional request parameter: if value is 0, created node is ordered so as - * to be the first child of its parent. + /** + * Optional request parameter: if value is 0, created node is ordered so as + * to be the first child of its parent. */ public static final String RP_ORDER = RP_PREFIX + "order"; - /** Code value for RP_ORDER */ + /** + * Code value for RP_ORDER + */ public static final String ORDER_ZERO = "0"; - /** Optional request parameter: if provided, added at the end of the computed - * (or supplied) redirect URL + /** + * Optional request parameter: redirect to the specified URL after POST + */ + public static final String RP_REDIRECT_TO = RP_PREFIX + "redirect"; + + /** + * Optional request parameter: if provided, added at the end of the computed + * (or supplied) redirect URL */ public static final String RP_DISPLAY_EXTENSION = RP_PREFIX + "displayExtension"; - - /** SLING-130, suffix that maps form field names to different JCR property names */ + + /** + * SLING-130, suffix that maps form field names to different JCR property names + */ public static final String VALUE_FROM_SUFFIX = "@ValueFrom"; + /** + * suffix that indicates a type hint parameter + */ public static final String TYPE_HINT_SUFFIX = "@TypeHint"; - public static final String DEFAULT_VALUE_SUFFIX = "@DefaultValue"; - - @Override - protected void doPost(SlingHttpServletRequest request, SlingHttpServletResponse response) - throws ServletException, IOException { - - Session s = null; - try { - - // select the Resource to process - Resource currentResource = request.getResource(); - Node currentNode = currentResource.adaptTo(Node.class); - - // need a Node, path and Session - final String currentPath; - if(currentNode != null) { - currentPath = currentNode.getPath(); - s = currentNode.getSession(); - } else { - currentPath = SlingRequestPaths.getPathInfo(request); - s = (Session)request.getAttribute(CoreConstants.SESSION); - } - - if(s==null) { - throw new ServletException("No JCR Session available, currentNode=" + currentNode); - } - - // process changes - processDeletes(request, s, currentPath); - processMoves(request, s, currentPath); - createOrUpdateNodesFromRequest(request, response, s); - - } catch(RepositoryException re) { - throw new SlingException(re.toString(), re); - - } finally { - try { - if (s != null && s.hasPendingChanges()) { - s.refresh(false); - } - } catch(RepositoryException re) { - log.warn("RepositoryException in finally block: "+ re.getMessage(),re); - } - } - } - - /** Create or update node(s) according to current request , and send response */ - protected void createOrUpdateNodesFromRequest(SlingHttpServletRequest request, SlingHttpServletResponse response, Session s) - throws RepositoryException, IOException { - - // find out the actual "save prefix" to use - only parameters starting with - // this prefix are saved as Properties, when creating nodes, see setPropertiesFromRequest() - final String savePrefix = getSavePrefix(request); - - // use the request path (disregarding resource resolution) - // but remove any extension or selectors - String currentPath = SlingRequestPaths.getPathInfo(request); - Node currentNode = null; - final int dotPos = currentPath.indexOf('.'); - if(dotPos >= 0) { - currentPath = currentPath.substring(0,dotPos); - } - - final String starSuffix = DEFAULT_CREATE_SUFFIX; - if(currentPath.endsWith(starSuffix)) { - // If the path ends with a *, create a node under its parent, with - // a generated node name - currentPath = currentPath.substring(0, currentPath.length() - starSuffix.length()); - currentPath += "/" + nodeNameGenerator.getNodeName(request.getRequestParameterMap(), savePrefix); - - // if resulting path exists, add a suffix until it's not the case anymore - if(s.itemExists(currentPath)) { - for(int suffix = 0; suffix < 100; suffix++) { - String newPath = currentPath + "_" + suffix; - if(!s.itemExists(newPath)) { - currentPath = newPath; - break; - } - } - } - - if(s.itemExists(currentPath)) { - response.sendError( - HttpServletResponse.SC_INTERNAL_SERVER_ERROR, - "Collision in generated node names for path=" + currentPath); - return; - } - - } else if(s.itemExists(currentPath)) { - // update to an existing node - final Item item = s.getItem(currentPath); - if(item.isNode()) { - currentNode = (Node)item; - } else { - response.sendError(HttpServletResponse.SC_CONFLICT, - "Item at path " + currentPath + " is not a Node"); - return; - } - - } else { - // request to create a new node at a specific path - use the supplied path as is - } - - Set createdNodes = new HashSet(); - if(currentNode == null) { - currentNode = deepCreateNode(s, currentPath, createdNodes); - } - currentPath = currentNode.getPath(); - - // process the "order" command if any - final String order = request.getParameter(RP_ORDER); - if(order!=null) { - processNodeOrder(currentNode,order); - } - - // walk the request parameters, create and save nodes and properties - setPropertiesFromRequest(currentNode, request, savePrefix, createdNodes); - - // sava data and send redirect - s.save(); - response.sendRedirect(getRedirectUrl(request,currentNode.getPath())); - } - - /** compute redirect URL (SLING-126) */ - protected String getRedirectUrl(SlingHttpServletRequest request, String currentNodePath) { - - // redirect param has priority (but see below, magic star) - String result = request.getParameter(RP_REDIRECT_TO); - final boolean magicStar = "*".equals(result); - - if(result==null || result.trim().length()==0) { - // try Referer - result = request.getHeader("Referer"); - } - - // redirect param = star means "redirect to current node", useful in browsers - // when you don't want to use the Referer - if(magicStar || result==null || result.trim().length()==0) { - // use path of current node, with optional extension - final String redirectExtension = request.getParameter(RP_DISPLAY_EXTENSION); - result = currentNodePath; - - if(redirectExtension!=null) { - if(redirectExtension.startsWith(".")) { - result += redirectExtension; - } else { - result += "." + redirectExtension; - } - } - - result = - SlingRequestPaths.getContextPath(request) - + SlingRequestPaths.getServletPath(request) - + result; - } - - if(log.isDebugEnabled()) { - log.debug("Will redirect to " + result); - } - - return result; - } - - /** Set Node properties from current request + /** + * suffix that indicates a default value parameter */ - private void setPropertiesFromRequest(Node n, SlingHttpServletRequest request, - String savePrefix, Set createdNodes) - throws RepositoryException { - - for(Map.Entry e : request.getRequestParameterMap().entrySet()) { - final String paramName = e.getKey(); - - if(paramName.startsWith(RP_PREFIX)) { - // do not store parameters with names starting with ujax: - continue; - } - - String propertyName = paramName; - if(savePrefix!=null) { - if(!paramName.startsWith(savePrefix)) { - continue; - } - propertyName = paramName.substring(savePrefix.length()); - } - - // ignore field with a '@TypeHint' suffix. this is dealt in RequestProperty - if (propertyName.endsWith(TYPE_HINT_SUFFIX)) { - continue; - } - - // ignore field with a '@DefaultValue' suffix. this is dealt in RequestProperty - if (propertyName.endsWith(DEFAULT_VALUE_SUFFIX)) { - continue; - } - - // SLING-130: VALUE_FROM_SUFFIX means take the value of this - // property from a different field - RequestParameter[] values = e.getValue(); - final int vfIndex = propertyName.indexOf(VALUE_FROM_SUFFIX); - if(vfIndex >= 0) { - // @ValueFrom example: - // - // causes the JCR Text property to be set to the value of the fulltext form field. - propertyName = propertyName.substring(0, vfIndex); - final RequestParameter[] rp = request.getRequestParameterMap().get(paramName); - if(rp == null || rp.length > 1) { - // @ValueFrom params must have exactly one value, else ignored - continue; - } - String mappedName = rp[0].getString(); - values = request.getRequestParameterMap().get(mappedName); - if(values==null) { - // no value for "fulltext" in our example, ignore parameter - continue; - } - } - - RequestProperty prop = new RequestProperty(request, savePrefix, propertyName, values); - - setProperty(n, request, createdNodes, prop); - } - } + public static final String DEFAULT_VALUE_SUFFIX = "@DefaultValue"; /** - * Set a single Property on node N - * - * @throws RepositoryException if a repository error occurs - */ - private void setProperty(Node n, SlingHttpServletRequest request, - Set createdNodes, RequestProperty prop) - throws RepositoryException { - - if (prop.getName().equals("")) { - return; - } + * utility class for generating node names + */ + private final NodeNameGenerator nodeNameGenerator = new NodeNameGenerator(); - // get or create the parent node - final String path = n.getPath(); - final Session s = n.getSession(); - Node parent; - if(prop.getKeyName().startsWith("/")) { - parent = deepCreateNode(s, prop.getParentPath(), createdNodes); - } else if (!prop.getParentPath().equals("")) { - parent = (Node) s.getItem(path + "/" + prop.getParentPath()); - } else { - parent = (Node) s.getItem(path); - } + @Override + protected void doPost(SlingHttpServletRequest request, + SlingHttpServletResponse response) + throws ServletException, IOException { - // call setter - if (prop.isFileUpload()) { - uploadHandler.setFile(request, parent, prop); + // create a post processor and process changes + UjaxPostProcessor p = createPostProcessor(request); + p.run(); + + // check for redirect url + String redirect = getRedirectUrl(p); + if (redirect != null && p.getError() == null) { + response.sendRedirect(redirect); } else { - final boolean nodeIsNew = createdNodes.contains(parent); - propertyValueSetter.setProperty(parent, prop, nodeIsNew); - } - } - - /** - * Deep creates a node, parent-padding with nt:unstructured nodes - * - * @param path absolute path to node that needs to be deep-created - */ - private Node deepCreateNode(Session s, String path, Set createdNodes) - throws RepositoryException { - if(log.isDebugEnabled()) { - log.debug("Deep-creating Node '" + path + "'"); - } - - String[] pathelems = path.substring(1).split("/"); - int i = 0; - String mypath = ""; - Node parent = s.getRootNode(); - while (i < pathelems.length) { - String name = pathelems[i]; - mypath += "/" + name; - if (!s.itemExists(mypath)) { - createdNodes.add(parent.addNode(name)); - } - parent = (Node) s.getItem(mypath); - i++; + // create a html response and send + UjaxHtmlResponse resp = new UjaxHtmlResponse(p, response); + resp.send(); } - return (parent); } /** - * Delete Items at the provided paths - * @param request the servlet request - * @param s the session - * @param currentPath the current path - * @throws RepositoryException if a repository error occurs - */ - private void processDeletes(SlingHttpServletRequest request, Session s, - String currentPath) - throws RepositoryException { - final String [] pathsToDelete = request.getParameterValues(RP_DELETE_PATH); - int deleteCount = 0; - - if (pathsToDelete != null) { - for(String path : pathsToDelete) { - if(!path.startsWith("/")) { - path = currentPath + "/" + path; - } - if(s.itemExists(path)) { - s.getItem(path).remove(); - deleteCount++; - if(log.isDebugEnabled()) { - log.debug("Deleted item " + path); - } - } else { - if(log.isDebugEnabled()) { - log.debug("Item '" + path + "' not found for deletion, ignored"); - } - } - } + * Creats a post processor for the given request. + * @param request the request for the processor + * @return a post context + * @throws ServletException if no session can be aquired or if there is a + * repository error. + */ + private UjaxPostProcessor createPostProcessor(SlingHttpServletRequest request) + throws ServletException { + Session s = request.getResourceResolver().adaptTo(Session.class); + if (s == null) { + throw new ServletException("No JCR Session available"); } - if(deleteCount > 0) { - s.save(); - } + // create the post context + return new UjaxPostProcessor(request, s, nodeNameGenerator); } /** - * Move nodes at the provided paths - * @param request the servlet request - * @param s the session - * @param currentPath the current path - * @throws RepositoryException if a repository error occurs - */ - private void processMoves(SlingHttpServletRequest request, Session s, - String currentPath) - throws RepositoryException { - int moveCount = 0; - final String [] moveSrc = request.getParameterValues(RP_MOVE_SRC); - final String [] moveDest = request.getParameterValues(RP_MOVE_DEST); - if (moveSrc == null || moveDest == null) { - return; - } - if (moveSrc.length != moveDest.length) { - return; - } - for (int i=0; inull + */ + protected String getRedirectUrl(UjaxPostProcessor ctx) { + // redirect param has priority (but see below, magic star) + String result = ctx.getRequest().getParameter(RP_REDIRECT_TO); + if (result != null) { + + // redirect to created/modified Resource + int star = result.indexOf('*'); + if (star >= 0) { + StringBuffer buf = new StringBuffer(); + + // anything before the star + if (star > 0) { + buf.append(result.substring(0, star)); + } + + // append the name of the manipulated node + String nodePath = ctx.getCurrentPath(); + if (nodePath == null) { + nodePath = ctx.getRootPath(); + } + buf.append(JcrResourceUtil.getName(nodePath)); + + // anything after the star + if (star < result.length() - 1) { + buf.append(result.substring(star + 1)); + } + + // use the created path as the redirect result + result = buf.toString(); } - s.move(src, dest); - moveCount++; + if (log.isDebugEnabled()) { - log.debug("moved {} to {}", src, dest); - } - } - if(moveCount > 0) { - s.save(); - } - } - - /** Return the "save prefix" to use, null if none */ - private String getSavePrefix(SlingHttpServletRequest request) { - String prefix = request.getParameter(RP_SAVE_PARAM_PREFIX); - if (prefix==null) { - prefix = DEFAULT_SAVE_PARAM_PREFIX; - } - - // if no parameters start with this prefix, it is not used - for (String name : request.getRequestParameterMap().keySet()) { - if (name.startsWith(prefix)) { - return prefix; + log.debug("Will redirect to " + result); } } - return null; + return result; } - /** If orderCode is ORDER_ZERO, move n so that it is the first - * child of its parent - * @throws RepositoryException */ - private void processNodeOrder(Node n, String orderCode) throws RepositoryException { - if(ORDER_ZERO.equals(orderCode)) { - final String path = n.getPath(); - final Node parent=(Node) n.getSession().getItem(path.substring(0,path.lastIndexOf('/'))); - final String myname=path.substring(path.lastIndexOf('/')+1); - final String beforename=parent.getNodes().nextNode().getName(); - parent.orderBefore(myname, beforename); - - if(log.isDebugEnabled()) { - log.debug("Node " + n.getPath() + " moved to be first child of its parent, due to orderCode=" + orderCode); - } - - } else { - if(log.isDebugEnabled()) { - log.debug("orderCode '" + orderCode + "' invalid, ignored"); - } - } - } } Added: incubator/sling/trunk/launchpad/launchpad-servlets/src/main/java/org/apache/sling/ujax/UjaxPropertyValueHandler.java URL: http://svn.apache.org/viewvc/incubator/sling/trunk/launchpad/launchpad-servlets/src/main/java/org/apache/sling/ujax/UjaxPropertyValueHandler.java?rev=618397&view=auto ============================================================================== --- incubator/sling/trunk/launchpad/launchpad-servlets/src/main/java/org/apache/sling/ujax/UjaxPropertyValueHandler.java (added) +++ incubator/sling/trunk/launchpad/launchpad-servlets/src/main/java/org/apache/sling/ujax/UjaxPropertyValueHandler.java Mon Feb 4 11:43:47 2008 @@ -0,0 +1,201 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.sling.ujax; + +import java.util.Calendar; + +import javax.jcr.Node; +import javax.jcr.Property; +import javax.jcr.PropertyType; +import javax.jcr.RepositoryException; + +/** + * Sets a Property on the given Node, in some cases with a specific type and + * value. For example, "lastModified" with an empty value is stored as the + * current Date. + */ +class UjaxPropertyValueHandler { + + public static final String CREATED_FIELD = "created"; + public static final String CREATED_BY_FIELD = "createdBy"; + public static final String LAST_MODIFIED_FIELD = "lastModified"; + public static final String LAST_MODIFIED_BY_FIELD = "lastModifiedBy"; + + /** + * the post processor + */ + private final UjaxPostProcessor ctx; + + /** + * current date for all properties in this request + */ + private final Calendar now = Calendar.getInstance(); + + /** + * Constructs a propert value handler + * @param ctx the post processor + */ + public UjaxPropertyValueHandler(UjaxPostProcessor ctx) { + this.ctx = ctx; + } + + + /** + * Set property on given node, with some automatic values when user provides + * the field name but no value. + * + * html example for testing: + * + * <input type="hidden" name="dateCreated"/> + * <input type="hidden" name="lastModified"/> + * <input type="hidden" name="createdBy" /> + * <input type="hidden" name="lastModifiedBy"/> + * + * + * @param parent the parent node + * @param prop the request property + * @throws RepositoryException if a repository error occurs + */ + void setProperty(Node parent, RequestProperty prop) + throws RepositoryException { + + final String name = prop.getName(); + if (prop.providesValue()) { + // if user provided a value, don't mess with it + setPropertyAsIs(parent, prop); + + } else if (CREATED_FIELD.equals(name)) { + if (parent.isNew()) { + setCurrentDate(parent, name); + } + + } else if (CREATED_BY_FIELD.equals(name)) { + if (parent.isNew()) { + setCurrentUser(parent, name); + } + + } else if (LAST_MODIFIED_FIELD.equals(name)) { + setCurrentDate(parent, name); + + } else if (LAST_MODIFIED_BY_FIELD.equals(name)) { + setCurrentUser(parent, name); + + } else { + // no magic field, set value as provided + setPropertyAsIs(parent, prop); + } + } + + /** + * Sets the property to the given date + * @param parent parent node + * @param name name of the property + * @throws RepositoryException if a repository error occurs + */ + private void setCurrentDate(Node parent, String name) + throws RepositoryException { + removePropertyIfExists(parent, name); + ctx.getChangeLog().onModified( + parent.setProperty(name, now).getPath() + ); + } + + /** + * set property to the current User id + * @param parent parent node + * @param name name of the property + * @throws RepositoryException if a repository error occurs + */ + private void setCurrentUser(Node parent, String name) + throws RepositoryException { + removePropertyIfExists(parent, name); + ctx.getChangeLog().onModified( + parent.setProperty(name, parent.getSession().getUserID()).getPath() + ); + } + + /** + * Removes the property with the given name from the parent node if it + * exists and if it's not a mandatory property. + * + * @param parent the parent node + * @param name the name of the property to remove + * @return path of the property that was removed or null if + * it was not removed + * @throws RepositoryException if a repository error occurs. + */ + private String removePropertyIfExists(Node parent, String name) + throws RepositoryException { + if (parent.hasProperty(name)) { + Property prop = parent.getProperty(name); + if (!prop.getDefinition().isMandatory()) { + String path = prop.getPath(); + prop.remove(); + return path; + } + } + return null; + } + + /** + * set property without processing, except for type hints + * + * @param parent the parent node + * @param prop the request property + * @throws RepositoryException if a repository error occurs. + */ + private void setPropertyAsIs(Node parent, RequestProperty prop) + throws RepositoryException { + + // no explicit typehint + int type = PropertyType.STRING; + if (prop.getTypeHint() != null) { + try { + type = PropertyType.valueFromName(prop.getTypeHint()); + } catch (Exception e) { + // ignore + } + } + + String[] values = prop.getStringValues(); + if (values == null) { + // remove property + ctx.getChangeLog().onDeleted( + removePropertyIfExists(parent, prop.getName()) + ); + } else if (values.length == 0) { + // do not create new prop here, but clear existing + if (parent.hasProperty(prop.getName())) { + ctx.getChangeLog().onModified( + parent.setProperty(prop.getName(), "").getPath() + ); + } + } else if (values.length == 1) { + removePropertyIfExists(parent, prop.getName()); + ctx.getChangeLog().onModified( + parent.setProperty(prop.getName(), values[0], type).getPath() + ); + } else { + removePropertyIfExists(parent, prop.getName()); + ctx.getChangeLog().onModified( + parent.setProperty(prop.getName(), values, type).getPath() + ); + } + } + +} Added: incubator/sling/trunk/launchpad/launchpad-servlets/src/main/resources/org/apache/sling/ujax/UjaxHtmlResponse.html URL: http://svn.apache.org/viewvc/incubator/sling/trunk/launchpad/launchpad-servlets/src/main/resources/org/apache/sling/ujax/UjaxHtmlResponse.html?rev=618397&view=auto ============================================================================== --- incubator/sling/trunk/launchpad/launchpad-servlets/src/main/resources/org/apache/sling/ujax/UjaxHtmlResponse.html (added) +++ incubator/sling/trunk/launchpad/launchpad-servlets/src/main/resources/org/apache/sling/ujax/UjaxHtmlResponse.html Mon Feb 4 11:43:47 2008 @@ -0,0 +1,61 @@ + + + + + + ${title} + + +

${title}

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Status
${status.code}
Message
${status.message}
Location${location}
Parent Location${parentLocation}
Path
${path}
Referer${referer}
ChangeLog
${changeLog}
+

Go Back

+

Modified Resource

+

Parent of Modified Resource

+ + \ No newline at end of file