Move Sling to new TLP location

git-svn-id: https://svn.eu.apache.org/repos/asf/sling/tags/org.apache.sling.servlets.post-2.0.2-incubator@785979 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..d645695
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,202 @@
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright [yyyy] [name of copyright owner]
+
+   Licensed 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.
diff --git a/NOTICE b/NOTICE
new file mode 100644
index 0000000..d26b355
--- /dev/null
+++ b/NOTICE
@@ -0,0 +1,8 @@
+Apache Sling Default Post Servlets
+Copyright 2008 The Apache Software Foundation
+
+Apache Sling is based on source code originally developed 
+by Day Software (http://www.day.com/).
+
+This product includes software developed at
+The Apache Software Foundation (http://www.apache.org/).
diff --git a/README.txt b/README.txt
new file mode 100644
index 0000000..c9155b5
--- /dev/null
+++ b/README.txt
@@ -0,0 +1,37 @@
+Apache Sling Default Post Servlets
+
+Provides default POST servlets.
+
+Disclaimer
+==========
+Apache Sling is an effort undergoing incubation at The Apache Software Foundation (ASF),
+sponsored by the Apache Jackrabbit PMC. Incubation is required of all newly accepted
+projects until a further review indicates that the infrastructure, communications,
+and decision making process have stabilized in a manner consistent with other
+successful ASF projects. While incubation status is not necessarily a reflection of
+the completeness or stability of the code, it does indicate that the project has yet
+to be fully endorsed by the ASF.
+
+Getting Started
+===============
+
+This component uses a Maven 2 (http://maven.apache.org/) build
+environment. It requires a Java 5 JDK (or higher) and Maven (http://maven.apache.org/)
+2.0.7 or later. We recommend to use the latest Maven version.
+
+If you have Maven 2 installed, you can compile and
+package the jar using the following command:
+
+    mvn package
+
+See the Maven 2 documentation for other build features.
+
+The latest source code for this component is available in the
+Subversion (http://subversion.tigris.org/) source repository of
+the Apache Software Foundation. If you have Subversion installed,
+you can checkout the latest source using the following command:
+
+    svn checkout http://svn.apache.org/repos/asf/incubator/sling/trunk/servlets/post
+
+See the Subversion documentation for other source control features.
+
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..1c59ee5
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,147 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+    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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>org.apache.sling</groupId>
+        <artifactId>sling</artifactId>
+        <version>3-incubator</version>
+        <relativePath>../../parent/pom.xml</relativePath>
+    </parent>
+
+    <artifactId>org.apache.sling.servlets.post</artifactId>
+    <packaging>bundle</packaging>
+    <version>2.0.2-incubator</version>
+
+    <name>Sling - Default POST Servlets</name>
+    <description>
+        Provides default POST servlets.
+    </description>
+
+    <scm>
+        <connection>scm:svn:http://svn.apache.org/repos/asf/incubator/sling/tags/org.apache.sling.servlets.post-2.0.2-incubator</connection>
+        <developerConnection>scm:svn:https://svn.apache.org/repos/asf/incubator/sling/tags/org.apache.sling.servlets.post-2.0.2-incubator</developerConnection>
+        <url>http://svn.apache.org/viewvc/incubator/sling/tags/org.apache.sling.servlets.post-2.0.2-incubator</url>
+    </scm>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.felix</groupId>
+                <artifactId>maven-scr-plugin</artifactId>
+            </plugin>
+
+            <plugin>
+                <groupId>org.apache.felix</groupId>
+                <artifactId>maven-bundle-plugin</artifactId>
+                <extensions>true</extensions>
+                <configuration>
+                    <instructions>
+                        <Export-Package>
+                            org.apache.sling.servlets.post;version=${pom.version}
+                        </Export-Package>
+                        <Private-Package>
+                            org.apache.sling.servlets.post.impl.*
+                        </Private-Package>
+                        <Sling-Bundle-Resources>
+                            /system/sling.js
+                        </Sling-Bundle-Resources>
+                    </instructions>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+    <reporting>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-javadoc-plugin</artifactId>
+                <configuration>
+                  <!-- No javadocs -->
+                    <excludePackageNames>
+                        org.apache.sling.servlets.post.impl
+                    </excludePackageNames>
+                </configuration>
+            </plugin>
+        </plugins>
+    </reporting>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.felix</groupId>
+            <artifactId>org.osgi.core</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.felix</groupId>
+            <artifactId>org.osgi.compendium</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>javax.servlet</groupId>
+            <artifactId>servlet-api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.sling</groupId>
+            <artifactId>org.apache.sling.api</artifactId>
+            <version>2.0.2-incubator</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.sling</groupId>
+            <artifactId>org.apache.sling.jcr.api</artifactId>
+            <version>2.0.2-incubator</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.sling</groupId>
+            <artifactId>org.apache.sling.jcr.resource</artifactId>
+            <version>2.0.2-incubator</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.jackrabbit</groupId>
+            <artifactId>jackrabbit-jcr-commons</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.sling</groupId>
+            <artifactId>org.apache.sling.commons.json</artifactId>
+            <version>2.0.2-incubator</version>
+        </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-simple</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.sling</groupId>
+            <artifactId>org.apache.sling.commons.testing</artifactId>
+            <version>2.0.2-incubator</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <scope>test</scope>
+        </dependency>
+        
+    </dependencies>
+
+</project>
+
diff --git a/src/main/java/org/apache/sling/servlets/post/AbstractSlingPostOperation.java b/src/main/java/org/apache/sling/servlets/post/AbstractSlingPostOperation.java
new file mode 100644
index 0000000..ddb22eb
--- /dev/null
+++ b/src/main/java/org/apache/sling/servlets/post/AbstractSlingPostOperation.java
@@ -0,0 +1,365 @@
+/*
+ * 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;
+
+import java.util.Enumeration;
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+
+import javax.jcr.Item;
+import javax.jcr.Node;
+import javax.jcr.NodeIterator;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+
+import org.apache.sling.api.SlingHttpServletRequest;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.api.resource.ResourceUtil;
+import org.apache.sling.api.servlets.HtmlResponse;
+import org.apache.sling.api.wrappers.SlingRequestPaths;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Holds various states and encapsulates methods that are needed to handle a
+ * post request.
+ */
+public abstract class AbstractSlingPostOperation implements SlingPostOperation {
+
+    /**
+     * default log
+     */
+    private final Logger log = LoggerFactory.getLogger(getClass());
+
+    /**
+     * Prepares and finalizes the actual operation. Preparation encompasses
+     * getting the absolute path of the item to operate on by calling the
+     * {@link #getItemPath(SlingHttpServletRequest)} method and setting the
+     * location and parent location on the response. After the operation has
+     * been done in the {@link #doRun(SlingHttpServletRequest, HtmlResponse)}
+     * method the session is saved if there are unsaved modifications. In case
+     * of errorrs, the unsaved changes in the session are rolled back.
+     *
+     * @param request the request to operate on
+     * @param response The <code>HtmlResponse</code> to record execution
+     *            progress.
+     */
+    public final void run(SlingHttpServletRequest request, HtmlResponse response) {
+
+        // calculate the paths
+        String path = getItemPath(request);
+        response.setPath(path);
+
+        // location
+        response.setLocation(externalizePath(request, path));
+
+        // parent location
+        path = ResourceUtil.getParent(path);
+        response.setParentLocation(externalizePath(request, path));
+
+        Session session = request.getResourceResolver().adaptTo(Session.class);
+
+        try {
+
+            doRun(request, response);
+
+            if (session.hasPendingChanges()) {
+                session.save();
+            }
+
+        } catch (Exception e) {
+
+            log.error("Exception during response processing.", e);
+            response.setError(e);
+
+        } finally {
+            try {
+                if (session.hasPendingChanges()) {
+                    session.refresh(false);
+                }
+            } catch (RepositoryException e) {
+                log.warn("RepositoryException in finally block: {}",
+                    e.getMessage(), e);
+            }
+        }
+
+    }
+
+    /**
+     * Returns the path of the resource of the request as the item path.
+     * <p>
+     * This method may be overwritten by extension if the operation has
+     * different requirements on path processing.
+     */
+    protected String getItemPath(SlingHttpServletRequest request) {
+        return request.getResource().getPath();
+    }
+
+    protected abstract void doRun(SlingHttpServletRequest request,
+            HtmlResponse response) throws RepositoryException;
+
+    /**
+     * Returns an iterator on <code>Resource</code> instances addressed in the
+     * {@link SlingPostConstants#RP_APPLY_TO} request parameter. If the request
+     * parameter is not set, <code>null</code> is returned. If the parameter
+     * is set with valid resources an empty iterator is returned. Any resources
+     * addressed in the {@link SlingPostConstants#RP_APPLY_TO} parameter is
+     * ignored.
+     *
+     * @param request The <code>SlingHttpServletRequest</code> object used to
+     *            get the {@link SlingPostConstants#RP_APPLY_TO} parameter.
+     * @return The iterator of resources listed in the parameter or
+     *         <code>null</code> if the parameter is not set in the request.
+     */
+    protected Iterator<Resource> getApplyToResources(
+            SlingHttpServletRequest request) {
+
+        String[] applyTo = request.getParameterValues(SlingPostConstants.RP_APPLY_TO);
+        if (applyTo == null) {
+            return null;
+        }
+
+        return new ApplyToIterator(request, applyTo);
+    }
+
+    /**
+     * Returns an external form of the given path prepending the context path
+     * and appending a display extension.
+     *
+     * @param path the path to externalize
+     * @return the url
+     */
+    protected final String externalizePath(SlingHttpServletRequest request,
+            String path) {
+        StringBuffer ret = new StringBuffer();
+        ret.append(SlingRequestPaths.getContextPath(request));
+        ret.append(request.getResourceResolver().map(path));
+
+        // append optional extension
+        String ext = request.getParameter(SlingPostConstants.RP_DISPLAY_EXTENSION);
+        if (ext != null && ext.length() > 0) {
+            if (ext.charAt(0) != '.') {
+                ret.append('.');
+            }
+            ret.append(ext);
+        }
+
+        return ret.toString();
+    }
+
+    /**
+     * Resolves the given path with respect to the current root path.
+     *
+     * @param relPath the path to resolve
+     * @return the given path if it starts with a '/'; a resolved path
+     *         otherwise.
+     */
+    protected final String resolvePath(String absPath, String relPath) {
+        if (relPath.startsWith("/")) {
+            return relPath;
+        }
+        return absPath + "/" + relPath;
+    }
+
+    /**
+     * Returns true if any of the request parameters starts with
+     * {@link SlingPostConstants#ITEM_PREFIX_RELATIVE_CURRENT <code>./</code>}.
+     * In this case only parameters starting with either of the prefixes
+     * {@link SlingPostConstants#ITEM_PREFIX_RELATIVE_CURRENT <code>./</code>},
+     * {@link SlingPostConstants#ITEM_PREFIX_RELATIVE_PARENT <code>../</code>}
+     * and {@link SlingPostConstants#ITEM_PREFIX_ABSOLUTE <code>/</code>} are
+     * considered as providing content to be stored. Otherwise all parameters
+     * not starting with the command prefix <code>:</code> are considered as
+     * parameters to be stored.
+     */
+    protected final boolean requireItemPathPrefix(
+            SlingHttpServletRequest request) {
+
+        boolean requirePrefix = false;
+
+        Enumeration<?> names = request.getParameterNames();
+        while (names.hasMoreElements() && !requirePrefix) {
+            String name = (String) names.nextElement();
+            requirePrefix = name.startsWith(SlingPostConstants.ITEM_PREFIX_RELATIVE_CURRENT);
+        }
+
+        return requirePrefix;
+    }
+
+    /**
+     * Returns <code>true</code> if the <code>name</code> starts with either
+     * of the prefixes
+     * {@link SlingPostConstants#ITEM_PREFIX_RELATIVE_CURRENT <code>./</code>},
+     * {@link SlingPostConstants#ITEM_PREFIX_RELATIVE_PARENT <code>../</code>}
+     * and {@link SlingPostConstants#ITEM_PREFIX_ABSOLUTE <code>/</code>}.
+     */
+    protected boolean hasItemPathPrefix(String name) {
+        return name.startsWith(SlingPostConstants.ITEM_PREFIX_ABSOLUTE)
+            || name.startsWith(SlingPostConstants.ITEM_PREFIX_RELATIVE_CURRENT)
+            || name.startsWith(SlingPostConstants.ITEM_PREFIX_RELATIVE_PARENT);
+    }
+
+    /**
+     * Orders the given node according to the specified command. The following
+     * syntax is supported: <xmp> | first | before all child nodes | before A |
+     * before child node A | after A | after child node A | last | after all
+     * nodes | N | at a specific position, N being an integer </xmp>
+     *
+     * @param item node to order
+     * @throws RepositoryException if an error occurs
+     */
+    protected void orderNode(SlingHttpServletRequest request, Item item)
+            throws RepositoryException {
+
+        String command = request.getParameter(SlingPostConstants.RP_ORDER);
+        if (command == null || command.length() == 0) {
+            // nothing to do
+            return;
+        }
+
+        if (!item.isNode()) {
+            return;
+        }
+
+        Node parent = item.getParent();
+
+        String next = null;
+        if (command.equals(SlingPostConstants.ORDER_FIRST)) {
+
+            next = parent.getNodes().nextNode().getName();
+
+        } else if (command.equals(SlingPostConstants.ORDER_LAST)) {
+
+            next = "";
+
+        } else if (command.startsWith(SlingPostConstants.ORDER_BEFORE)) {
+
+            next = command.substring(SlingPostConstants.ORDER_BEFORE.length());
+
+        } else if (command.startsWith(SlingPostConstants.ORDER_AFTER)) {
+
+            String name = command.substring(SlingPostConstants.ORDER_AFTER.length());
+            NodeIterator iter = parent.getNodes();
+            while (iter.hasNext()) {
+                Node n = iter.nextNode();
+                if (n.getName().equals(name)) {
+                    if (iter.hasNext()) {
+                        next = iter.nextNode().getName();
+                    } else {
+                        next = "";
+                    }
+                }
+            }
+
+        } else {
+            // check for integer
+            try {
+                // 01234
+                // abcde move a -> 2 (above 3)
+                // bcade move a -> 1 (above 1)
+                // bacde
+                int newPos = Integer.parseInt(command);
+                next = "";
+                NodeIterator iter = parent.getNodes();
+                while (iter.hasNext() && newPos >= 0) {
+                    Node n = iter.nextNode();
+                    if (n.getName().equals(item.getName())) {
+                        // if old node is found before index, need to
+                        // inc index
+                        newPos++;
+                    }
+                    if (newPos == 0) {
+                        next = n.getName();
+                        break;
+                    }
+                    newPos--;
+                }
+            } catch (NumberFormatException e) {
+                throw new IllegalArgumentException(
+                    "provided node ordering command is invalid: " + command);
+            }
+        }
+
+        if (next != null) {
+            if (next.equals("")) {
+                next = null;
+            }
+            parent.orderBefore(item.getName(), next);
+            if (log.isDebugEnabled()) {
+                log.debug("Node {} moved '{}'", item.getPath(), command);
+            }
+        } else {
+            throw new IllegalArgumentException(
+                "provided node ordering command is invalid: " + command);
+        }
+    }
+
+    private static class ApplyToIterator implements Iterator<Resource> {
+
+        private final ResourceResolver resolver;
+        private final Resource baseResource;
+        private final String[] paths;
+
+        private int pathIndex;
+
+        private Resource nextResource;
+
+        ApplyToIterator(SlingHttpServletRequest request, String[] paths) {
+            this.resolver = request.getResourceResolver();
+            this.baseResource = request.getResource();
+            this.paths = paths;
+            this.pathIndex = 0;
+
+            nextResource = seek();
+        }
+
+        public boolean hasNext() {
+            return nextResource != null;
+        }
+
+        public Resource next() {
+            if (!hasNext()) {
+                throw new NoSuchElementException();
+            }
+
+            Resource result = nextResource;
+            nextResource = seek();
+
+            return result;
+        }
+
+        public void remove() {
+            throw new UnsupportedOperationException();
+        }
+
+        private Resource seek() {
+            while (pathIndex < paths.length) {
+                String path = paths[pathIndex];
+                pathIndex++;
+
+                Resource res = resolver.getResource(baseResource, path);
+                if (res != null) {
+                    return res;
+                }
+            }
+
+            // no more elements in the array
+            return null;
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/org/apache/sling/servlets/post/SlingPostConstants.java b/src/main/java/org/apache/sling/servlets/post/SlingPostConstants.java
new file mode 100644
index 0000000..3b123f2
--- /dev/null
+++ b/src/main/java/org/apache/sling/servlets/post/SlingPostConstants.java
@@ -0,0 +1,303 @@
+/*
+ * 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;
+
+/**
+ * The <code>SlingPostConstants</code> interface provides constants for well
+ * known parameters of the core SlingPostServlet. Extensions of the servlet
+ * through implementations of the {@link SlingPostOperation} interface may
+ * extend this constants.
+ */
+public interface SlingPostConstants {
+
+    /**
+     * Prefix for parameter names which control this POST (RP_ stands for
+     * "request param") (value is ":"). This prefix must be used on all request
+     * parameters which have significance to POST request processing. Such
+     * parameters will not be used to denote properties to be written to the
+     * repository.
+     */
+    public static final String RP_PREFIX = ":";
+
+    /**
+     * The name of the parameter containing the operation to execute (value is
+     * ":operation"). If this parameter is missing or empty, the request is
+     * assumed to be a request to create new content or to modify existing
+     * content.
+     */
+    public static final String RP_OPERATION = RP_PREFIX + "operation";
+
+    /**
+     * The suffix to the resource path used to indicate to automatically
+     * generate the name of the new item to create during a content creation
+     * request (value is "/").
+     */
+    public static final String DEFAULT_CREATE_SUFFIX = "/";
+
+    /**
+     * An alternative suffix to the resource path used to indicate to
+     * automatically generate the name of the new item to create during a
+     * content creation request (value is "/*").
+     */
+    public static final String STAR_CREATE_SUFFIX = "/*";
+
+    /**
+     * Name of the predefined delete operation (value is "delete").
+     * <p>
+     * The delete operation requires no further request parameters and just
+     * deletes the content addressed by the request.
+     * <p>
+     * If the {@link #RP_APPLY_TO} parameter is set the resources listed in that
+     * parameter are deleted instead of the request resource.
+     */
+    public static final String OPERATION_DELETE = "delete";
+
+    /**
+     * Name of the predefined copy operation (value is "copy").
+     * <p>
+     * The copy operation requires the {@link #RP_DEST} request parameter
+     * denoting the path to copy the content to. In addition the
+     * {@link #RP_ORDER} parameter may be defined to specificy to relative node
+     * order of the destination node. Finally the {@link #RP_REPLACE} parameter
+     * may be set to indicate whether an existing item at the destination should
+     * be replaced or not.
+     * <p>
+     * If the {@link #RP_APPLY_TO} parameter is set the resources listed in that
+     * parameter are copied instead of the request resource.
+     */
+    public static final String OPERATION_COPY = "copy";
+
+    /**
+     * Name of the predefined move operation (value is "move")
+     * <p>
+     * The move operation requires the {@link #RP_DEST} request parameter
+     * denoting the path to move the content to. In addition the
+     * {@link #RP_ORDER} parameter may be defined to specificy to relative node
+     * order of the destination node. Finally the {@link #RP_REPLACE} parameter
+     * may be set to indicate whether an existing item at the destination should
+     * be replaced or not.
+     * <p>
+     * If the {@link #RP_APPLY_TO} parameter is set the resources listed in that
+     * parameter are moved instead of the request resource.
+     */
+    public static final String OPERATION_MOVE = "move";
+
+    /**
+     * Name of the request parameter used to indicate the resource to apply the
+     * operation to (value is ":applyTo").
+     * <p>
+     * This property is used by certain opertaions - namely
+     * {@link #OPERATION_COPY}, {@link #OPERATION_DELETE} and
+     * {@link #OPERATION_MOVE} - to apply the operation to multiple resources
+     * instead of the request resource.
+     */
+    public static final String RP_APPLY_TO = RP_PREFIX + "applyTo";
+
+    /**
+     * Name of the request parameter used to indicate the destination for the
+     * copy and move operations (value is ":dest"). This request parameter is
+     * required by the copy and move operations.
+     */
+    public static final String RP_DEST = RP_PREFIX + "dest";
+
+    /**
+     * Name of the request parameter indicating whether the destination for a
+     * copy or move operation is to be replaced if existing (value is
+     * ":replace"). Copy or move is only possible if the destination exists if
+     * the replace parameter is set to the case-insignificant value true.
+     */
+    public static final String RP_REPLACE = RP_PREFIX + "replace";
+
+    /**
+     * Optional request parameter indicating the order of newly created nodes in
+     * creation, copy and move operation requests (value is ":order").
+     * <p>
+     * The value of this parameter may be {@link #ORDER_FIRST},
+     * {@link #ORDER_BEFORE}, {@link #ORDER_AFTER}, {@link #ORDER_LAST} or a
+     * numberic value indicating the absolute position in the child list of the
+     * parent node.
+     */
+    public static final String RP_ORDER = RP_PREFIX + "order";
+
+    /**
+     * Possible value of the {@link #RP_ORDER} parameter indicating that the
+     * node by moved to the first position amongst its sibblings (value is
+     * "first").
+     */
+    public static final String ORDER_FIRST = "first";
+
+    /**
+     * Possible value of the {@link #RP_ORDER} parameter indicating that the
+     * node by moved immediately before the sibbling whose name is contained in
+     * the {@link #RP_ORDER} parameter (value is "before ").
+     */
+    public static final String ORDER_BEFORE = "before ";
+
+    /**
+     * Possible value of the {@link #RP_ORDER} parameter indicating that the
+     * node by moved immediately after the sibbling whose name is contained in
+     * the {@link #RP_ORDER} parameter (value is "after ").
+     */
+    public static final String ORDER_AFTER = "after ";
+
+    /**
+     * Possible value of the {@link #RP_ORDER} parameter indicating that the
+     * node by moved to the last position amongst its sibblings (value is
+     * "last").
+     */
+    public static final String ORDER_LAST = "last";
+
+    /**
+     * Optional request paramter specifying a node name for a newly created node
+     * (value is ":name").
+     */
+    public static final String RP_NODE_NAME = RP_PREFIX + "name";
+
+    /**
+     * Optional request paramter specifying a node name hint for a newly created
+     * node (value is ":nameHint").
+     */
+    public static final String RP_NODE_NAME_HINT = RP_PREFIX + "nameHint";
+
+    /**
+     * Prefix for properties addressing repository items with an absolute path
+     * (value is "/").
+     * 
+     * @see #ITEM_PREFIX_RELATIVE_CURRENT
+     */
+    public static final String ITEM_PREFIX_ABSOLUTE = "/";
+
+    /**
+     * Prefix for properties addressing repository items with a path relative to
+     * the current request item (value is "./").
+     * <p>
+     * When collecting parameters addressing repository items for modification,
+     * the parameters are first scanned to see whether there is a parameter with
+     * this relative path prefix. If such a parameter exists, the modification
+     * operations only assumes parameters whose name is prefixes with this
+     * prefix or the {@link #ITEM_PREFIX_ABSOLUTE} or the
+     * {@link #ITEM_PREFIX_RELATIVE_PARENT} to be parameters addressing
+     * properties to modify. Otherwise, that is if no parameter starts with this
+     * prefix, all parameters not starting with the
+     * {@link #RP_PREFIX command prefix} are considered addressing properties to
+     * modify.
+     */
+    public static final String ITEM_PREFIX_RELATIVE_CURRENT = "./";
+
+    /**
+     * Prefix for properties addressing repository items with a path relative to
+     * the parent of the request item (value is "../").
+     * 
+     * @see #ITEM_PREFIX_RELATIVE_CURRENT
+     */
+    public static final String ITEM_PREFIX_RELATIVE_PARENT = "../";
+
+    /**
+     * Optional request parameter: redirect to the specified URL after POST
+     */
+    public static final String RP_REDIRECT_TO = RP_PREFIX + "redirect";
+
+    /**
+     * Optional request parameter: define how the response is sent back to the
+     * client. Supported values for this property are
+     * {@link #STATUS_VALUE_BROWSER} and {@link #STATUS_VALUE_STANDARD}. The
+     * default is to assume {@link #STATUS_VALUE_STANDARD} if the parameter is
+     * not set or set to any other value.
+     */
+    public static final String RP_STATUS = RP_PREFIX + "status";
+
+    /**
+     * The supported value for the {@link #RP_STATUS} request parameter
+     * requesting to report success or failure of request processing using
+     * standard HTTP status codes. This value is assumed as the default value
+     * for the {@link #RP_STATUS} parameter if the parameter is missing or not
+     * any of the two supported values.
+     * 
+     * @see #RP_STATUS
+     * @see #STATUS_VALUE_BROWSER
+     */
+    public static final String STATUS_VALUE_STANDARD = "standard";
+
+    /**
+     * The supported value for the {@link #RP_STATUS} request parameter
+     * requesting to not report success or failure of request processing using
+     * standard HTTP status codes but instead alwas set the status to 200/OK and
+     * only report the real success or failure status in the XHTML response.
+     * 
+     * @see #RP_STATUS
+     * @see #STATUS_VALUE_STANDARD
+     */
+    public static final String STATUS_VALUE_BROWSER = "browser";
+
+    /**
+     * 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
+     */
+    public static final String VALUE_FROM_SUFFIX = "@ValueFrom";
+
+    /**
+     * Suffix indicating a type hint for the property (value is "@TypeHint").
+     */
+    public static final String TYPE_HINT_SUFFIX = "@TypeHint";
+
+    /**
+     * Suffix indicating a default value for a property (value is
+     * "@DefaultValue").
+     */
+    public static final String DEFAULT_VALUE_SUFFIX = "@DefaultValue";
+
+    /**
+     * Suffix indicating that the named property is to be removed before
+     * applying any new content (value is "@Delete").
+     */
+    public static final String SUFFIX_DELETE = "@Delete";
+
+    /**
+     * Suffix indicating that the named item is to be set from an item whose
+     * absolute or relative path is given in the parameter's value (value is
+     * "@MoveFrom").
+     * <p>
+     * This suffix is similar to the {@link #VALUE_FROM_SUFFIX} in that the
+     * value for the item is not taken from the request parameter itself but
+     * from somewhere else. In this case the value is set by moving another
+     * repository item (in the same workspace) to the location addressed by the
+     * parameter.
+     */
+    public static final String SUFFIX_MOVE_FROM = "@MoveFrom";
+
+    /**
+     * Suffix indicating that the named item is to be set from an item whose
+     * absolute or relative path is given in the parameter's value (value is
+     * "@CopyFrom").
+     * <p>
+     * This suffix is similar to the {@link #VALUE_FROM_SUFFIX} in that the
+     * value for the item is not taken from the request parameter itself but
+     * from somewhere else. In this case the value is set by copying another
+     * repository item (in the same workspace) to the location addressed by the
+     * parameter.
+     */
+    public static final String SUFFIX_COPY_FROM = "@CopyFrom";
+}
diff --git a/src/main/java/org/apache/sling/servlets/post/SlingPostOperation.java b/src/main/java/org/apache/sling/servlets/post/SlingPostOperation.java
new file mode 100644
index 0000000..bf74058
--- /dev/null
+++ b/src/main/java/org/apache/sling/servlets/post/SlingPostOperation.java
@@ -0,0 +1,77 @@
+/*
+ * 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;
+
+import org.apache.sling.api.SlingHttpServletRequest;
+import org.apache.sling.api.servlets.HtmlResponse;
+
+/**
+ * The <code>SlingPostOperation</code> interface defines the service API to be
+ * implemented by service providers extending the Sling default POST servlet.
+ * Service providers may register OSGi services of this type to be used by the
+ * Sling default POST servlet to handle specific operations.
+ * <p>
+ * The <code>SlingPostOperation</code> service must be registered with a
+ * {@link #PROP_OPERATION_NAME} registration property giving the name(s) of the
+ * operations supported by the service. The names will be used to find the
+ * actual operation from the
+ * {@link SlingPostConstants#RP_OPERATION <code>:operation</code>} request
+ * parameter.
+ * <p>
+ * The Sling default POST servlet defines the <code>copy</code>,
+ * <code>move</code> and <code>delete</code> operation names. These names
+ * should not be used by <code>SlingPostOperation</code> service providers.
+ */
+public interface SlingPostOperation {
+
+    /**
+     * The name of the Sling POST operation service (value is
+     * "org.apache.sling.servlets.post.SlingPostOperation").
+     */
+    public static final String SERVICE_NAME = SlingPostOperation.class.getName();
+
+    /**
+     * The name of the service registration property indicating the name(s) of
+     * the operation provided by the operation implementation (value is
+     * "sling.post.operation"). The value of this service property must be a
+     * single String or an array or <code>java.util.Vector</code> of Strings.
+     * If multiple strings are defined, the service is registered for all
+     * operation names.
+     */
+    public static final String PROP_OPERATION_NAME = "sling.post.operation";
+
+    /**
+     * Executes the operation provided by this service implementation. This
+     * method is called by the Sling default POST servlet.
+     * 
+     * @param request The <code>SlingHttpServletRequest</code> object
+     *            providing the request input for the operation.
+     * @param response The <code>HtmlResponse</code> into which the operation
+     *            steps should be recorded.
+     * @throws org.apache.sling.api.resource.ResourceNotFoundException May be
+     *             thrown if the operation requires an existing request
+     *             resource. If this exception is thrown the Sling default POST
+     *             servlet sends back a <code>404/NOT FOUND</code> response to
+     *             the client.
+     * @throws org.apache.sling.api.SlingException May be thrown if an error
+     *             occurrs running the operation.
+     */
+    void run(SlingHttpServletRequest request, HtmlResponse response);
+
+}
diff --git a/src/main/java/org/apache/sling/servlets/post/impl/SlingPostServlet.java b/src/main/java/org/apache/sling/servlets/post/impl/SlingPostServlet.java
new file mode 100644
index 0000000..ce89df1
--- /dev/null
+++ b/src/main/java/org/apache/sling/servlets/post/impl/SlingPostServlet.java
@@ -0,0 +1,272 @@
+/*
+ * 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 java.io.IOException;
+import java.util.Dictionary;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.sling.api.SlingHttpServletRequest;
+import org.apache.sling.api.SlingHttpServletResponse;
+import org.apache.sling.api.resource.ResourceNotFoundException;
+import org.apache.sling.api.resource.ResourceUtil;
+import org.apache.sling.api.servlets.HtmlResponse;
+import org.apache.sling.api.servlets.SlingAllMethodsServlet;
+import org.apache.sling.commons.osgi.OsgiUtil;
+import org.apache.sling.servlets.post.SlingPostConstants;
+import org.apache.sling.servlets.post.SlingPostOperation;
+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.operations.CopyOperation;
+import org.apache.sling.servlets.post.impl.operations.DeleteOperation;
+import org.apache.sling.servlets.post.impl.operations.ModifyOperation;
+import org.apache.sling.servlets.post.impl.operations.MoveOperation;
+import org.osgi.service.component.ComponentContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * POST servlet that implements the sling client library "protocol"
+ * 
+ * @scr.component immediate="true" label="%servlet.post.name"
+ *                description="%servlet.post.description"
+ * @scr.service interface="javax.servlet.Servlet"
+ * @scr.property name="service.description" value="Sling Post Servlet"
+ * @scr.property name="service.vendor" value="The Apache Software Foundation"
+ * 
+ * Use this as the default servlet for POST requests for Sling
+ * @scr.property name="sling.servlet.resourceTypes"
+ *               value="sling/servlet/default" private="true"
+ * @scr.property name="sling.servlet.methods" value="POST" private="true"
+ */
+public class SlingPostServlet extends SlingAllMethodsServlet {
+
+    private static final long serialVersionUID = 1837674988291697074L;
+
+    /**
+     * default log
+     */
+    private final Logger log = LoggerFactory.getLogger(getClass());
+
+    /**
+     * @scr.property values.0="EEE MMM dd yyyy HH:mm:ss 'GMT'Z"
+     *               values.1="yyyy-MM-dd'T'HH:mm:ss.SSSZ"
+     *               values.2="yyyy-MM-dd'T'HH:mm:ss" values.3="yyyy-MM-dd"
+     *               values.4="dd.MM.yyyy HH:mm:ss" values.5="dd.MM.yyyy"
+     */
+    private static final String PROP_DATE_FORMAT = "servlet.post.dateFormats";
+
+    /**
+     * @scr.property values.0="title" values.1="jcr:title" values.2="name"
+     *               values.3="description" values.4="jcr:description"
+     *               values.5="abstract"
+     */
+    private static final String PROP_NODE_NAME_HINT_PROPERTIES = "servlet.post.nodeNameHints";
+
+    /**
+     * @scr.property value="20" type="Integer"
+     */
+    private static final String PROP_NODE_NAME_MAX_LENGTH = "servlet.post.nodeNameMaxLength";
+
+    /**
+     * utility class for generating node names
+     */
+    private NodeNameGenerator nodeNameGenerator;
+
+    /**
+     * utility class for parsing date strings
+     */
+    private DateParser dateParser;
+
+    private SlingPostOperation modifyOperation;
+
+    private final Map<String, SlingPostOperation> postOperations = new HashMap<String, SlingPostOperation>();
+
+    @Override
+    public void init() {
+        // default operation: create/modify
+        modifyOperation = new ModifyOperation(nodeNameGenerator, dateParser,
+            getServletContext());
+
+        // other predefined operations
+        postOperations.put(SlingPostConstants.OPERATION_COPY,
+            new CopyOperation());
+        postOperations.put(SlingPostConstants.OPERATION_MOVE,
+            new MoveOperation());
+        postOperations.put(SlingPostConstants.OPERATION_DELETE,
+            new DeleteOperation());
+    }
+
+    @Override
+    public void destroy() {
+        modifyOperation = null;
+        postOperations.clear();
+    }
+
+    @Override
+    protected void doPost(SlingHttpServletRequest request,
+            SlingHttpServletResponse response) throws IOException {
+
+        // prepare the response
+        HtmlResponse htmlResponse = new HtmlResponse();
+        htmlResponse.setReferer(request.getHeader("referer"));
+
+        SlingPostOperation operation = getSlingPostOperation(request);
+        if (operation == null) {
+
+            htmlResponse.setStatus(
+                HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
+                "Invalid operation specified for POST request");
+
+        } else {
+
+            try {
+                operation.run(request, htmlResponse);
+            } catch (ResourceNotFoundException rnfe) {
+                htmlResponse.setStatus(HttpServletResponse.SC_NOT_FOUND,
+                    rnfe.getMessage());
+            } catch (Throwable throwable) {
+                htmlResponse.setError(throwable);
+            }
+
+        }
+
+        // check for redirect URL if processing succeeded
+        if (htmlResponse.isSuccessful()) {
+            String redirect = getRedirectUrl(request, htmlResponse);
+            if (redirect != null) {
+                response.sendRedirect(redirect);
+                return;
+            }
+        }
+
+        // create a html response and send if unsuccessful or no redirect
+        htmlResponse.send(response, isSetStatus(request));
+    }
+
+    private SlingPostOperation getSlingPostOperation(
+            SlingHttpServletRequest request) {
+        String operation = request.getParameter(SlingPostConstants.RP_OPERATION);
+        if (operation == null || operation.length() == 0) {
+            // standard create/modify operation;
+            return modifyOperation;
+        }
+
+        // named operation, retrieve from map
+        return postOperations.get(operation);
+    }
+
+    /**
+     * compute redirect URL (SLING-126)
+     * 
+     * @param ctx the post processor
+     * @return the redirect location or <code>null</code>
+     */
+    protected String getRedirectUrl(HttpServletRequest request, HtmlResponse ctx) {
+        // redirect param has priority (but see below, magic star)
+        String result = request.getParameter(SlingPostConstants.RP_REDIRECT_TO);
+        if (result != null && ctx.getPath() != 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
+                buf.append(ResourceUtil.getName(ctx.getPath()));
+
+                // 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();
+
+            } else if (result.endsWith(SlingPostConstants.DEFAULT_CREATE_SUFFIX)) {
+                // if the redirect has a trailing slash, append modified node
+                // name
+                result = result.concat(ResourceUtil.getName(ctx.getPath()));
+            }
+
+            if (log.isDebugEnabled()) {
+                log.debug("Will redirect to " + result);
+            }
+        }
+        return result;
+    }
+
+    protected boolean isSetStatus(SlingHttpServletRequest request) {
+        String statusParam = request.getParameter(SlingPostConstants.RP_STATUS);
+        if (statusParam == null) {
+            log.debug(
+                "getStatusMode: Parameter {} not set, assuming standard status code",
+                SlingPostConstants.RP_STATUS);
+            return true;
+        }
+
+        if (SlingPostConstants.STATUS_VALUE_BROWSER.equals(statusParam)) {
+            log.debug(
+                "getStatusMode: Parameter {} asks for user-friendly status code",
+                SlingPostConstants.RP_STATUS);
+            return false;
+        }
+
+        if (SlingPostConstants.STATUS_VALUE_STANDARD.equals(statusParam)) {
+            log.debug(
+                "getStatusMode: Parameter {} asks for standard status code",
+                SlingPostConstants.RP_STATUS);
+            return true;
+        }
+
+        log.debug(
+            "getStatusMode: Parameter {} set to unknown value {}, assuming standard status code",
+            SlingPostConstants.RP_STATUS);
+        return true;
+    }
+
+    // ---------- SCR Integration ----------------------------------------------
+
+    protected void activate(ComponentContext context) {
+        Dictionary<?, ?> props = context.getProperties();
+
+        String[] nameHints = OsgiUtil.toStringArray(props.get(PROP_NODE_NAME_HINT_PROPERTIES));
+        int nameMax = (int) OsgiUtil.toLong(
+            props.get(PROP_NODE_NAME_MAX_LENGTH), -1);
+        nodeNameGenerator = new NodeNameGenerator(nameHints, nameMax);
+
+        dateParser = new DateParser();
+        String[] dateFormats = OsgiUtil.toStringArray(props.get(PROP_DATE_FORMAT));
+        for (String dateFormat : dateFormats) {
+            dateParser.register(dateFormat);
+        }
+    }
+
+    protected void deactivate(ComponentContext context) {
+        nodeNameGenerator = null;
+        dateParser = null;
+    }
+}
diff --git a/src/main/java/org/apache/sling/servlets/post/impl/helper/DateParser.java b/src/main/java/org/apache/sling/servlets/post/impl/helper/DateParser.java
new file mode 100644
index 0000000..9758da9
--- /dev/null
+++ b/src/main/java/org/apache/sling/servlets/post/impl/helper/DateParser.java
@@ -0,0 +1,138 @@
+/*
+ * 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.helper;
+
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Locale;
+
+import javax.jcr.Value;
+import javax.jcr.ValueFactory;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Takes a string representation of a time-date string and tries for parse it
+ * using different formats.
+ */
+public class DateParser {
+
+    /**
+     * default log
+     */
+    private static final Logger log = LoggerFactory.getLogger(DateParser.class);
+
+    /**
+     * lits of formats
+     */
+    private final List<DateFormat> formats = new LinkedList<DateFormat>();
+
+    /**
+     * Registers a format string to the list of internally checked ones.
+     * Uses the {@link SimpleDateFormat}.
+     * @param format format as in {@link SimpleDateFormat}
+     * @throws IllegalArgumentException if the format is not valid.
+     */
+    public void register(String format) {
+        register(new SimpleDateFormat(format, Locale.US));
+    }
+
+    /**
+     * Registers a date format to the list of internally checked ones.
+     * @param format date format
+     */
+    public void register(DateFormat format) {
+        formats.add(format);
+    }
+
+    /**
+     * Parses the given source string and returns the respective calendar
+     * instance. If no format matches returns <code>null</code>.
+     * <p/>
+     * Note: method is synchronized because SimpleDateFormat is not.
+     *
+     * @param source date time source string
+     * @return calendar representation of the source or <code>null</code>
+     */
+    public synchronized Calendar parse(String source) {
+        for (DateFormat fmt: formats) {
+            try {
+                Date d = fmt.parse(source);
+                if (log.isDebugEnabled()) {
+                    log.debug("Parsed " + source + " using " + fmt + " into " + d);
+                }
+                Calendar c = Calendar.getInstance();
+                c.setTime(d);
+                return c;
+            } catch (ParseException e) {
+                if (log.isDebugEnabled()) {
+                    log.debug("Failed parsing " + source + " using " + fmt);
+                }
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Parses the given source strings and returns the respective calendar
+     * instances. If no format matches for any of the sources
+     * returns <code>null</code>.
+     * <p/>
+     * Note: method is synchronized because SimpleDateFormat is not.
+     *
+     * @param sources date time source strings
+     * @return calendar representations of the source or <code>null</code>
+     */
+    public synchronized Calendar[] parse(String sources[]) {
+        Calendar ret[] = new Calendar[sources.length];
+        for (int i=0; i< sources.length; i++) {
+            if ((ret[i] = parse(sources[i])) == null) {
+                return null;
+            }
+        }
+        return ret;
+    }
+    
+    /**
+     * Parses the given source strings and returns the respective jcr date value
+     * instances. If no format matches for any of the sources
+     * returns <code>null</code>.
+     * <p/>
+     * Note: method is synchronized because SimpleDateFormat is not.
+     *
+     * @param sources date time source strings
+     * @param factory the value factory
+     * @return jcr date value representations of the source or <code>null</code>
+     */
+    public synchronized Value[] parse(String sources[], ValueFactory factory) {
+        Value ret[] = new Value[sources.length];
+        for (int i=0; i< sources.length; i++) {
+            Calendar c = parse(sources[i]);
+            if (c == null) {
+                return null;
+            }
+            ret[i] = factory.createValue(c);
+        }
+        return ret;
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/org/apache/sling/servlets/post/impl/helper/NodeNameFilter.java b/src/main/java/org/apache/sling/servlets/post/impl/helper/NodeNameFilter.java
new file mode 100644
index 0000000..92817c1
--- /dev/null
+++ b/src/main/java/org/apache/sling/servlets/post/impl/helper/NodeNameFilter.java
@@ -0,0 +1,58 @@
+/*
+ * 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.helper;
+
+
+/**
+ * Filter a String so that it can be used as a NodeName.
+ */
+public class NodeNameFilter {
+
+    public static final String ALLOWED_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789_";
+    public static final char REPLACEMENT_CHAR = '_';
+
+    public String filter(String nodeName) {
+        final StringBuffer sb  = new StringBuffer();
+        char lastAdded = 0;
+
+        nodeName = nodeName.toLowerCase();
+        for(int i=0; i < nodeName.length(); i++) {
+            final char c = nodeName.charAt(i);
+            char toAdd = c;
+
+            if (ALLOWED_CHARS.indexOf(c) < 0) {
+                if (lastAdded == REPLACEMENT_CHAR) {
+                    // do not add several _ in a row
+                    continue;
+                }
+                toAdd = REPLACEMENT_CHAR;
+                
+            } else if(i == 0 && Character.isDigit(c)) {
+                sb.append(REPLACEMENT_CHAR);
+            }
+            
+            sb.append(toAdd);
+            lastAdded = toAdd;
+        }
+        
+        if (sb.length()==0) {
+            sb.append(REPLACEMENT_CHAR);
+        }
+
+        return sb.toString();
+    }
+}
diff --git a/src/main/java/org/apache/sling/servlets/post/impl/helper/NodeNameGenerator.java b/src/main/java/org/apache/sling/servlets/post/impl/helper/NodeNameGenerator.java
new file mode 100644
index 0000000..32864f6
--- /dev/null
+++ b/src/main/java/org/apache/sling/servlets/post/impl/helper/NodeNameGenerator.java
@@ -0,0 +1,131 @@
+/*
+ * 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.helper;
+
+import org.apache.sling.api.request.RequestParameter;
+import org.apache.sling.api.request.RequestParameterMap;
+import org.apache.sling.servlets.post.SlingPostConstants;
+
+/**
+ * Generates a node name based on a set of well-known request parameters
+ * like title, description, etc.
+ * See SLING-128.
+ */
+public class NodeNameGenerator {
+
+    private final String[] parameterNames;
+    private final NodeNameFilter filter = new NodeNameFilter();
+
+    public static final int DEFAULT_MAX_NAME_LENGTH = 20;
+
+    private int maxLength = DEFAULT_MAX_NAME_LENGTH;
+    private int counter;
+
+    public NodeNameGenerator(String[] parameterNames, int maxNameLength) {
+        if (parameterNames == null) {
+            this.parameterNames = new String[0];
+        } else {
+            this.parameterNames = parameterNames;
+        }
+
+        this.maxLength = (maxNameLength > 0)
+                ? maxNameLength
+                : DEFAULT_MAX_NAME_LENGTH;
+    }
+
+    /**
+     * Get a "nice" node name, if possible, based on given request
+     *
+     * @param parameters the request parameters
+     * @param requirePrefix <code>true</code> if the parameter names for
+     *      properties requires a prefix
+     * @return a nice node name
+     */
+    public String getNodeName(RequestParameterMap parameters,
+            boolean requirePrefix) {
+        String valueToUse = null;
+        boolean doFilter = true;
+
+        // find the first request parameter that matches one of
+        // our parameterNames, in order, and has a value
+        if (parameters!=null) {
+            // we first check for the special sling parameters
+            RequestParameter specialParam = parameters.getValue(SlingPostConstants.RP_NODE_NAME);
+            if ( specialParam != null ) {
+                if ( specialParam.getString() != null && specialParam.getString().length() > 0 ) {
+                    valueToUse = specialParam.getString();
+                    doFilter = false;
+                }
+            }
+            if ( valueToUse == null ) {
+                specialParam = parameters.getValue(SlingPostConstants.RP_NODE_NAME_HINT);
+                if ( specialParam != null ) {
+                    if ( specialParam.getString() != null && specialParam.getString().length() > 0 ) {
+                        valueToUse = specialParam.getString();
+                    }
+                }
+            }
+
+            if (valueToUse == null) {
+                for (String param : parameterNames) {
+                    if (valueToUse != null) {
+                        break;
+                    }
+                    if (requirePrefix) {
+                        param = SlingPostConstants.ITEM_PREFIX_RELATIVE_CURRENT.concat(param);
+                    }
+                    final RequestParameter[] pp = parameters.get(param);
+                    if (pp != null) {
+                        for (RequestParameter p : pp) {
+                            valueToUse = p.getString();
+                            if (valueToUse != null && valueToUse.length() > 0) {
+                                break;
+                            }
+                            valueToUse = null;
+                        }
+                    }
+                }
+            }
+        }
+        String result;
+        // should we filter?
+        if (valueToUse != null) {
+            if ( doFilter ) {
+                // filter value so that it works as a node name
+                result = filter.filter(valueToUse);
+            } else {
+                result = valueToUse;
+            }
+        } else {
+            // default value if none provided
+            result = nextCounter() + "_" + System.currentTimeMillis();
+        }
+
+        if ( doFilter ) {
+            // max length
+            if (result.length() > maxLength) {
+                result = result.substring(0,maxLength);
+            }
+        }
+
+        return result;
+    }
+
+    public synchronized int nextCounter() {
+        return ++counter;
+    }
+}
diff --git a/src/main/java/org/apache/sling/servlets/post/impl/helper/RequestProperty.java b/src/main/java/org/apache/sling/servlets/post/impl/helper/RequestProperty.java
new file mode 100644
index 0000000..48a795c
--- /dev/null
+++ b/src/main/java/org/apache/sling/servlets/post/impl/helper/RequestProperty.java
@@ -0,0 +1,255 @@
+/*
+ * 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.helper;
+
+import org.apache.sling.api.request.RequestParameter;
+import org.apache.sling.api.resource.ResourceUtil;
+import org.apache.sling.servlets.post.SlingPostConstants;
+
+/**
+ * Encapsulates all infos from the respective request parameters that are needed
+ * to create the repository property
+ */
+public class RequestProperty {
+
+    private static final RequestParameter[] EMPTY_PARAM_ARRAY = new RequestParameter[0];
+
+    public static final String DEFAULT_IGNORE = SlingPostConstants.RP_PREFIX
+        + "ignore";
+
+    public static final String DEFAULT_NULL = SlingPostConstants.RP_PREFIX
+        + "null";
+
+    private final String path;
+
+    private final String name;
+
+    private final String parentPath;
+
+    private RequestParameter[] values;
+
+    private String[] stringValues;
+
+    private String typeHint;
+
+    private boolean hasMultiValueTypeHint;
+
+    private RequestParameter[] defaultValues = EMPTY_PARAM_ARRAY;
+
+    private boolean isDelete;
+
+    private String repositoryResourcePath;
+
+    private boolean isRepositoryResourceMove;
+
+    public RequestProperty(String path) {
+        assert path.startsWith("/");
+        this.path = ResourceUtil.normalize(path);
+        this.parentPath = ResourceUtil.getParent(path);
+        this.name = ResourceUtil.getName(path);
+    }
+
+    public String getTypeHint() {
+        return typeHint;
+    }
+
+    public boolean hasMultiValueTypeHint() {
+        return this.hasMultiValueTypeHint;
+    }
+
+    public void setTypeHintValue(String typeHint) {
+        if ( typeHint != null && typeHint.endsWith("[]") ) {
+            this.typeHint = typeHint.substring(0, typeHint.length() - 2);
+            this.hasMultiValueTypeHint = true;
+        } else {
+            this.typeHint = typeHint;
+            this.hasMultiValueTypeHint = false;
+        }
+    }
+
+    public String getPath() {
+        return path;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public String getParentPath() {
+        return parentPath;
+    }
+
+    public boolean hasValues() {
+        return values != null;
+    }
+
+    public RequestParameter[] getValues() {
+        return values;
+    }
+
+    public void setValues(RequestParameter[] values) {
+        this.values = values;
+    }
+
+    public RequestParameter[] getDefaultValues() {
+        return defaultValues;
+    }
+
+    public void setDefaultValues(RequestParameter[] defaultValues) {
+        if (defaultValues == null) {
+            this.defaultValues = EMPTY_PARAM_ARRAY;
+        } else {
+            this.defaultValues = defaultValues;
+        }
+    }
+
+    public boolean isFileUpload() {
+        return !values[0].isFormField();
+    }
+
+    /**
+     * Checks if this property provides any values. this is the case if one of
+     * the values is not empty or if the default handling is not 'ignore'
+     *
+     * @return <code>true</code> if this property provides values
+     */
+    public boolean providesValue() {
+        // should void double creation of string values
+        String[] sv = getStringValues();
+        if (sv == null) {
+            // is missleading return type. but means that property should not
+            // get auto-create values
+            return true;
+        }
+        for (String s : sv) {
+            if (!s.equals("")) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Returns the assembled string array out of the provided request values and
+     * default values.
+     *
+     * @return a String array or <code>null</code> if the property needs to be
+     *         removed.
+     */
+    public String[] getStringValues() {
+        if (stringValues == null) {
+            if (values.length > 1) {
+                // TODO: how the default values work for MV props is not very
+                // clear
+                stringValues = new String[values.length];
+                for (int i = 0; i < stringValues.length; i++) {
+                    stringValues[i] = values[i].getString();
+                }
+            } else {
+                String value = values[0].getString();
+                if (value.equals("")) {
+                    if (defaultValues.length == 1) {
+                        String defValue = defaultValues[0].getString();
+                        if (defValue.equals(DEFAULT_IGNORE)) {
+                            // ignore means, do not create empty values
+                            return new String[0];
+                        } else if (defValue.equals(DEFAULT_NULL)) {
+                            // null means, remove property if exist
+                            return null;
+                        }
+                        value = defValue;
+                    }
+                }
+                stringValues = new String[] { value };
+            }
+        }
+        return stringValues;
+    }
+
+    /**
+     * Specifies whether this property should be deleted before any new content
+     * is to be set according to the values stored.
+     *
+     * @param isDelete <code>true</code> if the repository item described by
+     *            this is to be deleted before any other operation.
+     */
+    public void setDelete(boolean isDelete) {
+        this.isDelete = isDelete;
+    }
+
+    /**
+     * Returns <code>true</code> if the repository item described by this is
+     * to be deleted before setting new content to it.
+     */
+    public boolean isDelete() {
+        return isDelete;
+    }
+
+    /**
+     * Sets the path of the repository item from which the content for this
+     * property is to be copied or moved. The path may be relative in which case
+     * it will be resolved relative to the absolute path of this property.
+     *
+     * @param sourcePath The path of the repository item to get the content from
+     * @param isMove <code>true</code> if the source content is to be moved,
+     *            otherwise the source content is copied from the repository
+     *            item.
+     */
+    public void setRepositorySource(String sourcePath, boolean isMove) {
+
+        // make source path absolute
+        if (!sourcePath.startsWith("/")) {
+            sourcePath = getParentPath() + "/" + sourcePath;
+            sourcePath = ResourceUtil.normalize(sourcePath);
+        }
+
+        this.repositoryResourcePath = sourcePath;
+        this.isRepositoryResourceMove = isMove;
+    }
+
+    /**
+     * Returns <code>true</code> if the content of this property is to be set
+     * by moving content from another repository item.
+     *
+     * @see #getRepositorySource()
+     */
+    public boolean hasRepositoryMoveSource() {
+        return isRepositoryResourceMove;
+    }
+
+    /**
+     * Returns <code>true</code> if the content of this property is to be set
+     * by copying content from another repository item.
+     *
+     * @see #getRepositorySource()
+     */
+    public boolean hasRepositoryCopySource() {
+        return getRepositorySource() != null && !hasRepositoryMoveSource();
+    }
+
+    /**
+     * Returns the absolute path of the repository item from which the content
+     * for this property is to be copied or moved.
+     *
+     * @see #hasRepositoryCopySource()
+     * @see #hasRepositoryMoveSource()
+     * @see #setRepositorySource(String, boolean)
+     */
+    public String getRepositorySource() {
+        return repositoryResourcePath;
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/org/apache/sling/servlets/post/impl/helper/SlingFileUploadHandler.java b/src/main/java/org/apache/sling/servlets/post/impl/helper/SlingFileUploadHandler.java
new file mode 100644
index 0000000..53dcbf0
--- /dev/null
+++ b/src/main/java/org/apache/sling/servlets/post/impl/helper/SlingFileUploadHandler.java
@@ -0,0 +1,205 @@
+/*
+ * 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.helper;
+
+import java.io.IOException;
+import java.util.Calendar;
+
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+import javax.jcr.nodetype.NodeType;
+import javax.jcr.nodetype.NodeTypeManager;
+import javax.servlet.ServletContext;
+
+import org.apache.jackrabbit.util.Text;
+import org.apache.sling.api.request.RequestParameter;
+import org.apache.sling.api.servlets.HtmlResponse;
+
+/**
+ * Handles file uploads.
+ * <p/>
+ *
+ * Simple example:
+ * <xmp>
+ *   <form action="/home/admin" method="POST" enctype="multipart/form-data">
+ *     <input type="file" name="./portrait" />
+ *   </form>
+ * </xmp>
+ *
+ * this will create a nt:file node below "/home/admin" if the node type of
+ * "admin" is (derived from) nt:folder, a nt:resource node otherwise.
+ * <p/>
+ *
+ * Filename example:
+ * <xmp>
+ *   <form action="/home/admin" method="POST" enctype="multipart/form-data">
+ *     <input type="file" name="./*" />
+ *   </form>
+ * </xmp>
+ *
+ * same as above, but uses the filename of the uploaded file as name for the
+ * new node.
+ * <p/>
+ *
+ * Type hint example:
+ * <xmp>
+ *   <form action="/home/admin" method="POST" enctype="multipart/form-data">
+ *     <input type="file" name="./portrait" />
+ *     <input type="hidden" name="./portrait@TypeHint" value="my:file" />
+ *   </form>
+ * </xmp>
+ *
+ * this will create a new node with the type my:file below admin. if the hinted
+ * type extends from nt:file an intermediate file node is created otherwise
+ * directly a resource node.
+ */
+public class SlingFileUploadHandler {
+
+    // nodetype name string constants
+    public static final String NT_FOLDER = "nt:folder";
+    public static final String NT_FILE = "nt:file";
+    public static final String NT_RESOURCE = "nt:resource";
+    public static final String NT_UNSTRUCTURED = "nt:unstructured";
+
+    // item name string constants
+    public static final String JCR_CONTENT = "jcr:content";
+    public static final String JCR_LASTMODIFIED = "jcr:lastModified";
+    public static final String JCR_MIMETYPE = "jcr:mimeType";
+    public static final String JCR_ENCODING = "jcr:encoding";
+    public static final String JCR_DATA = "jcr:data";
+
+    /**
+     * The servlet context.
+     */
+    private final ServletContext servletContext;
+
+    /**
+     * Constructs file upload handler
+     * @param servletCtx the post processor
+     */
+    public SlingFileUploadHandler(ServletContext servletCtx) {
+        this.servletContext = servletCtx;
+    }
+
+    /**
+     * 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 <code>name</code> is '*', the filename of
+     * the uploaded file is used.
+     *
+     * @param parent the parent node
+     * @param prop the assembled property info
+     * @throws RepositoryException if an error occurs
+     */
+    public void setFile(Node parent, RequestProperty prop, HtmlResponse response)
+            throws RepositoryException {
+        RequestParameter value = prop.getValues()[0];
+        assert !value.isFormField();
+
+        // ignore if empty
+        if (value.getSize() <= 0) {
+            return;
+        }
+
+        // get node name
+        String name = prop.getName();
+        if (name.equals("*")) {
+            name = value.getFileName();
+            // strip of possible path (some browsers include the entire path)
+            name = name.substring(name.lastIndexOf('/') + 1);
+            name = name.substring(name.lastIndexOf('\\') + 1);
+        }
+        name = Text.escapeIllegalJcrChars(name);
+
+        // check type hint. if the type is ok and extends from nt:file,
+        // create an nt:file with that type. if it's invalid, drop it and let
+        // the parent node type decide.
+        boolean createNtFile = parent.isNodeType(NT_FOLDER);
+        String typeHint = prop.getTypeHint();
+        if (typeHint != null) {
+            try {
+                NodeTypeManager ntMgr = parent.getSession().getWorkspace().getNodeTypeManager();
+                NodeType nt = ntMgr.getNodeType(typeHint);
+                createNtFile = nt.isNodeType(NT_FILE);
+            } catch (RepositoryException e) {
+                // assuming type not valid.
+                typeHint = null;
+            }
+        }
+
+        // also create an nt:file if the name contains an extension
+        // the rationale is that if the file name is "important" we want
+        // an nt:file, and an image name with an extension is probably "important"
+        if(!createNtFile && name.indexOf('.') > 0) {
+            createNtFile = true;
+        }
+
+        // set empty type
+        if (typeHint == null) {
+            typeHint = createNtFile ? NT_FILE : NT_RESOURCE;
+        }
+
+        // remove node
+        if (parent.hasNode(name)) {
+            parent.getNode(name).remove();
+        }
+
+        // create nt:file node if needed
+        if (createNtFile) {
+            // create nt:file
+            parent = parent.addNode(name, typeHint);
+            response.onCreated(parent.getPath());
+            name = JCR_CONTENT;
+            typeHint = NT_RESOURCE;
+        }
+
+        // create resource node
+        Node res = parent.addNode(name, typeHint);
+        response.onCreated(res.getPath());
+
+        // get content type
+        String contentType = value.getContentType();
+        if (contentType != null) {
+            int idx = contentType.indexOf(';');
+            if (idx > 0) {
+                contentType = contentType.substring(0, idx);
+            }
+        }
+        if (contentType == null || contentType.equals("application/octet-stream")) {
+            // try to find a better content type
+            contentType = this.servletContext.getMimeType(value.getFileName());
+            if (contentType == null || contentType.equals("application/octet-stream")) {
+                contentType = "application/octet-stream";
+            }
+        }
+
+        // set properties
+        response.onModified(
+            res.setProperty(JCR_LASTMODIFIED, Calendar.getInstance()).getPath()
+        );
+        response.onModified(
+            res.setProperty(JCR_MIMETYPE, contentType).getPath()
+        );
+        try {
+            response.onModified(
+                res.setProperty(JCR_DATA, value.getInputStream()).getPath()
+            );
+        } catch (IOException e) {
+            throw new RepositoryException("Error while retrieving inputstream from parameter value.", e);
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/org/apache/sling/servlets/post/impl/helper/SlingPropertyValueHandler.java b/src/main/java/org/apache/sling/servlets/post/impl/helper/SlingPropertyValueHandler.java
new file mode 100644
index 0000000..9c57694
--- /dev/null
+++ b/src/main/java/org/apache/sling/servlets/post/impl/helper/SlingPropertyValueHandler.java
@@ -0,0 +1,285 @@
+/*
+ * 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.helper;
+
+import java.util.Calendar;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.jcr.Node;
+import javax.jcr.Property;
+import javax.jcr.PropertyType;
+import javax.jcr.RepositoryException;
+import javax.jcr.Value;
+import javax.jcr.ValueFactory;
+
+import org.apache.sling.api.servlets.HtmlResponse;
+
+/**
+ * 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.
+ */
+public class SlingPropertyValueHandler {
+
+    /**
+     * Defins a map of auto properties
+     */
+    private static final Map<String, AutoType> AUTO_PROPS = new HashMap<String, AutoType>();
+    static {
+        AUTO_PROPS.put("created", AutoType.CREATED);
+        AUTO_PROPS.put("createdBy", AutoType.CREATED_BY);
+        AUTO_PROPS.put("jcr:created", AutoType.CREATED);
+        AUTO_PROPS.put("jcr:createdBy", AutoType.CREATED_BY);
+        AUTO_PROPS.put("lastModified", AutoType.MODIFIED);
+        AUTO_PROPS.put("lastModifiedBy", AutoType.MODIFIED_BY);
+        AUTO_PROPS.put("jcr:lastModified", AutoType.MODIFIED);
+        AUTO_PROPS.put("jcr:lastModifiedBy", AutoType.MODIFIED_BY);
+    }
+
+    /**
+     * the post processor
+     */
+    private final HtmlResponse response;
+
+    private final DateParser dateParser;
+
+    /**
+     * current date for all properties in this request
+     */
+    private final Calendar now = Calendar.getInstance();
+
+    /**
+     * Constructs a propert value handler
+     */
+    public SlingPropertyValueHandler(DateParser dateParser, HtmlResponse response) {
+        this.dateParser = dateParser;
+        this.response = response;
+    }
+
+
+    /**
+     * Set property on given node, with some automatic values when user provides
+     * the field name but no value.
+     *
+     * html example for testing:
+     * <xmp>
+     *   <input type="hidden" name="created"/>
+     *   <input type="hidden" name="lastModified"/>
+     *   <input type="hidden" name="createdBy" />
+     *   <input type="hidden" name="lastModifiedBy"/>
+     * </xmp>
+     *
+     * @param parent the parent node
+     * @param prop the request property
+     * @throws RepositoryException if a repository error occurs
+     */
+    public 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 (AUTO_PROPS.containsKey(name)) {
+            // avoid collision with protected properties
+            switch (AUTO_PROPS.get(name)) {
+                case CREATED:
+                    if (parent.isNew()) {
+                        setCurrentDate(parent, name);
+                    }
+                    break;
+                case CREATED_BY:
+                    if (parent.isNew()) {
+                        setCurrentUser(parent, name);
+                    }
+                    break;
+                case MODIFIED:
+                    setCurrentDate(parent, name);
+                    break;
+                case MODIFIED_BY:
+                    setCurrentUser(parent, name);
+                    break;
+            }
+        } 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);
+        response.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);
+        response.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 <code>null</code> 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.UNDEFINED;
+        if (prop.getTypeHint() != null) {
+            try {
+                type = PropertyType.valueFromName(prop.getTypeHint());
+            } catch (Exception e) {
+                // ignore
+            }
+        }
+
+        String[] values = prop.getStringValues();
+        if (values == null) {
+            // remove property
+            response.onDeleted(
+                removePropertyIfExists(parent, prop.getName())
+            );
+        } else if (values.length == 0) {
+            // do not create new prop here, but clear existing
+            if (parent.hasProperty(prop.getName())) {
+                response.onModified(
+                    parent.setProperty(prop.getName(), "").getPath()
+                );
+            }
+        } else if (values.length == 1) {
+            final String removePath = removePropertyIfExists(parent, prop.getName());
+            // if the provided value is the empty string, we don't have to do anything.
+            if ( values[0].length() == 0 ) {
+                if ( removePath != null ) {
+                    response.onDeleted(removePath);
+                }
+            } else {
+                // modify property
+                if (type == PropertyType.DATE) {
+                    // try conversion
+                    Calendar c = dateParser.parse(values[0]);
+                    if (c != null) {
+                        if ( prop.hasMultiValueTypeHint() ) {
+                            final Value[] array = new Value[1];
+                            array[0] = parent.getSession().getValueFactory().createValue(c);
+                            response.onModified(
+                                parent.setProperty(prop.getName(), array).getPath()
+                            );
+                        } else {
+                            response.onModified(
+                                    parent.setProperty(prop.getName(), c).getPath()
+                                );
+                        }
+                        return;
+                    }
+                    // fall back to default behaviour
+                }
+                final Property p;
+                if ( type == PropertyType.UNDEFINED ) {
+                    p = parent.setProperty(prop.getName(), values[0]);
+                } else {
+                    if ( prop.hasMultiValueTypeHint() ) {
+                        final Value[] array = new Value[1];
+                        array[0] = parent.getSession().getValueFactory().createValue(values[0], type);
+                        p = parent.setProperty(prop.getName(), array);
+                    } else {
+                        p = parent.setProperty(prop.getName(), values[0], type);
+                    }
+                }
+                response.onModified(p.getPath());
+            }
+        } else {
+            removePropertyIfExists(parent, prop.getName());
+            if (type == PropertyType.DATE) {
+                // try conversion
+                ValueFactory valFac = parent.getSession().getValueFactory();
+                Value[] c = dateParser.parse(values, valFac);
+                if (c != null) {
+                    response.onModified(
+                        parent.setProperty(prop.getName(), c).getPath()
+                    );
+                    return;
+                }
+                // fall back to default behaviour
+            }
+            final Property p;
+            if ( type == PropertyType.UNDEFINED ) {
+                p = parent.setProperty(prop.getName(), values);
+            } else {
+                p = parent.setProperty(prop.getName(), values, type);
+            }
+            response.onModified(p.getPath());
+        }
+    }
+
+    /**
+     * Defines an auto property behavior
+     */
+    private enum AutoType {
+        CREATED,
+        CREATED_BY,
+        MODIFIED,
+        MODIFIED_BY
+    }
+}
diff --git a/src/main/java/org/apache/sling/servlets/post/impl/operations/AbstractCopyMoveOperation.java b/src/main/java/org/apache/sling/servlets/post/impl/operations/AbstractCopyMoveOperation.java
new file mode 100644
index 0000000..0b0ee84
--- /dev/null
+++ b/src/main/java/org/apache/sling/servlets/post/impl/operations/AbstractCopyMoveOperation.java
@@ -0,0 +1,156 @@
+/*
+ * 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.Iterator;
+
+import javax.jcr.Item;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.sling.api.SlingHttpServletRequest;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ResourceNotFoundException;
+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;
+
+/**
+ * The <code>AbstractCopyMoveOperation</code> is the abstract base close for
+ * the {@link CopyOperation} and {@link MoveOperation} classes implementing
+ * commong behaviour.
+ */
+abstract class AbstractCopyMoveOperation extends AbstractSlingPostOperation {
+
+    @Override
+    protected final void doRun(SlingHttpServletRequest request,
+            HtmlResponse response) throws RepositoryException {
+
+        Resource resource = request.getResource();
+        String source = resource.getPath();
+
+        // ensure dest is not empty/null and is absolute
+        String dest = request.getParameter(SlingPostConstants.RP_DEST);
+        if (dest == null || dest.length() == 0) {
+            throw new IllegalArgumentException("Unable to process "
+                + getOperationName() + ". Missing destination");
+        }
+
+        // register whether the path ends with a trailing slash
+        boolean trailingSlash = dest.endsWith("/");
+
+        // ensure destination is an absolute and normalized path
+        if (!dest.startsWith("/")) {
+            dest = ResourceUtil.getParent(source) + "/" + dest;
+        }
+        dest = ResourceUtil.normalize(dest);
+
+        // destination parent and name
+        String dstParent = trailingSlash ? dest : ResourceUtil.getParent(dest);
+
+        // delete destination if already exists
+        Session session = request.getResourceResolver().adaptTo(Session.class);
+        if (!trailingSlash && session.itemExists(dest)) {
+
+            final String replaceString = request.getParameter(SlingPostConstants.RP_REPLACE);
+            final boolean isReplace = "true".equalsIgnoreCase(replaceString);
+            if (!isReplace) {
+                response.setStatus(HttpServletResponse.SC_PRECONDITION_FAILED,
+                    "Cannot " + getOperationName() + " " + resource + " to "
+                        + dest + ": destination exists");
+                return;
+            }
+
+        } else {
+
+            // check if path to destination exists and create it, but only
+            // if it's a descendant of the current node
+            if (!dstParent.equals("") && !session.itemExists(dstParent)) {
+                response.setStatus(HttpServletResponse.SC_PRECONDITION_FAILED,
+                    "Cannot " + getOperationName() + " " + resource + " to "
+                        + dest + ": parent of destination does not exist");
+                return;
+            }
+
+            // the destination is newly created, hence a create request
+            response.setCreateRequest(true);
+        }
+
+        Iterator<Resource> resources = getApplyToResources(request);
+        if (resources == null) {
+
+            // ensure we have an item underlying the request's resource
+            Item item = resource.adaptTo(Item.class);
+            if (item == null) {
+                throw new ResourceNotFoundException("Missing source "
+                    + resource + " for " + getOperationName());
+            }
+
+            String dstName = trailingSlash ? null : ResourceUtil.getName(dest);
+            execute(response, item, dstParent, dstName);
+
+        } else {
+
+            // multiple applyTo requires trailing slash on destination
+            if (!trailingSlash) {
+                throw new IllegalArgumentException(
+                    "Applying "
+                        + getOperationName()
+                        + " to multiple resources requires a trailing slash on the destination");
+            }
+
+            // multiple copy will never return 201/CREATED
+            response.setCreateRequest(false);
+
+            while (resources.hasNext()) {
+                Resource applyTo = resources.next();
+                Item item = applyTo.adaptTo(Item.class);
+                if (item != null) {
+                    execute(response, item, dstParent, null);
+                }
+            }
+
+        }
+
+        // finally apply the ordering parameter
+        orderNode(request, session.getItem(dest));
+    }
+
+    /**
+     * Returns a short name to be used in log and status messages.
+     */
+    protected abstract String getOperationName();
+
+    /**
+     * Actually executes the operation.
+     * 
+     * @param response The <code>HtmlResponse</code> used to record success of
+     *            the operation.
+     * @param source The source item to act upon.
+     * @param destParent The absolute path of the parent of the target item.
+     * @param destName The name of the target item inside the
+     *            <code>destParent</code>. If <code>null</code> the name of
+     *            the <code>source</code> is used as the target item name.
+     * @throws RepositoryException May be thrown if an error occurrs executing
+     *             the operation.
+     */
+    protected abstract void execute(HtmlResponse response, Item source,
+            String destParent, String destName) throws RepositoryException;
+
+}
diff --git a/src/main/java/org/apache/sling/servlets/post/impl/operations/CopyOperation.java b/src/main/java/org/apache/sling/servlets/post/impl/operations/CopyOperation.java
new file mode 100644
index 0000000..7d58515
--- /dev/null
+++ b/src/main/java/org/apache/sling/servlets/post/impl/operations/CopyOperation.java
@@ -0,0 +1,168 @@
+/*
+ * 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.Item;
+import javax.jcr.Node;
+import javax.jcr.NodeIterator;
+import javax.jcr.Property;
+import javax.jcr.PropertyIterator;
+import javax.jcr.RepositoryException;
+import javax.jcr.nodetype.NodeType;
+
+import org.apache.sling.api.servlets.HtmlResponse;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The <code>CopyOperation</code> class implements the
+ * {@link org.apache.sling.servlets.post.SlingPostConstants#OPERATION_COPY copy}
+ * operation for the Sling default POST servlet.
+ */
+public class CopyOperation extends AbstractCopyMoveOperation {
+
+    /**
+     * default log
+     */
+    private static final Logger log = LoggerFactory.getLogger(CopyOperation.class);
+
+    @Override
+    protected String getOperationName() {
+        return "copy";
+    }
+
+    @Override
+    protected void execute(HtmlResponse response, Item source,
+            String destParent, String destName) throws RepositoryException {
+
+        copy(source, (Node) source.getSession().getItem(destParent), destName);
+
+        String dest = destParent + "/" + destName;
+        response.onCopied(source.getPath(), dest);
+        log.debug("copy {} to {}", source, dest);
+    }
+
+    /**
+     * Copy the <code>src</code> item into the <code>dstParent</code> node.
+     * The name of the newly created item is set to <code>name</code>.
+     *
+     * @param src The item to copy to the new location
+     * @param dstParent The node into which the <code>src</code> node is to be
+     *            copied
+     * @param name The name of the newly created item. If this is
+     *            <code>null</code> the new item gets the same name as the
+     *            <code>src</code> item.
+     * @throws RepositoryException May be thrown in case of any problem copying
+     *             the content.
+     * @see #copy(Node, Node, String)
+     * @see #copy(Property, Node, String)
+     */
+    static void copy(Item src, Node dstParent, String name)
+            throws RepositoryException {
+        if (src.isNode()) {
+            copy((Node) src, dstParent, name);
+        } else {
+            copy((Property) src, dstParent, name);
+        }
+    }
+
+    /**
+     * Copy the <code>src</code> node into the <code>dstParent</code> node.
+     * The name of the newly created node is set to <code>name</code>.
+     * <p>
+     * This method does a recursive (deep) copy of the subtree rooted at the
+     * source node to the destination. Any protected child nodes and and
+     * properties are not copied.
+     *
+     * @param src The node to copy to the new location
+     * @param dstParent The node into which the <code>src</code> node is to be
+     *            copied
+     * @param name The name of the newly created node. If this is
+     *            <code>null</code> the new node gets the same name as the
+     *            <code>src</code> node.
+     * @throws RepositoryException May be thrown in case of any problem copying
+     *             the content.
+     */
+    static void copy(Node src, Node dstParent, String name)
+            throws RepositoryException {
+
+        // ensure destination name
+        if (name == null) {
+            name = src.getName();
+        }
+
+        // ensure new node creation
+        if (dstParent.hasNode(name)) {
+            dstParent.getNode(name).remove();
+        }
+
+        // create new node
+        Node dst = dstParent.addNode(name, src.getPrimaryNodeType().getName());
+        for (NodeType mix : src.getMixinNodeTypes()) {
+            dst.addMixin(mix.getName());
+        }
+
+        // copy the properties
+        for (PropertyIterator iter = src.getProperties(); iter.hasNext();) {
+            copy(iter.nextProperty(), dst, null);
+        }
+
+        // copy the child nodes
+        for (NodeIterator iter = src.getNodes(); iter.hasNext();) {
+            Node n = iter.nextNode();
+            if (!n.getDefinition().isProtected()) {
+                copy(n, dst, null);
+            }
+        }
+    }
+
+    /**
+     * Copy the <code>src</code> property into the <code>dstParent</code>
+     * node. The name of the newly created property is set to <code>name</code>.
+     * <p>
+     * If the source property is protected, this method does nothing.
+     *
+     * @param src The property to copy to the new location
+     * @param dstParent The node into which the <code>src</code> property is
+     *            to be copied
+     * @param name The name of the newly created property. If this is
+     *            <code>null</code> the new property gets the same name as the
+     *            <code>src</code> property.
+     * @throws RepositoryException May be thrown in case of any problem copying
+     *             the content.
+     */
+    static void copy(Property src, Node dstParent, String name)
+            throws RepositoryException {
+        if (!src.getDefinition().isProtected()) {
+            if (name == null) {
+                name = src.getName();
+            }
+
+            // ensure new property creation
+            if (dstParent.hasProperty(name)) {
+                dstParent.getProperty(name).remove();
+            }
+
+            if (src.getDefinition().isMultiple()) {
+                dstParent.setProperty(name, src.getValues());
+            } else {
+                dstParent.setProperty(name, src.getValue());
+            }
+        }
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/servlets/post/impl/operations/DeleteOperation.java b/src/main/java/org/apache/sling/servlets/post/impl/operations/DeleteOperation.java
new file mode 100644
index 0000000..e688b9b
--- /dev/null
+++ b/src/main/java/org/apache/sling/servlets/post/impl/operations/DeleteOperation.java
@@ -0,0 +1,68 @@
+/*
+ * 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.Iterator;
+
+import javax.jcr.Item;
+import javax.jcr.RepositoryException;
+
+import org.apache.sling.api.SlingHttpServletRequest;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ResourceNotFoundException;
+import org.apache.sling.api.servlets.HtmlResponse;
+import org.apache.sling.servlets.post.AbstractSlingPostOperation;
+
+/**
+ * The <code>DeleteOperation</code> class implements the
+ * {@link org.apache.sling.servlets.post.SlingPostConstants#OPERATION_DELETE delete}
+ * operation for the Sling default POST servlet.
+ */
+public class DeleteOperation extends AbstractSlingPostOperation {
+
+    @Override
+    protected void doRun(SlingHttpServletRequest request, HtmlResponse response)
+            throws RepositoryException {
+
+        Iterator<Resource> res = getApplyToResources(request);
+        if (res == null) {
+            
+            Resource resource = request.getResource();
+            Item item = resource.adaptTo(Item.class);
+            if (item == null) {
+                throw new ResourceNotFoundException("Missing source "
+                    + resource + " for delete");
+            }
+
+            item.remove();
+            response.onDeleted(resource.getPath());
+            
+        } else {
+            
+            while (res.hasNext()) {
+                Resource resource = res.next();
+                Item item = resource.adaptTo(Item.class);
+                if (item != null) {
+                    item.remove();
+                    response.onDeleted(resource.getPath());
+                }
+            }
+            
+        }
+
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/org/apache/sling/servlets/post/impl/operations/ModifyOperation.java b/src/main/java/org/apache/sling/servlets/post/impl/operations/ModifyOperation.java
new file mode 100644
index 0000000..b727dcb
--- /dev/null
+++ b/src/main/java/org/apache/sling/servlets/post/impl/operations/ModifyOperation.java
@@ -0,0 +1,685 @@
+/*
+ * 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.Item;
+import javax.jcr.Node;
+import javax.jcr.Property;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+
+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;
+
+/**
+ * The <code>ModifyOperation</code> class implements the default operation
+ * called by the Sling default POST servlet if no operation is requested by the
+ * client. This operation is able to create and/or modify content.
+ */
+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
+    protected 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);
+
+        // ensure root of new content
+        processCreate(session, reqProperties, response);
+
+        // write content from existing content (@Move/CopyFrom parameters)
+        processMoves(session, reqProperties, response);
+        processCopies(session, reqProperties, response);
+
+        // cleanup any old content (@Delete parameters)
+        processDeletes(session, reqProperties, response);
+
+        // write content from form
+        writeContent(session, reqProperties, response);
+
+        // order content
+        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(),
+                requireItemPathPrefix(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
+     */
+    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);
+        }
+
+    }
+
+    /**
+     * Moves all repository content listed as repository move source in the
+     * request properties to the locations indicated by the resource properties.
+     */
+    private void processMoves(Session session,
+            Map<String, RequestProperty> reqProperties, HtmlResponse response)
+            throws RepositoryException {
+
+        for (RequestProperty property : reqProperties.values()) {
+            if (property.hasRepositoryMoveSource()) {
+                processMovesCopiesInternal(property, true, session,
+                    reqProperties, response);
+            }
+        }
+    }
+
+    /**
+     * Copies all repository content listed as repository copy source in the
+     * request properties to the locations indicated by the resource properties.
+     */
+    private void processCopies(Session session,
+            Map<String, RequestProperty> reqProperties, HtmlResponse response)
+            throws RepositoryException {
+
+        for (RequestProperty property : reqProperties.values()) {
+            if (property.hasRepositoryCopySource()) {
+                processMovesCopiesInternal(property, false, session,
+                    reqProperties, response);
+            }
+        }
+    }
+
+    /**
+     * Internal implementation of the
+     * {@link #processCopies(Session, Map, HtmlResponse)} and
+     * {@link #processMoves(Session, Map, HtmlResponse)} methods taking into
+     * account whether the source is actually a property or a node.
+     * <p>
+     * Any intermediary nodes to the destination as indicated by the
+     * <code>property</code> path are created using the
+     * <code>reqProperties</code> as indications for required node types.
+     *
+     * @param property The {@link RequestProperty} identifying the source
+     *            content of the operation.
+     * @param isMove <code>true</code> if the source item is to be moved.
+     *            Otherwise the source item is just copied.
+     * @param session The repository session to use to access the content
+     * @param reqProperties All accepted request properties. This is used to
+     *            create intermediary nodes along the property path.
+     * @param response The <code>HtmlResponse</code> into which successfull
+     *            copies and moves as well as intermediary node creations are
+     *            recorded.
+     * @throws RepositoryException May be thrown if an error occurrs.
+     */
+    private void processMovesCopiesInternal(RequestProperty property,
+            boolean isMove, Session session,
+            Map<String, RequestProperty> reqProperties, HtmlResponse response)
+            throws RepositoryException {
+
+        String propPath = property.getPath();
+        String source = property.getRepositorySource();
+
+        // only continue here, if the source really exists
+        if (session.itemExists(source)) {
+
+            // if the destination item already exists, remove it
+            // first, otherwise ensure the parent location
+            if (session.itemExists(propPath)) {
+                session.getItem(propPath).remove();
+                response.onDeleted(propPath);
+            } else {
+                deepGetOrCreateNode(session, property.getParentPath(),
+                    reqProperties, response);
+            }
+
+            // move through the session and record operation
+            Item sourceItem = session.getItem(source);
+            if (sourceItem.isNode()) {
+
+                // node move/copy through session
+                if (isMove) {
+                    session.move(source, propPath);
+                } else {
+                    Node sourceNode = (Node) sourceItem;
+                    Node destParent = (Node) session.getItem(property.getParentPath());
+                    CopyOperation.copy(sourceNode, destParent,
+                        property.getName());
+                }
+
+            } else {
+
+                // property move manually
+                Property sourceProperty = (Property) sourceItem;
+
+                // create destination property
+                Node destParent = (Node) session.getItem(property.getParentPath());
+                CopyOperation.copy(sourceProperty, destParent, null);
+
+                // remove source property (if not just copying)
+                if (isMove) {
+                    sourceProperty.remove();
+                }
+            }
+
+            // make sure the property is not deleted even in case for a given
+            // property both @MoveFrom and @Delete is set
+            property.setDelete(false);
+
+            // record successful move
+            if (isMove) {
+                response.onMoved(source, propPath);
+            } else {
+                response.onCopied(source, propPath);
+            }
+        }
+    }
+
+    /**
+     * Removes all properties listed as {@link RequestProperty#isDelete()} from
+     * the repository.
+     *
+     * @param session The <code>javax.jcr.Session</code> used to access the
+     *            repository to delete the properties.
+     * @param reqProperties The map of request properties to check for
+     *            properties to be removed.
+     * @param response The <code>HtmlResponse</code> to be updated with
+     *            information on deleted properties.
+     * @throws RepositoryException Is thrown if an error occurrs checking or
+     *             removing properties.
+     */
+    private void processDeletes(Session session,
+            Map<String, RequestProperty> reqProperties, HtmlResponse response)
+            throws RepositoryException {
+
+        for (RequestProperty property : reqProperties.values()) {
+            if (property.isDelete()) {
+                String propPath = property.getPath();
+                if (session.itemExists(propPath)) {
+                    session.getItem(propPath).remove();
+                    response.onDeleted(propPath);
+                }
+            }
+        }
+
+    }
+
+    /**
+     * 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()) {
+            if (prop.hasValues()) {
+                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) {
+
+        boolean requireItemPrefix = requireItemPathPrefix(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;
+            }
+            // SLING-298: skip form encoding parameter
+            if (paramName.equals("_charset_")) {
+                continue;
+            }
+            // skip parameters that do not start with the save prefix
+            if (requireItemPrefix && !hasItemPathPrefix(paramName)) {
+                continue;
+            }
+
+            // ensure the paramName is an absolute property name
+            String propPath = toPropertyPath(paramName, response);
+
+            // @TypeHint example
+            // <input type="text" name="./age" />
+            // <input type="hidden" name="./age@TypeHint" value="long" />
+            // causes the setProperty using the 'long' property type
+            if (propPath.endsWith(SlingPostConstants.TYPE_HINT_SUFFIX)) {
+                RequestProperty prop = getOrCreateRequestProperty(
+                    reqProperties, propPath,
+                    SlingPostConstants.TYPE_HINT_SUFFIX);
+
+                final RequestParameter[] rp = e.getValue();
+                if (rp.length > 0) {
+                    prop.setTypeHintValue(rp[0].getString());
+                }
+
+                continue;
+            }
+
+            // @DefaultValue
+            if (propPath.endsWith(SlingPostConstants.DEFAULT_VALUE_SUFFIX)) {
+                RequestProperty prop = getOrCreateRequestProperty(
+                    reqProperties, propPath,
+                    SlingPostConstants.DEFAULT_VALUE_SUFFIX);
+
+                prop.setDefaultValues(e.getValue());
+
+                continue;
+            }
+
+            // SLING-130: VALUE_FROM_SUFFIX means take the value of this
+            // property from a different field
+            // @ValueFrom example:
+            // <input name="./Text@ValueFrom" type="hidden" value="fulltext" />
+            // causes the JCR Text property to be set to the value of the
+            // fulltext form field.
+            if (propPath.endsWith(SlingPostConstants.VALUE_FROM_SUFFIX)) {
+                RequestProperty prop = getOrCreateRequestProperty(
+                    reqProperties, propPath,
+                    SlingPostConstants.VALUE_FROM_SUFFIX);
+
+                // @ValueFrom params must have exactly one value, else ignored
+                if (e.getValue().length == 1) {
+                    String refName = e.getValue()[0].getString();
+                    RequestParameter[] refValues = request.getRequestParameters(refName);
+                    if (refValues != null) {
+                        prop.setValues(refValues);
+                    }
+                }
+
+                continue;
+            }
+
+            // SLING-458: Allow Removal of properties prior to update
+            // @Delete example:
+            // <input name="./Text@Delete" type="hidden" />
+            // causes the JCR Text property to be deleted before update
+            if (propPath.endsWith(SlingPostConstants.SUFFIX_DELETE)) {
+                RequestProperty prop = getOrCreateRequestProperty(
+                    reqProperties, propPath, SlingPostConstants.SUFFIX_DELETE);
+
+                prop.setDelete(true);
+
+                continue;
+            }
+
+            // SLING-455: @MoveFrom means moving content to another location
+            // @MoveFrom example:
+            // <input name="./Text@MoveFrom" type="hidden" value="/tmp/path" />
+            // causes the JCR Text property to be set by moving the /tmp/path
+            // property to Text.
+            if (propPath.endsWith(SlingPostConstants.SUFFIX_MOVE_FROM)) {
+                RequestProperty prop = getOrCreateRequestProperty(
+                    reqProperties, propPath,
+                    SlingPostConstants.SUFFIX_MOVE_FROM);
+
+                // @MoveFrom params must have exactly one value, else ignored
+                if (e.getValue().length == 1) {
+                    String sourcePath = e.getValue()[0].getString();
+                    prop.setRepositorySource(sourcePath, true);
+                }
+
+                continue;
+            }
+
+            // SLING-455: @CopyFrom means moving content to another location
+            // @CopyFrom example:
+            // <input name="./Text@CopyFrom" type="hidden" value="/tmp/path" />
+            // causes the JCR Text property to be set by copying the /tmp/path
+            // property to Text.
+            if (propPath.endsWith(SlingPostConstants.SUFFIX_COPY_FROM)) {
+                RequestProperty prop = getOrCreateRequestProperty(
+                    reqProperties, propPath,
+                    SlingPostConstants.SUFFIX_COPY_FROM);
+
+                // @MoveFrom params must have exactly one value, else ignored
+                if (e.getValue().length == 1) {
+                    String sourcePath = e.getValue()[0].getString();
+                    prop.setRepositorySource(sourcePath, false);
+                }
+
+                continue;
+            }
+
+            // plain property, create from values
+            RequestProperty prop = getOrCreateRequestProperty(reqProperties,
+                propPath, null);
+            prop.setValues(e.getValue());
+        }
+
+        return reqProperties;
+    }
+
+    /**
+     * Returns the <code>paramName</code> as an absolute (unnormalized)
+     * property path by prepending the response path (<code>response.getPath</code>)
+     * to the parameter name if not already absolute.
+     */
+    private String toPropertyPath(String paramName, HtmlResponse response) {
+        if (!paramName.startsWith("/")) {
+            paramName = response.getPath() + "/" + paramName;
+        }
+
+        return paramName;
+    }
+
+    /**
+     * Returns the request property for the given property path. If such a
+     * request property does not exist yet it is created and stored in the
+     * <code>props</code>.
+     *
+     * @param props The map of already seen request properties.
+     * @param paramName The absolute path of the property including the
+     *            <code>suffix</code> to be looked up.
+     * @param suffix The (optional) suffix to remove from the
+     *            <code>paramName</code> before looking it up.
+     * @return The {@link RequestProperty} for the <code>paramName</code>.
+     */
+    private RequestProperty getOrCreateRequestProperty(
+            Map<String, RequestProperty> props, String paramName, String suffix) {
+        if (suffix != null && paramName.endsWith(suffix)) {
+            paramName = paramName.substring(0, paramName.length()
+                - suffix.length());
+        }
+
+        RequestProperty prop = props.get(paramName);
+        if (prop == null) {
+            prop = new RequestProperty(paramName);
+            props.put(paramName, prop);
+        }
+
+        return prop;
+    }
+
+    /**
+     * 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;
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/servlets/post/impl/operations/MoveOperation.java b/src/main/java/org/apache/sling/servlets/post/impl/operations/MoveOperation.java
new file mode 100644
index 0000000..83707e7
--- /dev/null
+++ b/src/main/java/org/apache/sling/servlets/post/impl/operations/MoveOperation.java
@@ -0,0 +1,57 @@
+/*
+ * 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.Item;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+
+import org.apache.sling.api.servlets.HtmlResponse;
+
+/**
+ * The <code>MoveOperation</code> class implements the
+ * {@link org.apache.sling.servlets.post.SlingPostConstants#OPERATION_MOVE move}
+ * operation for the Sling default POST servlet.
+ */
+public class MoveOperation extends AbstractCopyMoveOperation {
+
+    @Override
+    protected String getOperationName() {
+        return "move";
+    }
+
+    @Override
+    protected void execute(HtmlResponse response, Item source,
+            String destParent, String destName) throws RepositoryException {
+
+        if (destName == null) {
+            destName = source.getName();
+        }
+
+        String sourcePath = source.getPath();
+        String destPath = destParent + "/" + destName;
+        Session session = source.getSession();
+
+        if (session.itemExists(destPath)) {
+            session.getItem(destPath).remove();
+        }
+
+        session.move(sourcePath, destPath);
+        response.onMoved(sourcePath, destPath);
+    }
+
+}
diff --git a/src/main/resources/META-INF/DISCLAIMER b/src/main/resources/META-INF/DISCLAIMER
new file mode 100644
index 0000000..90850c2
--- /dev/null
+++ b/src/main/resources/META-INF/DISCLAIMER
@@ -0,0 +1,7 @@
+Apache Sling is an effort undergoing incubation at The Apache Software Foundation (ASF),
+sponsored by the Apache Jackrabbit PMC. Incubation is required of all newly accepted
+projects until a further review indicates that the infrastructure, communications,
+and decision making process have stabilized in a manner consistent with other
+successful ASF projects. While incubation status is not necessarily a reflection of
+the completeness or stability of the code, it does indicate that the project has yet
+to be fully endorsed by the ASF.
\ No newline at end of file
diff --git a/src/main/resources/META-INF/LICENSE b/src/main/resources/META-INF/LICENSE
new file mode 100644
index 0000000..d645695
--- /dev/null
+++ b/src/main/resources/META-INF/LICENSE
@@ -0,0 +1,202 @@
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright [yyyy] [name of copyright owner]
+
+   Licensed 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.
diff --git a/src/main/resources/META-INF/NOTICE b/src/main/resources/META-INF/NOTICE
new file mode 100644
index 0000000..d26b355
--- /dev/null
+++ b/src/main/resources/META-INF/NOTICE
@@ -0,0 +1,8 @@
+Apache Sling Default Post Servlets
+Copyright 2008 The Apache Software Foundation
+
+Apache Sling is based on source code originally developed 
+by Day Software (http://www.day.com/).
+
+This product includes software developed at
+The Apache Software Foundation (http://www.apache.org/).
diff --git a/src/main/resources/OSGI-INF/metatype/metatype.properties b/src/main/resources/OSGI-INF/metatype/metatype.properties
new file mode 100644
index 0000000..1346cb6
--- /dev/null
+++ b/src/main/resources/OSGI-INF/metatype/metatype.properties
@@ -0,0 +1,46 @@
+#
+#  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.
+#
+
+
+#
+# This file contains localization strings for configuration labels and
+# descriptions as used in the metatype.xml descriptor generated by the
+# the Sling SCR plugin
+
+servlet.post.name = Sling POST Servlet
+servlet.post.description = The Sling POST Servlet is registered as the default \
+ servlet to handle POST requests in Sling.
+servlet.post.dateFormats.name = Date Format
+servlet.post.dateFormats.description = List SimpleDateFormat strings for date \
+ formats supported for parsing from request input to data fields. The default \
+ value is [ "EEE MMM dd yyyy HH:mm:ss 'GMT'Z", "yyyy-MM-dd'T'HH:mm:ss.SSSZ", \
+ "yyyy-MM-dd'T'HH:mm:ss", "yyyy-MM-dd", "dd.MM.yyyy HH:mm:ss", "dd.MM.yyyy" ].
+servlet.post.nodeNameHints.name = Node Name Hint Properties
+servlet.post.nodeNameHints.description = The list of properties whose values \
+ may be used to derive a name for newly created nodes. When handling a request \
+ to create a new node, the name of the node is automatically generated if the \
+ request URL ends with a star ("*") or a slash ("/"). In this case the request \
+ parameters listed in this configuration value may be used to create the name. \
+ Default value is [ "title", "jcr:title", "name", "description", \
+ "jcr:description", "abstract" ].
+servlet.post.nodeNameMaxLength.name = Maximum Node Name Length 
+servlet.post.nodeNameMaxLength.description = Maximum number of characters to \
+ use for automatically generated node names. The default value is 20. Note, \
+ that actual node names may be generated with at most 4 more characters if the \
+ numeric suffixes must be appended to make the name unique.
\ No newline at end of file
diff --git a/src/main/resources/system/sling.js b/src/main/resources/system/sling.js
new file mode 100644
index 0000000..2ba7381
--- /dev/null
+++ b/src/main/resources/system/sling.js
@@ -0,0 +1,420 @@
+/* 
+ * 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.
+ */
+
+/**
+ * 	The sling javascript client gives access to a JCR repository
+ *	from client-side java code, using the sling post servlet as a back-end.	   
+ *	 
+ * @version $Rev: $, $Date: 2007-03-27 16:30:52 +0200 (Tue, 27 Mar 2007) $
+ */
+
+var Sling = null;
+
+// start sling code scope
+(function() {
+
+	Sling = new Object();
+	Sling.NAME_OF_THIS_FILE = "sling.js";
+	
+	/** This method tries to figure out what to do with a page */
+	Sling.wizard = function() {
+	    //TODO add lots of magic here
+	    var form=document.getElementById("slingform");
+	    if (!form) form=document.forms[0];
+	    if (form) {
+	        var sp=new Object();
+	        sp.formElement=form;
+	        Sling.setupPage(sp);
+	    }
+	
+	}
+	/** Call this to merge sling data in an HTML page
+		TODO deprecate other functions
+	*/
+	Sling.setupPage = function(options) {
+	  var tree = Sling.getContent(Sling._getJsonUrl(),1);
+	  
+	  if(options.formElement) {
+		Sling._setFormValues(options.formElement,Sling._getJsonUrl(),tree);
+	  }
+	  
+	  if(options.displayElement) {
+	  	Sling.displayValues(options.displayElement,tree);
+	  }
+	}
+	
+	/**
+	 * HTTP GET XHR Helper
+	 * @param {String} url The URL
+	 * @return the XHR object, use .responseText for the data
+	 * @type String
+	 */
+	Sling.httpGet = function(url) {
+	    var httpcon = Sling.getXHR();
+	    if (httpcon) {
+			httpcon.open('GET', url, false);
+			httpcon.send(null);
+			return httpcon;
+	    } else {
+			return null;
+	    }
+	}
+	/**
+	 * Produces a "sort-of-json" string representation of a object
+	 * for debugging purposes only
+	 * @param {Object} obj The object
+	 * @param {int} level The indentation level
+	 * @return The result
+	 * @type String
+	 */
+	Sling.dumpObj = function(obj, level) {
+		var res="";
+		for (var a in obj) {
+			if (typeof(obj[a])!="object") {
+				res+=a+":"+obj[a]+"  ";
+			} else {
+				res+=a+": { ";
+				res+=Sling.dumpObj(obj[a])+"} ";
+			}
+		}
+		return (res);
+	}
+	
+	/** Produces an aggregate of get all the property names used
+	 * in a tree as a helper for table oriented display
+	 * @param {Object} obj The Content Tree object
+	 * @param {Object} names internal object used for collecting all
+	 *  the names during the recursion
+	 * @return An Array of names of properties that exist in a tree
+	 * @type Array
+	 */
+	Sling.getAllPropNames = function(obj, names) {
+		var root=false;
+	    if (!names) {
+	        names=new Object();
+	        root=true;
+	    }
+	    for (var a in obj) {
+			if (typeof(obj[a])!="object") {
+	            names[a]="1";
+			} else {
+				getAllPropNames(obj[a], names);
+			}
+	    }
+	    if (root) {
+	        var ar=new Array();
+	        var i=0;
+	        for (var a in ar) {
+	            ar[i]=a;
+	            i++;
+	        }
+	        names=ar;
+	    }
+	    return (names);
+	}
+	
+	/** Reads a tree of items given a maxlevel from the repository as JSON
+	 * @param {String} path Path into the current workspace
+	 * @param {int} maxlevel maximum depth to traverse to
+	 * @param {Array} filters filter only these properties
+	 * @return An Object tree of content nodes and properties, null if not found
+	 * @type Object
+	 */
+	Sling.getContent = function(path, maxlevels, filter) {
+	    var obj=new Object();
+	    if (!path)  {
+	        path=Sling.currentPath;
+	    }
+	    if (path.indexOf("/")==0) {
+			/*
+			this assumes that paths that start with a slash
+			are meant to be workspace paths rather than URLs
+			and therefore need some additions before they are sent
+			to the server
+			*/
+			if(maxlevels == "0" || maxlevels) {
+			  maxlevels = "." + maxlevels;
+			} else {
+			  maxlevels = "";
+			}
+			path=Sling.baseurl + path + maxlevels + ".json";
+		}
+	    //checking for a trailing "/*"
+	    if (path.indexOf("/*")>=0) return obj;
+	
+		// TODO for now we explicitely defeat caching on this...there must be a better way
+		// but in tests IE6 tends to cache too much
+		var passThroughCacheParam = "?clock=" + new Date().getTime();
+	    var res=Sling.httpGet(path + passThroughCacheParam + (maxlevels?"&maxlevels="+maxlevels:""));
+	    
+	    if(res.status == 200) {
+	    	var obj=Sling.evalString(res.responseText);
+			if (!filter) {
+				for (var a in obj) {
+					if (a.indexOf("jcr:")==0) delete(obj[a]);
+				}
+			}
+			return obj;
+	    }
+	    return null; 
+	}
+	
+	/** Remove content by path */
+	Sling.removeContent = function(path) {
+		var httpcon = Sling.getXHR();
+		if (httpcon) {
+			var params = ":delete="+path;
+			httpcon.open('POST', Sling.baseurl + path, false);
+
+			// Send the proper header information along with the request
+			httpcon.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
+			httpcon.setRequestHeader("Content-length", params.length);
+			httpcon.setRequestHeader("Connection", "close");
+			httpcon.send(params);
+			return httpcon;
+		} else {
+			return false;
+		}
+	}
+	
+	/** eval str, accepting various object delimiters */
+	Sling.evalString = function(str) {
+		var obj = null;
+	    if(str.indexOf('[')==0) {
+		    eval("obj="+str);
+	    } else if(str.indexOf('{')==0) {
+	        eval("obj="+str);
+	    } else {
+		    eval("obj={"+str+"}");
+		}
+		return obj;
+	}
+	 
+	/** Get "session info" from repository. Mainly answers the question: "Who am I"
+	 *  and "Which workspace am I logged into.
+	 * @return An Object tree containing the session information, null if server status <> 200
+	 * @type Object
+	 */
+	Sling.getSessionInfo = function() {
+	    var res=Sling.httpGet(Sling.baseurl+"/system/sling/info.sessionInfo.json");
+	    if(res.status == 200) {
+	    	return Sling.evalString(res.responseText);
+	    }
+	    return null;
+	}
+	
+	/** Replace extension in a path */
+	Sling._replaceExtension = function(path,newExtension) {
+		var i = path.lastIndexOf(".");
+		if(i >= 0) path = path.substring(0,i);
+		i = path.lastIndexOf(".");
+		if(i >= 0) path = path.substring(0,i);
+		return path + newExtension;
+	}
+	
+	/** Get the JSON data URL that for the current page
+	 *	(assuming a .extension for the current page, .html or something else)	
+	 */
+	Sling._getJsonUrl = function() {
+	  return Sling._replaceExtension(window.location.href,".json");
+	}
+	
+	/** Get the content repository path from the URL
+	 *	(assuming a .extension for the current page, .html or something else)
+	 */
+	Sling._getPath = function() {
+	
+	    var noextensions=Sling._replaceExtension(window.location.href,"");
+	    var path=noextensions.substring(Sling.baseurl.length);
+	    return (path);
+	}
+	
+	/** Display values inside a container: an element inside given container,
+	 *	with an id like ./stuff, has its innerHTML set to the value of stuff
+	 *	in the tree, if it exists.
+	 */
+	Sling.displayValues = function(container,tree) {
+	  if(!tree) {
+	    tree = Sling.getContent(Sling._getJsonUrl(),1);
+	  }
+	  
+	  var elements = container.getElementsByTagName("*"); 
+	  var toSet = new Array();
+	  for (var i = 0; i < elements.length; i++) { 
+	    var value = Sling._getElementValue(elements[i],tree);
+	    if(value) {
+	      toSet[toSet.length] = { e:elements[i], v:value };
+	    }
+	  }
+	  
+	  for(var i = 0; i < toSet.length; i++) {
+	    toSet[i].e.innerHTML = toSet[i].v;
+	  }
+	}
+	
+	/** If e has an ID that matches a property of tree, set e's innerHTML accordingly */
+	Sling._getElementValue = function(e,tree) {
+	  var id = e.getAttribute("id");
+	  if(id) {
+	    return tree[id.substring(2)];
+	  }
+	}
+	
+	  
+	/** Set form elements based on the tree of items passed into the method
+	 * @param {IdOrElement} form the Form element to set, or its id
+	 * @param {String} path passes a string specifying the path
+	 * @param {Object} tree optionally pass the content that you want the
+	 * form to be populated with. This assumes an item tree as returned by
+	 * getContent().
+	 * Returns an object indicating whether data was found on the server.
+	 *
+	 */
+	Sling._setFormValues = function(form, path, tree) {
+		var result = new Object();
+		
+	    /** TODO: deal with abolute paths?
+	     *  TODO: deal with @ValueFrom
+	     */
+	    if (!path) return;
+	
+	    form.setAttribute("action", path);
+	
+	    if (!tree) {
+			tree=Sling.getContent(path,1);
+	    }
+	
+	    var elems=form.elements;
+	    var i=0;
+	    formfieldprefix="";
+	
+	    while (elems.length > i) {
+	        var elem=elems[i];
+	        var a=elem.name;
+	        if (a.indexOf("./")==0) {
+	            formfieldprefix="./";
+	            break;
+	        }
+	        i++;
+	    }
+	
+	    var i=0;
+	    while (elems.length > i) {
+	        var elem=elems[i];
+	        var a=elem.name;
+	        
+			if (a.indexOf("/")==0) {
+				var nodepath=a.substring(0,a.lastIndexOf("/"));
+				var propname=a.substring(a.lastIndexOf("/")+1);
+				var node=Sling.getContent(nodepath);
+				var propval=node[propname];
+			} else if (a.indexOf(formfieldprefix)==0) {
+	            var propname=a.substring(formfieldprefix.length);
+				var propval=tree[propname];
+			}
+			
+			if (propval) {
+            	if (elem.type == "file") {
+            		// cannot edit uplodaded files for now
+                } else if (elem.type == "checkbox") {
+                    var vals;
+                    if (typeof(propval)=="object") vals=propval;
+                    else {
+                        vals=new Array();
+                        vals[0]=propval;
+                    }
+                    var j=0;
+                    while (vals.length > j) {
+                        if (vals[j] == elem.value) elem.checked=true;
+                        j++;
+                    }
+                 } else {
+                    elem.value=propval;
+                 }
+	        }
+	        i++;
+	    }
+	    
+	}
+	
+	/** return Path as specified as the URL Parameter
+	 *  @param URL
+	 *  @return The Path parameter isolated from the URL
+	 *  @type String
+	 */
+	Sling.TODO_NOT_USED_isolatePathFromUrl = function(url) {
+	  var pattern = "[\\?&]Path=([^&#]*)";
+	  var regex = new RegExp( pattern );
+	  var results = regex.exec( url );
+	  if( results == null )
+	        // none found
+	        return "";
+	  else
+	        // found
+	        return unescape(results[1]);
+	}
+	
+	/**
+	 *	Get an XMLHttpRequest in a portable way
+	 *		
+	 */
+	Sling.getXHR = function () {
+		var xhr=null;
+		
+		if(!xhr) {
+			try {
+				// built-in (firefox, recent Opera versions, etc)
+				xhr=new XMLHttpRequest();
+			} catch (e) {
+				// ignore
+			}
+		}
+		
+		if(!xhr) {
+			try {
+				// IE, newer versions
+				xhr=new ActiveXObject("Msxml2.XMLHTTP");
+			} catch (e) {
+				// ignore
+			}
+		}
+		
+		if(!xhr) {
+			try {
+				// IE, older versions
+				xhr=new ActiveXObject("Microsoft.XMLHTTP");
+			} catch (e) {
+				// ignore
+			}
+		}
+		
+		if(!xhr) {
+			alert("Unable to access XMLHttpRequest object, sling will not work!");
+		}
+		
+		return xhr;
+	}
+	
+	// obtain the base_url to communicate with sling on the server
+	var scripts = document.getElementsByTagName("SCRIPT")
+	var script = scripts[scripts.length-1].src
+	Sling.baseurl = script.substring(0,script.length - ("/" + Sling.NAME_OF_THIS_FILE.length));
+	Sling.currentPath = Sling._getPath();
+	Sling.isNew  = (Sling.currentPath.indexOf("/*")>=0)?true:false;
+
+// end sling code scope	
+})();
diff --git a/src/test/java/org/apache/sling/servlets/post/impl/NodeNameFilterTest.java b/src/test/java/org/apache/sling/servlets/post/impl/NodeNameFilterTest.java
new file mode 100644
index 0000000..8039ca4
--- /dev/null
+++ b/src/test/java/org/apache/sling/servlets/post/impl/NodeNameFilterTest.java
@@ -0,0 +1,54 @@
+/*
+ * 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.servlets.post.impl.helper.NodeNameFilter;
+
+public class NodeNameFilterTest extends TestCase {
+    private final NodeNameFilter filter = new NodeNameFilter();
+    
+    protected void runTest(String [] data) {
+        for(int i=0; i < data.length; i++) {
+            final String input = data[i];
+            i++;
+            final String expected = data[i];
+            final String actual = filter.filter(input);
+            assertEquals(expected, actual);
+        }
+    }
+    
+    public void testBasicFiltering() {
+        final String [] data = {
+                "test", "test",
+                "t?st", "t_st",
+                "t??st", "t_st"
+        };
+        
+        runTest(data);
+    }
+    
+    public void testNoInitialNumber() {
+        final String [] data = {
+                "1234", "_1234",
+                "1", "_1"
+        };
+        
+        runTest(data);
+    }
+}
\ No newline at end of file
diff --git a/src/test/java/org/apache/sling/servlets/post/impl/SlingPostServletTest.java b/src/test/java/org/apache/sling/servlets/post/impl/SlingPostServletTest.java
new file mode 100644
index 0000000..3430c12
--- /dev/null
+++ b/src/test/java/org/apache/sling/servlets/post/impl/SlingPostServletTest.java
@@ -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;
+        }
+    }
+}