Added: 
incubator/sling/trunk/servlets/post/src/main/java/org/apache/sling/servlets/post/impl/operations/ModifyOperation.java
URL: 
http://svn.apache.org/viewvc/incubator/sling/trunk/servlets/post/src/main/java/org/apache/sling/servlets/post/impl/operations/ModifyOperation.java?rev=656302&view=auto
==============================================================================
--- 
incubator/sling/trunk/servlets/post/src/main/java/org/apache/sling/servlets/post/impl/operations/ModifyOperation.java
 (added)
+++ 
incubator/sling/trunk/servlets/post/src/main/java/org/apache/sling/servlets/post/impl/operations/ModifyOperation.java
 Wed May 14 07:49:38 2008
@@ -0,0 +1,430 @@
+/*
+ * 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.servlets.post.impl.operations;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.servlet.ServletContext;
+
+import org.apache.sling.api.SlingException;
+import org.apache.sling.api.SlingHttpServletRequest;
+import org.apache.sling.api.request.RequestParameter;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ResourceUtil;
+import org.apache.sling.api.servlets.HtmlResponse;
+import org.apache.sling.servlets.post.AbstractSlingPostOperation;
+import org.apache.sling.servlets.post.SlingPostConstants;
+import org.apache.sling.servlets.post.impl.helper.DateParser;
+import org.apache.sling.servlets.post.impl.helper.NodeNameGenerator;
+import org.apache.sling.servlets.post.impl.helper.RequestProperty;
+import org.apache.sling.servlets.post.impl.helper.SlingFileUploadHandler;
+import org.apache.sling.servlets.post.impl.helper.SlingPropertyValueHandler;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class ModifyOperation extends AbstractSlingPostOperation {
+
+    /** default log */
+    private final Logger log = LoggerFactory.getLogger(getClass());
+
+    /**
+     * utility class for generating node names
+     */
+    private final NodeNameGenerator nodeNameGenerator;
+
+    private final DateParser dateParser;
+
+    /**
+     * handler that deals with file upload
+     */
+    private final SlingFileUploadHandler uploadHandler;
+
+    public ModifyOperation(NodeNameGenerator nodeNameGenerator,
+            DateParser dateParser, ServletContext servletContext) {
+        this.nodeNameGenerator = nodeNameGenerator;
+        this.dateParser = dateParser;
+        this.uploadHandler = new SlingFileUploadHandler(servletContext);
+    }
+
+    @Override
+    public void doRun(SlingHttpServletRequest request, HtmlResponse response)
+            throws RepositoryException {
+
+        Map<String, RequestProperty> reqProperties = collectContent(request,
+            response);
+
+        // do not change order unless you have a very good reason.
+        Session session = request.getResourceResolver().adaptTo(Session.class);
+
+        processCreate(session, reqProperties, response);
+        writeContent(session, reqProperties, response);
+
+        String path = response.getPath();
+        orderNode(request, session.getItem(path));
+    }
+
+    @Override
+    protected String getItemPath(SlingHttpServletRequest request) {
+
+        // calculate the paths
+        StringBuffer rootPathBuf = new StringBuffer();
+        String suffix;
+        Resource currentResource = request.getResource();
+        if (ResourceUtil.isSyntheticResource(currentResource)) {
+
+            // 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
+        boolean doGenerateName = false;
+        if (suffix != null) {
+
+            // cut off any selectors/extension from the suffix
+            int dotPos = suffix.indexOf('.');
+            if (dotPos > 0) {
+                suffix = suffix.substring(0, dotPos);
+            }
+
+            // and check whether it is a create request (trailing /)
+            if (suffix.endsWith(SlingPostConstants.DEFAULT_CREATE_SUFFIX)) {
+                suffix = suffix.substring(0, suffix.length()
+                    - SlingPostConstants.DEFAULT_CREATE_SUFFIX.length());
+                doGenerateName = true;
+
+                // or with the star suffix /*
+            } else if (suffix.endsWith(SlingPostConstants.STAR_CREATE_SUFFIX)) 
{
+                suffix = suffix.substring(0, suffix.length()
+                    - SlingPostConstants.STAR_CREATE_SUFFIX.length());
+                doGenerateName = true;
+            }
+
+            // append the remains of the suffix to the path buffer
+            rootPathBuf.append(suffix);
+
+        }
+
+        String path = rootPathBuf.toString();
+
+        if (doGenerateName) {
+            try {
+                path = generateName(request, path);
+            } catch (RepositoryException re) {
+                throw new SlingException("Failed to generate name", re);
+            }
+        }
+
+        return path;
+    }
+
+    private String generateName(SlingHttpServletRequest request, String 
basePath)
+            throws RepositoryException {
+
+        // If the path ends with a *, create a node under its parent, with
+        // a generated node name
+        basePath += "/"
+            + nodeNameGenerator.getNodeName(request.getRequestParameterMap(),
+                getSavePrefix(request));
+
+        // if resulting path exists, add a suffix until it's not the case
+        // anymore
+        Session session = request.getResourceResolver().adaptTo(Session.class);
+
+        // if resulting path exists, add a suffix until it's not the case
+        // anymore
+        if (session.itemExists(basePath)) {
+            for (int idx = 0; idx < 1000; idx++) {
+                String newPath = basePath + "_" + idx;
+                if (!session.itemExists(newPath)) {
+                    basePath = newPath;
+                    break;
+                }
+            }
+        }
+
+        // if it still exists there are more than 1000 nodes ?
+        if (session.itemExists(basePath)) {
+            throw new RepositoryException(
+                "Collision in generated node names for path=" + basePath);
+        }
+
+        return basePath;
+    }
+
+    /**
+     * Create node(s) according to current request
+     * 
+     * @throws RepositoryException if a repository error occurs
+     * @throws ServletException if an internal error occurs
+     */
+    private void processCreate(Session session,
+            Map<String, RequestProperty> reqProperties, HtmlResponse response)
+            throws RepositoryException {
+
+        String path = response.getPath();
+        if (!session.itemExists(path)) {
+            deepGetOrCreateNode(session, path, reqProperties, response);
+            response.setCreateRequest(true);
+        }
+
+    }
+
+    /**
+     * Writes back the content
+     * 
+     * @throws RepositoryException if a repository error occurs
+     * @throws ServletException if an internal error occurs
+     */
+    private void writeContent(Session session,
+            Map<String, RequestProperty> reqProperties, HtmlResponse response)
+            throws RepositoryException {
+
+        SlingPropertyValueHandler propHandler = new SlingPropertyValueHandler(
+            dateParser, response);
+
+        for (RequestProperty prop : reqProperties.values()) {
+            Node parent = deepGetOrCreateNode(session, prop.getParentPath(),
+                reqProperties, response);
+            // skip jcr special properties
+            if (prop.getName().equals("jcr:primaryType")
+                || prop.getName().equals("jcr:mixinTypes")) {
+                continue;
+            }
+            if (prop.isFileUpload()) {
+                uploadHandler.setFile(parent, prop, response);
+            } else {
+                propHandler.setProperty(parent, prop);
+            }
+        }
+    }
+
+    /**
+     * Collects the properties that form the content to be written back to the
+     * repository.
+     * 
+     * @throws RepositoryException if a repository error occurs
+     * @throws ServletException if an internal error occurs
+     */
+    private Map<String, RequestProperty> collectContent(
+            SlingHttpServletRequest request, HtmlResponse response) {
+
+        String savePrefix = getSavePrefix(request);
+
+        // walk the request parameters and collect the properties
+        Map<String, RequestProperty> reqProperties = new HashMap<String, 
RequestProperty>();
+        for (Map.Entry<String, RequestParameter[]> e : 
request.getRequestParameterMap().entrySet()) {
+            final String paramName = e.getKey();
+
+            // do not store parameters with names starting with sling:post
+            if (paramName.startsWith(SlingPostConstants.RP_PREFIX)) {
+                continue;
+            }
+            // ignore field with a '@TypeHint' suffix. this is dealt with later
+            if (paramName.endsWith(SlingPostConstants.TYPE_HINT_SUFFIX)) {
+                continue;
+            }
+            // ignore field with a '@DefaultValue' suffix. this is dealt with
+            // later
+            if (paramName.endsWith(SlingPostConstants.DEFAULT_VALUE_SUFFIX)) {
+                continue;
+            }
+            // SLING-298: skip form encoding parameter
+            if (paramName.equals("_charset_")) {
+                continue;
+            }
+            // skip parameters that do not start with the save prefix
+            if (!paramName.startsWith(savePrefix)) {
+                continue;
+            }
+            String propertyName = paramName.substring(savePrefix.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(SlingPostConstants.VALUE_FROM_SUFFIX);
+            if (vfIndex >= 0) {
+                // @ValueFrom example:
+                // <input name="./[EMAIL PROTECTED]" type="hidden" 
value="fulltext"
+                // />
+                // 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 add it to the list
+            String propPath = propertyName;
+            if (!propPath.startsWith("/")) {
+                propPath = response.getPath() + "/" + propertyName;
+            }
+            RequestProperty prop = new RequestProperty(propPath, values);
+
+            // @TypeHint example
+            // <input type="text" name="./age" />
+            // <input type="hidden" name="./[EMAIL PROTECTED]" value="long" />
+            // causes the setProperty using the 'long' property type
+            final String thName = savePrefix + propertyName
+                + SlingPostConstants.TYPE_HINT_SUFFIX;
+            final RequestParameter rp = request.getRequestParameter(thName);
+            if (rp != null) {
+                prop.setTypeHint(rp.getString());
+            }
+
+            // @DefaultValue
+            final String dvName = savePrefix + propertyName
+                + SlingPostConstants.DEFAULT_VALUE_SUFFIX;
+            prop.setDefaultValues(request.getRequestParameters(dvName));
+
+            reqProperties.put(propPath, prop);
+        }
+
+        return reqProperties;
+    }
+
+    /**
+     * Checks the collected content for a jcr:primaryType property at the
+     * specified path.
+     * 
+     * @param path path to check
+     * @return the primary type or <code>null</code>
+     */
+    private String getPrimaryType(Map<String, RequestProperty> reqProperties,
+            String path) {
+        RequestProperty prop = reqProperties.get(path + "/jcr:primaryType");
+        return prop == null ? null : prop.getStringValues()[0];
+    }
+
+    /**
+     * Checks the collected content for a jcr:mixinTypes property at the
+     * specified path.
+     * 
+     * @param path path to check
+     * @return the mixin types or <code>null</code>
+     */
+    private String[] getMixinTypes(Map<String, RequestProperty> reqProperties,
+            String path) {
+        RequestProperty prop = reqProperties.get(path + "/jcr:mixinTypes");
+        return prop == null ? null : prop.getStringValues();
+    }
+
+    /**
+     * Deep gets or creates a node, parent-padding with default nodes nodes. If
+     * the path is empty, the given parent node is returned.
+     * 
+     * @param path path to node that needs to be deep-created
+     * @return node at path
+     * @throws RepositoryException if an error occurs
+     * @throws IllegalArgumentException if the path is relative and parent is
+     *             <code>null</code>
+     */
+    private Node deepGetOrCreateNode(Session session, String path,
+            Map<String, RequestProperty> reqProperties, HtmlResponse response)
+            throws RepositoryException {
+        if (log.isDebugEnabled()) {
+            log.debug("Deep-creating Node '{}'", path);
+        }
+        if (path == null || !path.startsWith("/")) {
+            throw new IllegalArgumentException("path must be an absolute 
path.");
+        }
+        // get the starting node
+        String startingNodePath = path;
+        Node startingNode = null;
+        while (startingNode == null) {
+            if (startingNodePath.equals("/")) {
+                startingNode = session.getRootNode();
+            } else if (session.itemExists(startingNodePath)) {
+                startingNode = (Node) session.getItem(startingNodePath);
+            } else {
+                int pos = startingNodePath.lastIndexOf('/');
+                if (pos > 0) {
+                    startingNodePath = startingNodePath.substring(0, pos);
+                } else {
+                    startingNodePath = "/";
+                }
+            }
+        }
+        // is the searched node already existing?
+        if (startingNodePath.length() == path.length()) {
+            return startingNode;
+        }
+        // create nodes
+        int from = (startingNodePath.length() == 1
+                ? 1
+                : startingNodePath.length() + 1);
+        Node node = startingNode;
+        while (from > 0) {
+            final int to = path.indexOf('/', from);
+            final String name = to < 0 ? path.substring(from) : path.substring(
+                from, to);
+            // although the node should not exist (according to the first test
+            // above)
+            // we do a sanety check.
+            if (node.hasNode(name)) {
+                node = node.getNode(name);
+            } else {
+                final String tmpPath = to < 0 ? path : path.substring(0, to);
+                // check for node type
+                final String nodeType = getPrimaryType(reqProperties, tmpPath);
+                if (nodeType != null) {
+                    node = node.addNode(name, nodeType);
+                } else {
+                    node = node.addNode(name);
+                }
+                // check for mixin types
+                final String[] mixinTypes = getMixinTypes(reqProperties,
+                    tmpPath);
+                if (mixinTypes != null) {
+                    for (String mix : mixinTypes) {
+                        node.addMixin(mix);
+                    }
+                }
+                response.onCreated(node.getPath());
+            }
+            from = to + 1;
+        }
+        return node;
+    }
+
+}

Added: 
incubator/sling/trunk/servlets/post/src/main/java/org/apache/sling/servlets/post/impl/operations/MoveOperation.java
URL: 
http://svn.apache.org/viewvc/incubator/sling/trunk/servlets/post/src/main/java/org/apache/sling/servlets/post/impl/operations/MoveOperation.java?rev=656302&view=auto
==============================================================================
--- 
incubator/sling/trunk/servlets/post/src/main/java/org/apache/sling/servlets/post/impl/operations/MoveOperation.java
 (added)
+++ 
incubator/sling/trunk/servlets/post/src/main/java/org/apache/sling/servlets/post/impl/operations/MoveOperation.java
 Wed May 14 07:49:38 2008
@@ -0,0 +1,50 @@
+/*
+ * 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.servlets.post.impl.operations;
+
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+
+import org.apache.sling.api.servlets.HtmlResponse;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Holds various states and encapsulates methods that are needed to handle a
+ * post request.
+ */
+public class MoveOperation extends AbstractCopyMoveOperation {
+
+    /**
+     * default log
+     */
+    private static final Logger log = 
LoggerFactory.getLogger(MoveOperation.class);
+
+    @Override
+    protected String getOperationName() {
+        return "move";
+    }
+    
+    @Override
+    protected void execute(HtmlResponse response, Session session,
+            String source, String dest) throws RepositoryException {
+        
+        session.move(source, dest);
+        response.onMoved(source, dest);
+        log.debug("moved {} to {}", source, dest);
+    }
+}

Modified: 
incubator/sling/trunk/servlets/post/src/test/java/org/apache/sling/servlets/post/impl/NodeNameFilterTest.java
URL: 
http://svn.apache.org/viewvc/incubator/sling/trunk/servlets/post/src/test/java/org/apache/sling/servlets/post/impl/NodeNameFilterTest.java?rev=656302&r1=656301&r2=656302&view=diff
==============================================================================
--- 
incubator/sling/trunk/servlets/post/src/test/java/org/apache/sling/servlets/post/impl/NodeNameFilterTest.java
 (original)
+++ 
incubator/sling/trunk/servlets/post/src/test/java/org/apache/sling/servlets/post/impl/NodeNameFilterTest.java
 Wed May 14 07:49:38 2008
@@ -18,6 +18,8 @@
 
 import junit.framework.TestCase;
 
+import org.apache.sling.servlets.post.impl.helper.NodeNameFilter;
+
 public class NodeNameFilterTest extends TestCase {
     private final NodeNameFilter filter = new NodeNameFilter();
     

Added: 
incubator/sling/trunk/servlets/post/src/test/java/org/apache/sling/servlets/post/impl/SlingPostServletTest.java
URL: 
http://svn.apache.org/viewvc/incubator/sling/trunk/servlets/post/src/test/java/org/apache/sling/servlets/post/impl/SlingPostServletTest.java?rev=656302&view=auto
==============================================================================
--- 
incubator/sling/trunk/servlets/post/src/test/java/org/apache/sling/servlets/post/impl/SlingPostServletTest.java
 (added)
+++ 
incubator/sling/trunk/servlets/post/src/test/java/org/apache/sling/servlets/post/impl/SlingPostServletTest.java
 Wed May 14 07:49:38 2008
@@ -0,0 +1,79 @@
+/*
+ * 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.servlets.post.impl;
+
+import junit.framework.TestCase;
+
+import org.apache.sling.commons.testing.sling.MockSlingHttpServletRequest;
+import org.apache.sling.servlets.post.SlingPostConstants;
+
+public class SlingPostServletTest extends TestCase {
+
+    public void testIsSetStatus() {
+        StatusParamSlingHttpServletRequest req = new 
StatusParamSlingHttpServletRequest();
+        SlingPostServlet servlet = new SlingPostServlet();
+
+        // 1. null parameter, expect true
+        req.setStatusParam(null);
+        assertTrue("Standard status expected for null param",
+            servlet.isSetStatus(req));
+
+        // 2. "standard" parameter, expect true
+        req.setStatusParam(SlingPostConstants.STATUS_VALUE_STANDARD);
+        assertTrue("Standard status expected for '"
+            + SlingPostConstants.STATUS_VALUE_STANDARD + "' param",
+            servlet.isSetStatus(req));
+
+        // 3. "browser" parameter, expect false
+        req.setStatusParam(SlingPostConstants.STATUS_VALUE_BROWSER);
+        assertFalse("Browser status expected for '"
+            + SlingPostConstants.STATUS_VALUE_BROWSER + "' param",
+            servlet.isSetStatus(req));
+
+        // 4. any parameter, expect true
+        String param = "knocking on heaven's door";
+        req.setStatusParam(param);
+        assertTrue("Standard status expected for '" + param + "' param",
+            servlet.isSetStatus(req));
+    }
+
+    private static class StatusParamSlingHttpServletRequest extends
+            MockSlingHttpServletRequest {
+
+        private String statusParam;
+
+        public StatusParamSlingHttpServletRequest() {
+            // nothing to setup, we don't care
+            super(null, null, null, null, null);
+        }
+
+        @Override
+        public String getParameter(String name) {
+            if (SlingPostConstants.RP_STATUS.equals(name)) {
+                return statusParam;
+            }
+
+            return super.getParameter(name);
+        }
+
+        void setStatusParam(String statusParam) {
+            this.statusParam = statusParam;
+        }
+    }
+}


Reply via email to