You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jackrabbit.apache.org by an...@apache.org on 2009/02/04 15:13:25 UTC

svn commit: r740749 [1/2] - in /jackrabbit/trunk/jackrabbit-jcr-server/src: main/java/org/apache/jackrabbit/server/remoting/ main/java/org/apache/jackrabbit/server/remoting/davex/ main/java/org/apache/jackrabbit/server/util/ test/java/org/apache/jackra...

Author: angela
Date: Wed Feb  4 14:13:24 2009
New Revision: 740749

URL: http://svn.apache.org/viewvc?rev=740749&view=rev
Log:
- JCR-1958: Enhanced JCR remoting (work in progress)

Added:
    jackrabbit/trunk/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/server/remoting/
    jackrabbit/trunk/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/server/remoting/davex/
    jackrabbit/trunk/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/server/remoting/davex/BatchReadConfig.java   (with props)
    jackrabbit/trunk/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/server/remoting/davex/DiffException.java   (with props)
    jackrabbit/trunk/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/server/remoting/davex/DiffHandler.java   (with props)
    jackrabbit/trunk/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/server/remoting/davex/DiffParser.java   (with props)
    jackrabbit/trunk/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/server/remoting/davex/JcrRemotingServlet.java   (with props)
    jackrabbit/trunk/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/server/remoting/davex/JsonDiffHandler.java   (with props)
    jackrabbit/trunk/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/server/remoting/davex/JsonWriter.java   (with props)
    jackrabbit/trunk/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/server/util/
    jackrabbit/trunk/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/server/util/HttpMultipartPost.java   (with props)
    jackrabbit/trunk/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/server/util/RequestData.java   (with props)
    jackrabbit/trunk/jackrabbit-jcr-server/src/test/java/org/apache/jackrabbit/server/
    jackrabbit/trunk/jackrabbit-jcr-server/src/test/java/org/apache/jackrabbit/server/remoting/
    jackrabbit/trunk/jackrabbit-jcr-server/src/test/java/org/apache/jackrabbit/server/remoting/davex/
    jackrabbit/trunk/jackrabbit-jcr-server/src/test/java/org/apache/jackrabbit/server/remoting/davex/DiffParserTest.java   (with props)
    jackrabbit/trunk/jackrabbit-jcr-server/src/test/java/org/apache/jackrabbit/server/remoting/davex/TestAll.java   (with props)

Added: jackrabbit/trunk/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/server/remoting/davex/BatchReadConfig.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/server/remoting/davex/BatchReadConfig.java?rev=740749&view=auto
==============================================================================
--- jackrabbit/trunk/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/server/remoting/davex/BatchReadConfig.java (added)
+++ jackrabbit/trunk/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/server/remoting/davex/BatchReadConfig.java Wed Feb  4 14:13:24 2009
@@ -0,0 +1,159 @@
+/*
+ * 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.jackrabbit.server.remoting.davex;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+import java.util.Map;
+import java.util.HashMap;
+import java.util.Properties;
+import java.util.Enumeration;
+import java.io.InputStream;
+import java.io.IOException;
+
+/**
+ * <code>BatchReadConfig</code> defines if and how deep child item
+ * information should be retrieved, when accessing a <code>Node</code>.
+ * The configuration is based on node type names.
+ */
+class BatchReadConfig {
+
+    private static Logger log = LoggerFactory.getLogger(BatchReadConfig.class);
+
+    public static final int DEPTH_DEFAULT = 0;
+    public static final int DEPTH_INFINITE = -1;
+
+    private int defaultDepth = DEPTH_INFINITE;
+    private final Map depthMap = new HashMap();
+
+    /**
+     * Create an empty batch-read config.
+     */
+    public BatchReadConfig() {
+
+    }
+
+    /**
+     * Load the batch read configuration.
+     * 
+     * @param in An input stream.
+     * @throws IOException If an error occurs.
+     */
+    public void load(InputStream in) throws IOException {
+        Properties props = new Properties();
+        props.load(in);
+        add(props);
+    }
+
+    public void add(Properties props) {
+        for (Enumeration en = props.propertyNames(); en.hasMoreElements();) {
+            String name = en.nextElement().toString();
+            String depth = props.getProperty(name);
+            try {
+                Integer intg = new Integer(depth);
+                depthMap.put(name, intg);
+            } catch (NumberFormatException e) {
+                // invalid entry in the properties file -> ignore
+                log.warn("Invalid depth value for name " + name + ". " + depth + " cannot be parsed into an integer.");
+            }
+        }
+    }
+
+    /**
+     * Return the depth for the given node type name. If the name is
+     * not defined in this configuration, the {@link #DEPTH_DEFAULT default value}
+     * is returned.
+     *
+     * @param ntName The jcr name of the node type.
+     * @return {@link #DEPTH_INFINITE -1} If all child infos should be return or
+     * any value greater than {@link #DEPTH_DEFAULT 0} if only parts of the
+     * subtree should be returned. If the given nodetype name is not defined
+     * in this configuration, the default depth {@link #DEPTH_DEFAULT 0} will
+     * be returned.
+     */
+    public int getDepth(String ntName) {
+        if (depthMap.containsKey(ntName)) {
+            return ((Integer) (depthMap.get(ntName))).intValue();
+        } else {
+            return DEPTH_DEFAULT;
+        }
+    }
+
+    /**
+     * Return the depth for the given node or the
+     * {@link #DEPTH_DEFAULT default value} if the config does not provide
+     * an specific entry for the given node..
+     *
+     * @param node The node for with depth information should be retrieved.
+     * @return {@link #DEPTH_INFINITE -1} If all child infos should be return or
+     * any value greater than {@link #DEPTH_DEFAULT 0} if only parts of the
+     * subtree should be returned.
+     */
+    public int getDepth(Node node) {
+        int depth = DEPTH_DEFAULT;
+        try {
+            String ntName = node.getPrimaryNodeType().getName();
+            if (depthMap.containsKey(ntName)) {
+                depth = ((Integer) (depthMap.get(ntName))).intValue();
+            }
+        } catch (RepositoryException e) {
+            // ignore and return default.
+        }
+        return depth;
+    }
+
+    /**
+     * Define the batch-read depth for the given node type name.
+     *
+     * @param ntName jcr name of the node type for which <code>depth</code> is defined.
+     * @param depth Depth for the specified node type name.
+     * @throws IllegalArgumentException if <code>ntName</code> is <code>null</code>
+     * or <code>depth</code> is lower than {@link #DEPTH_INFINITE}.
+     */
+    public void setDepth(String ntName, int depth) {
+        if (ntName == null || depth < DEPTH_INFINITE) {
+            throw new IllegalArgumentException();
+        }
+        depthMap.put(ntName, new Integer(depth));
+    }
+
+    /**
+     * Returns the default depth.
+     *
+     * @return  the default depth.
+     */
+    public int getDefaultDepth() {
+        return defaultDepth;
+    }
+
+    /**
+     * Set the default depth.
+     *
+     * @param depth The default depth.
+     * @throws IllegalArgumentException if <code>depth</code> is lower than
+     * {@link #DEPTH_INFINITE}.
+     */
+    public void setDefaultDepth(int depth) {
+        if (depth < -1) {
+            throw new IllegalArgumentException();
+        }
+        defaultDepth = depth;
+    }
+}

Propchange: jackrabbit/trunk/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/server/remoting/davex/BatchReadConfig.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: jackrabbit/trunk/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/server/remoting/davex/BatchReadConfig.java
------------------------------------------------------------------------------
    svn:keywords = author date id revision url

Added: jackrabbit/trunk/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/server/remoting/davex/DiffException.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/server/remoting/davex/DiffException.java?rev=740749&view=auto
==============================================================================
--- jackrabbit/trunk/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/server/remoting/davex/DiffException.java (added)
+++ jackrabbit/trunk/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/server/remoting/davex/DiffException.java Wed Feb  4 14:13:24 2009
@@ -0,0 +1,31 @@
+/*
+ * 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.jackrabbit.server.remoting.davex;
+
+/**
+ * <code>DiffException</code>...
+ */
+class DiffException extends Exception {
+
+    public DiffException(String message) {
+        super(message);
+    }
+
+    public DiffException(String message, Throwable cause) {
+        super(message, cause);
+    }
+}

Propchange: jackrabbit/trunk/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/server/remoting/davex/DiffException.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: jackrabbit/trunk/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/server/remoting/davex/DiffException.java
------------------------------------------------------------------------------
    svn:keywords = author date id revision url

Added: jackrabbit/trunk/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/server/remoting/davex/DiffHandler.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/server/remoting/davex/DiffHandler.java?rev=740749&view=auto
==============================================================================
--- jackrabbit/trunk/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/server/remoting/davex/DiffHandler.java (added)
+++ jackrabbit/trunk/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/server/remoting/davex/DiffHandler.java Wed Feb  4 14:13:24 2009
@@ -0,0 +1,31 @@
+/*
+ * 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.jackrabbit.server.remoting.davex;
+
+/**
+ * <code>DiffHandler</code>...
+ */
+interface DiffHandler {
+
+    void addNode(String targetPath, String diffValue) throws DiffException;
+
+    void setProperty(String targetPath, String diffValue) throws DiffException;
+
+    void remove(String targetPath, String diffValue) throws DiffException;
+
+    void move(String targetPath, String diffValue) throws DiffException;
+}

Propchange: jackrabbit/trunk/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/server/remoting/davex/DiffHandler.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: jackrabbit/trunk/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/server/remoting/davex/DiffHandler.java
------------------------------------------------------------------------------
    svn:keywords = author date id revision url

Added: jackrabbit/trunk/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/server/remoting/davex/DiffParser.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/server/remoting/davex/DiffParser.java?rev=740749&view=auto
==============================================================================
--- jackrabbit/trunk/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/server/remoting/davex/DiffParser.java (added)
+++ jackrabbit/trunk/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/server/remoting/davex/DiffParser.java Wed Feb  4 14:13:24 2009
@@ -0,0 +1,237 @@
+/*
+ * 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.jackrabbit.server.remoting.davex;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.Reader;
+import java.io.StringReader;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+
+/** <code>DiffParser</code>... */
+class DiffParser {
+
+    // TODO: review again: currently all line-sep. chars before an diff-char are
+    // TODO: ignored unless they are escaped in way the handler understands (e.g.
+    // TODO: JSON does: \\r for \r).
+    // TODO: in contrast line sep. at the end of the string are treated as value.
+    // TODO: ... similar: line sep. following by non-diff sybol.
+
+    private final DiffHandler handler;
+
+    private static final int EOF = -1;
+
+    private static final char SYMBOL_ADD_NODE = '+';
+    private static final char SYMBOL_MOVE = '>';
+    private static final char SYMBOL_REMOVE = '-';
+    private static final char SYMBOL_SET_PROPERTY = '^';
+
+    private static final int STATE_START_LINE = 0;
+    private static final int STATE_START_TARGET = 1;
+    private static final int STATE_TARGET = 2;
+    private static final int STATE_START_VALUE = 3;
+    private static final int STATE_VALUE = 4;
+
+    /**
+     *
+     * @param handler
+     */
+    public DiffParser(DiffHandler handler) {
+        this.handler = handler;
+    }
+
+    /**
+     *
+     * @param str
+     * @throws IOException
+     * @throws DiffException
+     */
+    public void parse(String str) throws IOException, DiffException {
+        parse(new BufferedReader(new StringReader(str)));
+    }
+
+    /**
+     *
+     * @param input
+     * @param charSetName
+     * @throws IOException
+     * @throws DiffException
+     */
+    public void parse(InputStream input, String charSetName) throws IOException, DiffException {
+        parse(new BufferedReader(new InputStreamReader(input, charSetName)));
+    }
+
+    /**
+     *
+     * @param reader
+     * @throws IOException
+     * @throws DiffException
+     */
+    public void parse(Reader reader) throws IOException, DiffException {
+        int action = -1;
+        String path = null;
+
+        StringBuffer lineSeparator = null;
+        StringBuffer bf = null;
+
+        int state = STATE_START_LINE;
+        int next = reader.read();
+
+        while (next != EOF) {
+            switch (state) {
+                case STATE_START_LINE:
+                    if (isSymbol(next)) {
+                        // notify the last action read
+                        if (action > -1) {
+                            informAction(action, path, bf);
+                        }
+                        // ... and  start recording the next action
+                        action = next;
+                        bf = null;
+                        lineSeparator = null;
+                        state = STATE_START_TARGET;
+                    } else if (isLineSeparator(next)) {
+                        // still line-separator -> append c to the lineSeparator
+                        // buffer and keep state set to STATE_START_LINE
+                        if (lineSeparator == null) {
+                            throw new DiffException("Invalid start of new line.");
+                        } else {
+                            lineSeparator.append((char) next);
+                        }
+                    } else if (lineSeparator != null && bf != null) {
+                        // append the collected return/linefeed chars as part
+                        // of the value read and continued reading value.
+                        bf.append(lineSeparator);
+                        bf.append((char) next);
+                        lineSeparator = null;
+                        state = STATE_VALUE;
+                    } else {
+                        throw new DiffException("Invalid start of new line.");
+                    }
+                    break;
+
+                case STATE_START_TARGET:
+                    if (Character.isWhitespace((char) next) || next == ':') {
+                        throw new DiffException("Invalid start of target path '" + next + "'");
+                    }
+                    bf = new StringBuffer();
+                    bf.append((char) next);
+                    state = STATE_TARGET;
+                    break;
+
+                case STATE_TARGET:
+                    if (Character.isWhitespace((char) next) && endsWithDelim(bf)) {
+                        // a sequence of 'wsp:wsp' indicates the delimiter between
+                        // the target path and the diff value.
+                        path = bf.substring(0, bf.lastIndexOf(":")).trim();
+                        state = STATE_START_VALUE;
+                        // reset buffer
+                        bf = null;
+                    } else {
+                        // continue reading the path into the buffer.
+                        bf.append((char) next);
+                    }
+                    break;
+
+                case STATE_START_VALUE:
+                    if (isLineSeparator(next)) {
+                        lineSeparator = new StringBuffer();
+                        lineSeparator.append((char) next);
+                        bf = new StringBuffer();
+                        state = STATE_START_LINE;
+                    } else {
+                        bf = new StringBuffer();
+                        bf.append((char) next);
+                        state = STATE_VALUE;
+                    }
+                    break;
+
+                case STATE_VALUE:
+                    if (isLineSeparator(next)) {
+                        lineSeparator = new StringBuffer();
+                        lineSeparator.append((char) next);
+                        state = STATE_START_LINE;
+                    } else {
+                        bf.append((char) next);
+                        // keep state set to STATE_VALUE
+                    }
+                    break;
+
+            }
+            // read the next character.
+            next = reader.read();
+        }
+
+        // a diff ending after a command or within the target is invalid.
+        if (state == STATE_START_TARGET || state == STATE_TARGET) {
+            throw new DiffException("Invalid end of DIFF string: missing separator and value.");
+        }
+        if (state == STATE_START_VALUE ) {
+            // line separator AND buffer must be null
+            if (!(lineSeparator == null && bf == null)) {
+                throw new DiffException("Invalid end of DIFF string.");
+            }
+        }
+
+        // append eventual remaining line-searators to the value
+        if (lineSeparator != null) {
+            bf.append(lineSeparator);
+        }
+        // notify the last action read
+        informAction(action, path, bf);
+    }
+
+    private void informAction(int action, String path, StringBuffer diffVal) throws DiffException {
+        if (path == null) {
+            throw new DiffException("Missing path for action " + action + "(diffValue = '"+ diffVal +"')");
+        }
+        String value = (diffVal == null) ? null : diffVal.toString();
+        switch (action) {
+            case SYMBOL_ADD_NODE:
+                handler.addNode(path, value);
+                break;
+            case SYMBOL_SET_PROPERTY:
+                handler.setProperty(path, value);
+                break;
+            case SYMBOL_MOVE:
+                handler.move(path, value);
+                break;
+            case SYMBOL_REMOVE:
+                handler.remove(path, value);
+                break;
+            default:
+                throw new DiffException("Invalid action " + action);
+        }
+    }
+
+    private static boolean isSymbol(int c) {
+        return c == SYMBOL_ADD_NODE || c == SYMBOL_SET_PROPERTY || c == SYMBOL_MOVE || c == SYMBOL_REMOVE;
+    }
+
+    private static boolean isLineSeparator(int c) {
+        return c == '\n' || c == '\r';
+
+    }
+    private static boolean endsWithDelim(StringBuffer bf) {
+        if (bf.length() < 2) {
+            return false;
+        } else {
+            return ':' == bf.charAt(bf.length()-1) && Character.isWhitespace(bf.charAt(bf.length()-2));
+        }
+    }
+}

Propchange: jackrabbit/trunk/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/server/remoting/davex/DiffParser.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: jackrabbit/trunk/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/server/remoting/davex/DiffParser.java
------------------------------------------------------------------------------
    svn:keywords = author date id revision url

Added: jackrabbit/trunk/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/server/remoting/davex/JcrRemotingServlet.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/server/remoting/davex/JcrRemotingServlet.java?rev=740749&view=auto
==============================================================================
--- jackrabbit/trunk/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/server/remoting/davex/JcrRemotingServlet.java (added)
+++ jackrabbit/trunk/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/server/remoting/davex/JcrRemotingServlet.java Wed Feb  4 14:13:24 2009
@@ -0,0 +1,507 @@
+/*
+ * 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.jackrabbit.server.remoting.davex;
+
+import org.apache.jackrabbit.webdav.DavException;
+import org.apache.jackrabbit.webdav.DavLocatorFactory;
+import org.apache.jackrabbit.webdav.DavMethods;
+import org.apache.jackrabbit.webdav.DavResource;
+import org.apache.jackrabbit.webdav.DavResourceLocator;
+import org.apache.jackrabbit.webdav.DavServletResponse;
+import org.apache.jackrabbit.webdav.DavSession;
+import org.apache.jackrabbit.webdav.WebdavRequest;
+import org.apache.jackrabbit.webdav.WebdavResponse;
+import org.apache.jackrabbit.webdav.version.DeltaVConstants;
+import org.apache.jackrabbit.webdav.jcr.JcrDavException;
+import org.apache.jackrabbit.webdav.jcr.JcrDavSession;
+import org.apache.jackrabbit.webdav.jcr.JCRWebdavServerServlet;
+import org.apache.jackrabbit.util.Text;
+import org.apache.jackrabbit.JcrConstants;
+import org.apache.jackrabbit.server.util.RequestData;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.jcr.Item;
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.Workspace;
+import javax.jcr.ItemNotFoundException;
+import javax.jcr.NodeIterator;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletResponse;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Iterator;
+
+/** <code>JcrRemotingServlet</code>... */
+public abstract class JcrRemotingServlet extends JCRWebdavServerServlet {
+
+    private static Logger log = LoggerFactory.getLogger(JcrRemotingServlet.class);
+
+    /**
+     * the home init parameter. other relative filesystem paths are
+     * relative to this location.
+     */
+    public static final String INIT_PARAM_HOME = "home";
+    /**
+     * the 'temp-directory' init parameter
+     */
+    public static final String INIT_PARAM_TMP_DIRECTORY = "temp-directory";
+    /**
+     * temp-dir attribute to be set to the servlet-context
+     */
+    public static final String ATTR_TMP_DIRECTORY = "remoting-servlet.tmpdir";
+
+    /**
+     * the 'temp-directory' init parameter
+     */
+    public static final String INIT_PARAM_BATCHREAD_CONFIG = "batchread-config";
+
+    private static final String PARAM_DIFF = ":diff";
+    private static final String PARAM_COPY = ":copy";
+    private static final String PARAM_CLONE = ":clone";
+
+    private BatchReadConfig brConfig;
+
+    public void init() throws ServletException {
+        super.init();
+
+        brConfig = new BatchReadConfig();
+        String brConfigParam = getServletConfig().getInitParameter(INIT_PARAM_BATCHREAD_CONFIG);
+        if (brConfigParam == null) {
+            log.debug("batchread-config missing -> initialize defaults.");
+            brConfig.setDepth("nt:file", BatchReadConfig.DEPTH_INFINITE);
+            brConfig.setDepth("nt:folder", 1);
+            brConfig.setDefaultDepth(5);
+        } else {
+            try {
+                InputStream in = getServletContext().getResourceAsStream(brConfigParam);
+                if (in != null) {
+                    brConfig.load(in);
+                }
+            } catch (IOException e) {
+                log.debug("Unable to build resource filter provider.");
+            }
+        }
+
+        // setup home directory
+        String paramHome = getServletConfig().getInitParameter(INIT_PARAM_HOME);
+        if (paramHome == null) {
+            log.debug("missing init-param " + INIT_PARAM_HOME + ". using default: jr_home");
+            paramHome = "jr_home";
+        }
+        File home;
+        try {
+            home = new File(paramHome).getCanonicalFile();
+        } catch (IOException e) {
+            throw new ServletException(INIT_PARAM_HOME + " invalid." + e.toString());
+        }
+        home.mkdirs();
+
+        String tmp = getServletConfig().getInitParameter(INIT_PARAM_TMP_DIRECTORY);
+        if (tmp == null) {
+            log.warn("No " + INIT_PARAM_TMP_DIRECTORY + " specified. using 'tmp'");
+            tmp = "tmp";
+        }
+        File tmpDirectory = new File(home, tmp);
+        tmpDirectory.mkdirs();
+        log.info("  temp-directory = " + tmpDirectory.getPath());
+        getServletContext().setAttribute(ATTR_TMP_DIRECTORY, tmpDirectory);
+
+        // force usage of custom locator factory.
+        super.setLocatorFactory(new DavLocatorFactoryImpl(getInitParameter(INIT_PARAM_RESOURCE_PATH_PREFIX)));
+    }
+
+    protected void doGet(WebdavRequest webdavRequest,
+                         WebdavResponse webdavResponse,
+                         DavResource davResource) throws IOException, DavException {
+        if (canHandle(DavMethods.DAV_GET, webdavRequest, davResource)) {
+            // return json representation of the requested resource
+            try {
+                Item item = ((JcrDavSession) webdavRequest.getDavSession()).getRepositorySession().getItem(davResource.getLocator().getRepositoryPath());
+                if (item.isNode()) {
+                    webdavResponse.setContentType("text/plain;charset=utf-8");
+                    webdavResponse.setStatus(DavServletResponse.SC_OK);
+                    
+                    JsonWriter writer = new JsonWriter(webdavResponse.getWriter());
+                    int depth = ((WrappingLocator) davResource.getLocator()).depth;
+                    if (depth < BatchReadConfig.DEPTH_INFINITE) {
+                        depth = getDepth((Node) item);
+                    }
+                    writer.write((Node) item, depth);
+                } else {
+                    // properties cannot be requested as json object.
+                    throw new JcrDavException(new ItemNotFoundException("No node at " + item.getPath()), DavServletResponse.SC_NOT_FOUND);
+                }
+            } catch (RepositoryException e) {
+                // should only get here if the item does not exist.
+                log.debug(e.getMessage());
+                throw new JcrDavException(e);
+            }
+        } else {
+            super.doGet(webdavRequest, webdavResponse, davResource);
+        }
+    }
+
+    protected void doPost(WebdavRequest webdavRequest, WebdavResponse webdavResponse, DavResource davResource)
+            throws IOException, DavException {
+        if (canHandle(DavMethods.DAV_POST, webdavRequest, davResource)) {
+            // special remoting request: the defined parameters are exclusive
+            // and cannot be combined.
+            Session session = getRepositorySession(webdavRequest);
+            RequestData data = new RequestData(webdavRequest, getTempDirectory(getServletContext()));
+            String loc = null;
+            try {
+                String[] pValues;
+                if ((pValues = data.getParameterValues(PARAM_CLONE)) != null) {
+                    loc = clone(session, pValues, davResource.getLocator());
+                } else if ((pValues = data.getParameterValues(PARAM_COPY)) != null) {
+                    loc = copy(session, pValues, davResource.getLocator());
+                } else if (data.getParameterValues(PARAM_DIFF) != null) {
+                    String targetPath = davResource.getLocator().getRepositoryPath();
+                    processDiff(session, targetPath, data);
+                } else {
+                    String targetPath = davResource.getLocator().getRepositoryPath();
+                    loc = modifyContent(session, targetPath, data);
+                }
+
+                // TODO: append entity
+                if (loc == null) {
+                    webdavResponse.setStatus(HttpServletResponse.SC_OK);
+                } else {
+                    webdavResponse.setHeader(DeltaVConstants.HEADER_LOCATION, loc);
+                    webdavResponse.setStatus(HttpServletResponse.SC_CREATED);
+                }
+            } catch (RepositoryException e) {
+                log.warn(e.getMessage());
+                throw new JcrDavException(e);
+            } catch (DiffException e) {
+                log.warn(e.getMessage());
+                Throwable cause = e.getCause();
+                if (cause instanceof RepositoryException) {
+                    throw new JcrDavException((RepositoryException) cause);
+                } else {
+                    throw new DavException(DavServletResponse.SC_BAD_REQUEST, "Invalid diff format.");
+                }
+            } finally {
+                data.dispose();
+            }
+        } else {
+            super.doPost(webdavRequest, webdavResponse, davResource);
+        }
+    }
+
+    private boolean canHandle(int methodCode, WebdavRequest request, DavResource davResource) {
+        DavResourceLocator locator = davResource.getLocator();
+        switch (methodCode) {
+            case DavMethods.DAV_GET:
+                return davResource.exists() && ((WrappingLocator) locator).isJson;
+            case DavMethods.DAV_POST:
+                String ct = request.getContentType();
+                return ct.startsWith("multipart/form-data") ||
+                       ct.startsWith("application/x-www-form-urlencoded");
+            default:
+                return false;
+        }
+    }
+
+    private int getDepth(Node node) throws RepositoryException {
+        return brConfig.getDepth(node.getPrimaryNodeType().getName());
+    }
+
+    private static String clone(Session session, String[] cloneArgs, DavResourceLocator reqLocator) throws RepositoryException {
+        Workspace wsp = session.getWorkspace();
+        String destPath = null;
+        for (int i = 0; i < cloneArgs.length; i++) {
+            String[] args = cloneArgs[i].split(",");
+            if (args.length == 4) {
+                wsp.clone(args[0], args[1], args[2], new Boolean(args[3]).booleanValue());
+                destPath = args[2];
+            } else {
+                throw new RepositoryException(":clone parameter must have a value consisting of the 4 args needed for a Workspace.clone() call.");
+            }
+        }
+        return buildLocationHref(session, destPath, reqLocator);
+    }
+
+    private static String copy(Session session, String[] copyArgs, DavResourceLocator reqLocator) throws RepositoryException {
+        Workspace wsp = session.getWorkspace();
+        String destPath = null;
+        for (int i = 0; i < copyArgs.length; i++) {
+            String[] args = copyArgs[i].split(",");
+            switch (args.length) {
+                case 2:
+                    wsp.copy(args[0], args[1]);
+                    destPath = args[1];
+                    break;
+                case 3:
+                    wsp.copy(args[0], args[1], args[2]);
+                    destPath = args[2];
+                    break;
+                default:
+                    throw new RepositoryException(":copy parameter must have a value consisting of 2 jcr paths or workspaceName plus 2 jcr paths separated by ','.");
+            }
+        }
+        return buildLocationHref(session, destPath, reqLocator);
+    }
+
+    private static String buildLocationHref(Session s, String destPath, DavResourceLocator reqLocator) throws RepositoryException {
+        if (destPath != null) {
+            NodeIterator it = s.getRootNode().getNodes(destPath.substring(1));
+            Node n = null;
+            while (it.hasNext()) {
+                n = it.nextNode();
+            }
+            if (n != null) {
+                DavResourceLocator loc = reqLocator.getFactory().createResourceLocator(reqLocator.getPrefix(), reqLocator.getWorkspacePath(), n.getPath(), false);
+                return loc.getHref(true);
+            }
+        }
+
+        // unable to determine -> no location header sent back.
+        return null;
+    }
+
+    private static void processDiff(Session session, String targetPath, RequestData data)
+            throws RepositoryException, DiffException, IOException {
+
+        String[] diffs = data.getParameterValues(PARAM_DIFF);
+        DiffHandler handler = new JsonDiffHandler(session, targetPath, data);
+        DiffParser parser = new DiffParser(handler);
+
+        for (int i = 0; i < diffs.length; i++) {
+            boolean success = false;
+            try {
+                String diff = diffs[i];
+                parser.parse(diff);
+
+                session.save();
+                success = true;
+            } finally {
+                if (!success) {
+                    session.refresh(false);
+                }
+            }
+        }
+    }
+
+    /**
+     * TODO: doesn't work properly with intermedite SNS-nodes
+     * TODO: doesn't respect jcr:uuid properties.
+     *
+     * @param session
+     * @param targetPath
+     * @param data
+     * @throws RepositoryException
+     * @throws DiffException
+     */
+    private static String modifyContent(Session session, String targetPath, RequestData data)
+            throws RepositoryException, DiffException {
+
+        JsonDiffHandler dh = new JsonDiffHandler(session, targetPath, data);
+        boolean success = false;
+        try {
+            for (Iterator pNames = data.getParameterNames(); pNames.hasNext();) {
+                String paramName = pNames.next().toString();
+                String propPath = dh.getItemPath(paramName);
+                String parentPath = Text.getRelativeParent(propPath, 1);
+
+                if (!session.itemExists(parentPath) || !session.getItem(parentPath).isNode()) {
+                    createNode(session, parentPath, data);
+                }
+
+                if (JcrConstants.JCR_PRIMARYTYPE.equals(Text.getName(propPath))) {
+                    // already handled by createNode above -> ignore
+                    continue;
+                }
+                // none of the special properties -> let the diffhandler take care
+                // of the property creation/modification.
+                dh.setProperty(paramName, null);
+            }
+
+            // save the complete set of modifications
+            session.save();
+            success = true;
+        } finally {
+            if (!success) {
+                session.refresh(false);
+            }
+        }
+        return null; // TODO build loc-href if items were created.
+    }
+
+    /**
+     * 
+     * @param session
+     * @param nodePath
+     * @param data
+     * @throws RepositoryException
+     */
+    private static void createNode(Session session, String nodePath, RequestData data) throws RepositoryException {
+        Node parent = session.getRootNode();
+        String[] smgts = Text.explode(nodePath, '/');
+
+        for (int i = 0; i < smgts.length; i++) {
+            String nodeName = smgts[i];
+            if (parent.hasNode(nodeName)) {
+                parent = parent.getNode(nodeName);
+            } else {
+                // need to create the node
+                // TODO: won't work for SNS
+                String nPath = parent.getPath() + "/" + nodeName;
+                String ntName = data.getParameter(nPath + "/" + JcrConstants.JCR_PRIMARYTYPE);
+                if (ntName == null) {
+                    parent = parent.addNode(nodeName);
+                } else {
+                    parent = parent.addNode(nodeName, ntName);
+                }
+            }
+        }
+    }
+
+    /**
+     *
+     * @param request
+     * @return
+     * @throws DavException
+     */
+    private static Session getRepositorySession(WebdavRequest request) throws DavException {
+        DavSession ds = request.getDavSession();
+        return JcrDavSession.getRepositorySession(ds);
+    }
+
+    /**
+     * Returns the temp directory
+     *
+     * @return the temp directory
+     */
+    private static File getTempDirectory(ServletContext servletCtx) {
+        return (File) servletCtx.getAttribute(ATTR_TMP_DIRECTORY);
+    }
+
+    //--------------------------------------------------------------------------
+    /**
+     * TODO: TOBEFIXED will not behave properly if resource path (i.e. item name)
+     * TODO            ends with .json extension and/or contains a depth-selector pattern.
+     */
+    private static class DavLocatorFactoryImpl extends org.apache.jackrabbit.webdav.jcr.DavLocatorFactoryImpl {
+
+        public DavLocatorFactoryImpl(String s) {
+            super(s);
+        }
+
+        public DavResourceLocator createResourceLocator(String string, String string1) {
+            return new WrappingLocator(super.createResourceLocator(string, string1), isJson(string1), getDepth(string1));
+        }
+
+        public DavResourceLocator createResourceLocator(String string, String string1, String string2) {
+            return super.createResourceLocator(string, string1, string2);
+        }
+
+        public DavResourceLocator createResourceLocator(String string, String string1, String string2, boolean b) {
+            return super.createResourceLocator(string, string1, string2, b);
+        }
+
+        protected String getRepositoryPath(String resourcePath, String wspPath) {
+            if (resourcePath == null) {
+                return null;
+            }
+            String rp = resourcePath;
+            if (isJson(rp)) {
+                rp = resourcePath.substring(0, resourcePath.lastIndexOf('.'));
+                int pos = rp.lastIndexOf(".");
+                if (pos > -1) {
+                    String depthStr = rp.substring(pos + 1);
+                    try {
+                        Integer.parseInt(depthStr);
+                        rp = rp.substring(0, pos);
+                    } catch (NumberFormatException e) {
+                        // ignore return rp
+                    }
+                }
+            }
+            return super.getRepositoryPath(rp, wspPath);
+        }
+
+        private static boolean isJson(String s) {
+            return s.endsWith(".json");
+        }
+
+        private static int getDepth(String s) {
+            int depth = Integer.MIN_VALUE;
+            if (isJson(s)) {
+                String tmp = s.substring(0, s.lastIndexOf('.'));
+                int pos = tmp.lastIndexOf(".");
+                if (pos > -1) {
+                    String depthStr = tmp.substring(pos + 1);
+                    try {
+                        depth = Integer.parseInt(depthStr);
+                    } catch (NumberFormatException e) {
+                        // missing depth
+                    }
+                }
+            }
+            return depth;
+        }
+    }
+
+    private static class WrappingLocator implements DavResourceLocator {
+
+        private final DavResourceLocator loc;
+        private final boolean isJson;
+        private final int depth;
+
+        private WrappingLocator(DavResourceLocator loc, boolean isJson, int depth) {
+            this.loc = loc;
+            this.isJson = isJson;
+            this.depth = depth;
+        }
+        public String getPrefix() {
+            return loc.getPrefix();
+        }
+        public String getResourcePath() {
+            return loc.getResourcePath();
+        }
+        public String getWorkspacePath() {
+            return loc.getWorkspacePath();
+        }
+        public String getWorkspaceName() {
+            return loc.getWorkspaceName();
+        }
+        public boolean isSameWorkspace(DavResourceLocator davResourceLocator) {
+            return loc.isSameWorkspace(davResourceLocator);
+        }
+        public boolean isSameWorkspace(String string) {
+            return loc.isSameWorkspace(string);
+        }
+        public String getHref(boolean b) {
+            return loc.getHref(b);
+        }
+        public boolean isRootLocation() {
+            return loc.isRootLocation();
+        }
+        public DavLocatorFactory getFactory() {
+            return loc.getFactory();
+        }
+        public String getRepositoryPath() {
+            return loc.getRepositoryPath();
+        }
+    }
+}

Propchange: jackrabbit/trunk/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/server/remoting/davex/JcrRemotingServlet.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: jackrabbit/trunk/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/server/remoting/davex/JcrRemotingServlet.java
------------------------------------------------------------------------------
    svn:keywords = author date id revision url

Added: jackrabbit/trunk/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/server/remoting/davex/JsonDiffHandler.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/server/remoting/davex/JsonDiffHandler.java?rev=740749&view=auto
==============================================================================
--- jackrabbit/trunk/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/server/remoting/davex/JsonDiffHandler.java (added)
+++ jackrabbit/trunk/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/server/remoting/davex/JsonDiffHandler.java Wed Feb  4 14:13:24 2009
@@ -0,0 +1,684 @@
+/*
+ * 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.jackrabbit.server.remoting.davex;
+
+import org.apache.jackrabbit.JcrConstants;
+import org.apache.jackrabbit.server.util.RequestData;
+import org.apache.jackrabbit.commons.json.JsonHandler;
+import org.apache.jackrabbit.commons.json.JsonParser;
+import org.apache.jackrabbit.util.Text;
+import org.apache.jackrabbit.webdav.jcr.JcrValueType;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.xml.sax.ContentHandler;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.AttributesImpl;
+
+import javax.jcr.ImportUUIDBehavior;
+import javax.jcr.Item;
+import javax.jcr.ItemNotFoundException;
+import javax.jcr.Node;
+import javax.jcr.NodeIterator;
+import javax.jcr.PropertyType;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.Value;
+import javax.jcr.ValueFactory;
+import javax.jcr.nodetype.NodeType;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Stack;
+
+/** <code>JsonDiffHandler</code>... */
+class JsonDiffHandler implements DiffHandler {
+
+    private static final Logger log = LoggerFactory.getLogger(JsonDiffHandler.class);
+
+    private static final String ORDER_POSITION_AFTER = "#after";
+    private static final String ORDER_POSITION_BEFORE = "#before";
+    private static final String ORDER_POSITION_FIRST = "#first";
+    private static final String ORDER_POSITION_LAST = "#last";
+
+    private final Session session;
+    private final ValueFactory vf;
+    private final String requestItemPath;
+    private final RequestData data;
+
+    JsonDiffHandler(Session session, String requestItemPath, RequestData data) throws RepositoryException {
+        this.session = session;
+        this.requestItemPath = requestItemPath;
+        this.data = data;
+        vf = session.getValueFactory();
+    }
+
+    //--------------------------------------------------------< DiffHandler >---
+    /**
+     *
+     * @param targetPath
+     * @param diffValue
+     */
+    public void addNode(String targetPath, String diffValue) throws DiffException {
+        if (diffValue == null || !(diffValue.startsWith("{") && diffValue.endsWith("}"))) {
+            throw new DiffException("Invalid 'addNode' value '" + diffValue + "'");
+        }
+
+        try {
+            String itemPath = getItemPath(targetPath);
+            String parentPath = Text.getRelativeParent(itemPath, 1);
+            String nodeName = Text.getName(itemPath);
+
+            addNode(parentPath, nodeName, diffValue);
+
+        } catch (RepositoryException e) {
+            throw new DiffException(e.getMessage(), e);
+        }
+    }
+
+    public void setProperty(String targetPath, String diffValue) throws DiffException {
+        try {
+            String itemPath = getItemPath(targetPath);
+            Item item = session.getItem(Text.getRelativeParent(itemPath, 1));
+            if (!item.isNode()) {
+                throw new DiffException("No such node " + itemPath, new ItemNotFoundException(itemPath));
+            }
+
+            Node parent = (Node) item;
+            String propName = Text.getName(itemPath);
+
+            if (JcrConstants.JCR_MIXINTYPES.equals(propName)) {
+                setMixins(parent, extractValuesFromRequest(targetPath));
+            } else {
+                if (diffValue == null || diffValue.length() == 0) {
+                    // single valued property with value present in multipart.
+                    Value[] vs = extractValuesFromRequest(targetPath);
+                    if (vs.length == 0) {
+                        if (parent.hasProperty(propName)) {
+                            // avoid problems with single vs. multi valued props.
+                            parent.getProperty(propName).remove();
+                        } else {
+                            // property does not exist -> stick to rule that missing
+                            // [] indicates single valued.
+                            parent.setProperty(propName, (Value) null);
+                        }
+                    } else if (vs.length == 1) {
+                        parent.setProperty(propName, vs[0]);
+                    } else {
+                        throw new DiffException("Unexpected number of values in multipart. Was " + vs.length + " but expected 1.");
+                    }
+                } else if (diffValue.startsWith("[") && diffValue.endsWith("]")) {
+                    // multivalued property
+                    if (diffValue.length() == 2) {
+                        // empty array OR values in multipart
+                        Value[] vs = extractValuesFromRequest(targetPath);
+                        parent.setProperty(propName, vs);
+                    } else {
+                        // json array
+                        Value[] vs = extractValues(diffValue);
+                        parent.setProperty(propName, vs);
+                    }
+                } else {
+                    // single prop value included in the diff
+                    Value v = extractValue(diffValue);
+                    parent.setProperty(propName, v);
+                }
+            }
+        } catch (RepositoryException e) {
+            throw new DiffException(e.getMessage(), e);
+        } catch (IOException e) {
+            throw new DiffException(e.getMessage(), e);
+        }
+    }
+
+    public void remove(String targetPath, String diffValue) throws DiffException {
+        if (!(diffValue == null || diffValue.length() == 0)) {
+            throw new DiffException("'remove' may not have a diffValue.");
+        }
+        try {
+            String itemPath = getItemPath(targetPath);
+            session.getItem(itemPath).remove();
+        } catch (RepositoryException e) {
+            throw new DiffException(e.getMessage(), e);
+        }
+    }
+
+    public void move(String targetPath, String diffValue) throws DiffException {
+        if (diffValue == null || diffValue.length() == 0) {
+            throw new DiffException("Invalid 'move' value '" + diffValue + "'");
+        }
+        try {
+            String srcPath = getItemPath(targetPath);
+            String orderPosition = getOrderPosition(diffValue);
+            if (orderPosition == null) {
+                // simple move
+                String destPath = getItemPath(diffValue);
+                session.move(srcPath, destPath);
+            } else {
+                String srcName = Text.getName(srcPath);
+                int pos = diffValue.lastIndexOf('#');
+                String destName = (pos == 0) ? null : Text.getName(diffValue.substring(0, pos));
+
+                Item item = session.getItem(Text.getRelativeParent(srcPath, 1));
+                if (!item.isNode()) {
+                    throw new ItemNotFoundException(srcPath);
+                }
+                Node parent = (Node) item;
+
+                if (ORDER_POSITION_FIRST.equals(orderPosition)) {
+                    if (destName != null) {
+                        throw new DiffException(ORDER_POSITION_FIRST + " may not have a leading destination.");
+                    }
+                    destName = Text.getName(parent.getNodes().nextNode().getPath());
+                    parent.orderBefore(srcName, destName);
+                } else if (ORDER_POSITION_LAST.equals(orderPosition)) {
+                    if (destName != null) {
+                        throw new DiffException(ORDER_POSITION_LAST + " may not have a leading destination.");
+                    }
+                    parent.orderBefore(srcName, null);
+                } else if (ORDER_POSITION_AFTER.equals(orderPosition)) {
+                    if (destName == null) {
+                        throw new DiffException(ORDER_POSITION_AFTER + " must have a leading destination.");
+                    }
+                    for (NodeIterator it = parent.getNodes(); it.hasNext();) {
+                        Node child = it.nextNode();
+                        if (destName.equals(child.getName())) {
+                            if (it.hasNext()) {
+                                destName = Text.getName(it.nextNode().getName());
+                            } else {
+                                destName = null;
+                            }
+                            break;
+                        }
+                    }
+                    // reorder... if no child node matches the original destName
+                    // the reorder will fail. no extra check.
+                    parent.orderBefore(srcName, destName);
+                } else {
+                    // standard jcr reorder (before)
+                    parent.orderBefore(srcName, destName);
+                }
+            }
+
+        } catch (RepositoryException e) {
+            throw new DiffException(e.getMessage(), e);
+        }
+    }
+
+    //--------------------------------------------------------------------------
+    /**
+     * 
+     * @param diffPath
+     * @return
+     * @throws RepositoryException
+     */
+    String getItemPath(String diffPath) throws RepositoryException {
+        if (diffPath.startsWith("/")) {
+            // diff path is already an absolute path
+            return diffPath;
+        } else {
+            // diff path is relative to the item path retrieved from the
+            // request URI -> calculate item path.
+            return requestItemPath + diffPath;
+        }
+    }
+
+    private void addNode(String parentPath, String nodeName, String diffValue)
+            throws DiffException, RepositoryException {
+        Item item = session.getItem(parentPath);
+        if (!item.isNode()) {
+            throw new ItemNotFoundException(parentPath);
+        }
+
+        Node parent = (Node) item;
+        try {
+            NodeHandler hndlr = new NodeHandler(parent, nodeName);            
+            new JsonParser(hndlr).parse(diffValue);
+        } catch (IOException e) {
+            throw new DiffException(e.getMessage());
+        }
+    }
+
+    private static Node importNode(Node parent, String nodeName, String ntName,
+                                   String uuid) throws RepositoryException {
+
+        String uri = "http://www.jcp.org/jcr/sv/1.0";
+        String prefix = "sv:";
+
+        ContentHandler ch = parent.getSession().getImportContentHandler(parent.getPath(), ImportUUIDBehavior.IMPORT_UUID_COLLISION_THROW);
+        try {
+            ch.startDocument();
+
+            String nN = "node";
+            AttributesImpl attrs = new AttributesImpl();
+            attrs.addAttribute(uri, "name", prefix + "name", "CDATA", nodeName);
+            ch.startElement(uri, nN, prefix + nN, attrs);
+
+            // primary node type
+            String pN = "property";
+            attrs = new AttributesImpl();
+            attrs.addAttribute(uri, "name", prefix + "name", "CDATA", JcrConstants.JCR_PRIMARYTYPE);
+            attrs.addAttribute(uri, "type", prefix + "type", "CDATA", PropertyType.nameFromValue(PropertyType.NAME));
+            ch.startElement(uri, pN, prefix + pN, attrs);
+            ch.startElement(uri, "value", prefix + "value", new AttributesImpl());
+            char[] val = ntName.toCharArray();
+            ch.characters(val, 0, val.length);
+            ch.endElement(uri, "value", prefix + "value");
+            ch.endElement(uri, pN, prefix + pN);
+
+            // uuid
+            attrs = new AttributesImpl();
+            attrs.addAttribute(uri, "name", prefix + "name", "CDATA", JcrConstants.JCR_UUID);
+            attrs.addAttribute(uri, "type", prefix + "type", "CDATA", PropertyType.nameFromValue(PropertyType.STRING));
+            ch.startElement(uri, pN, prefix + pN, attrs);
+            ch.startElement(uri, "value", prefix + "value", new AttributesImpl());
+            val = uuid.toCharArray();
+            ch.characters(val, 0, val.length);
+            ch.endElement(uri, "value", prefix + "value");
+            ch.endElement(uri, pN, prefix + pN);
+
+            ch.endElement(uri, nN, prefix + nN);
+            ch.endDocument();
+
+        } catch (SAXException e) {
+            throw new RepositoryException(e);
+        }
+
+        Node n = null;
+        NodeIterator it = parent.getNodes(nodeName);
+        while (it.hasNext()) {
+            n = it.nextNode();
+        }
+        if (n == null) {
+            throw new RepositoryException("Internal error: No child node added.");
+        }
+        return n;
+    }
+
+    private static void setMixins(Node n, Value[] values) throws RepositoryException {
+        if (values.length == 0) {
+            // remove all mixins
+            NodeType[] mixins = n.getMixinNodeTypes();
+            for (int i = 0; i < mixins.length; i++) {
+                String mixinName = mixins[i].getName();
+                n.removeMixin(mixinName);
+            }
+        } else {
+            List newMixins = new ArrayList(values.length);
+            for (int i = 0; i < values.length; i++) {
+                newMixins.add(values[i].getString());
+            }
+            NodeType[] mixins = n.getMixinNodeTypes();
+            for (int i = 0; i < mixins.length; i++) {
+                String mixinName = mixins[i].getName();
+                if (!newMixins.remove(mixinName)) {
+                    n.removeMixin(mixinName);
+                }
+            }
+            for (Iterator mixIt = newMixins.iterator(); mixIt.hasNext();) {
+                n.addMixin(mixIt.next().toString());
+            }
+        }
+    }
+
+    private static String getOrderPosition(String diffValue) {
+        String position = null;
+        if (diffValue.indexOf('#') > -1) {
+            if (diffValue.endsWith(ORDER_POSITION_FIRST) ||
+                    diffValue.endsWith(ORDER_POSITION_LAST) ||
+                    diffValue.endsWith(ORDER_POSITION_BEFORE) ||
+                    diffValue.endsWith(ORDER_POSITION_AFTER)) {
+                position = diffValue.substring(diffValue.lastIndexOf('#'));
+            } // else: apparently # is part of the move path.
+        }
+        return position;
+    }
+
+    private Value[] extractValuesFromRequest(String paramName) throws RepositoryException, IOException {
+        ValueFactory vf = session.getValueFactory();
+        Value[] vs;
+        InputStream[] ins = data.getFileParameters(paramName);
+        if (ins != null) {
+            vs = new Value[ins.length];
+            for (int i = 0; i < ins.length; i++) {
+                vs[i] = vf.createValue(ins[i]);
+            }
+        } else {
+            String[] strs = data.getParameterValues(paramName);
+            if (strs == null) {
+                vs = new Value[0];
+            } else {
+                List valList = new ArrayList(strs.length);
+                for (int i = 0; i < strs.length; i++) {
+                    if (strs[i] != null) {
+                        String[] types = data.getParameterTypes(paramName);
+                        int type = (types == null || types.length <= i) ? PropertyType.UNDEFINED : JcrValueType.typeFromContentType(types[i]);
+                        if (type == PropertyType.UNDEFINED) {
+                            valList.add(vf.createValue(strs[i]));
+                        } else {
+                            valList.add(vf.createValue(strs[i], type));
+                        }
+                    }
+                }
+                vs = (Value[]) valList.toArray(new Value[valList.size()]);
+            }
+        }
+        return vs;
+    }
+
+    private Value extractValue(String diffValue) throws RepositoryException, DiffException, IOException {
+        ValueHandler hndlr = new ValueHandler();
+        // surround diffvalue { key : } to make it parsable
+        new JsonParser(hndlr).parse("{\"a\":"+diffValue+"}");
+
+        return hndlr.getValue();
+    }
+
+    private Value[] extractValues(String diffValue) throws RepositoryException, DiffException, IOException {
+        ValuesHandler hndlr = new ValuesHandler();
+        // surround diffvalue { key : } to make it parsable
+        new JsonParser(hndlr).parse("{\"a\":"+diffValue+"}");
+        
+        return hndlr.getValues();
+    }
+
+    //--------------------------------------------------------------------------
+    /**
+     * Inner class used to parse a single value
+     */
+    private class ValueHandler implements JsonHandler {
+        private Value v;
+
+        public void object() throws IOException {
+            // ignore
+        }
+        public void endObject() throws IOException {
+            // ignore
+        }
+        public void array() throws IOException {
+            // ignore
+        }
+        public void endArray() throws IOException {
+            // ignore
+        }
+        public void key(String key) throws IOException {
+            // ignore
+        }
+
+        public void value(String value) throws IOException {
+            v = (value == null) ? null : vf.createValue(value);
+        }
+        public void value(boolean value) throws IOException {
+            v = vf.createValue(value);
+        }
+        public void value(long value) throws IOException {
+            v = vf.createValue(value);
+        }
+        public void value(double value) throws IOException {
+            v = vf.createValue(value);
+        }
+
+        private Value getValue() {
+            return v;
+        }
+    }
+
+    /**
+     * Inner class used to parse the values from a simple json array
+     */
+    private class ValuesHandler implements JsonHandler {
+        private List values = new ArrayList();
+
+        public void object() throws IOException {
+            // ignore
+        }
+        public void endObject() throws IOException {
+            // ignore
+        }
+        public void array() throws IOException {
+            // ignore
+        }
+        public void endArray() throws IOException {
+            // ignore
+        }
+        public void key(String key) throws IOException {
+            // ignore
+        }
+
+        public void value(String value) throws IOException {
+            if (value != null) {
+                values.add(vf.createValue(value));
+            } else {
+                log.warn("Null element for a multivalued property -> Ignore.");
+            }
+        }
+        public void value(boolean value) throws IOException {
+            values.add(vf.createValue(value));
+        }
+        public void value(long value) throws IOException {
+            values.add(vf.createValue(value));
+        }
+        public void value(double value) throws IOException {
+            values.add(vf.createValue(value));
+        }
+
+        private Value[] getValues() {
+            return (Value[]) values.toArray(new Value[values.size()]);
+        }
+    }
+
+    /**
+     * Inner class for parsing a simple json object defining a node and its
+     * child nodes and/or child properties
+     */
+    private class NodeHandler implements JsonHandler {
+        private Node parent;
+        private String key;
+
+        private Stack st = new Stack();
+
+        private NodeHandler(Node parent, String nodeName) throws IOException {
+            this.parent = parent;
+            key = nodeName;
+        }
+
+        public void object() throws IOException {
+            ImportNode n = new ImportNode(key);
+            if (!st.isEmpty()) {
+                Object obj = st.peek();
+                if (obj instanceof ImportNode) {
+                    ((ImportNode)obj).addNode(n);
+                } else {
+                    throw new IOException("Invalid DIFF format: The JSONArray may only contain simple values.");
+                }
+            }
+            st.push(n);
+        }
+
+        public void endObject() throws IOException {
+            // element on stack must be ImportMvProp since array may only
+            // contain simple values, no arrays/objects are allowed.
+            Object obj = st.pop();
+            if (!((obj instanceof ImportNode))) {
+                throw new IOException("Invalid DIFF format.");
+            }
+            if (st.isEmpty()) {
+                // everything parsed -> start adding all nodes and properties
+                try {
+                    ((ImportNode) obj).createItem(parent);                    
+                } catch (RepositoryException e) {
+                    log.error(e.getMessage());
+                    throw new IOException("Invalid DIFF format");
+                }
+            }
+        }
+
+        public void array() throws IOException {
+            ImportMvProp prop = new ImportMvProp(key);
+            Object obj = st.peek();
+            if (obj instanceof ImportNode) {
+                ((ImportNode)obj).addProp(prop);
+            } else {
+                throw new IOException("Invalid DIFF format: The JSONArray may only contain simple values.");
+            }
+            st.push(prop);
+        }
+
+        public void endArray() throws IOException {
+            // element on stack must be ImportMvProp since array may only
+            // contain simple values, no arrays/objects are allowed.
+            Object obj = st.pop();
+            if (!((obj instanceof ImportMvProp))) {
+                throw new IOException("Invalid DIFF format: The JSONArray may only contain simple values.");                
+            }
+        }
+
+        public void key(String key) throws IOException {
+            this.key = key;
+        }
+
+        public void value(String value) throws IOException {
+            Value v = (value == null) ? null : vf.createValue(value);
+            value(v);
+        }
+
+        public void value(boolean value) throws IOException {
+            value(vf.createValue(value));
+        }
+
+        public void value(long value) throws IOException {
+            Value v = vf.createValue(value);
+            value(v);
+        }
+
+        public void value(double value) throws IOException {
+            value(vf.createValue(value));
+        }
+                
+        private void value(Value v) throws IOException {
+            Object obj = st.peek();
+            if (obj instanceof ImportMvProp) {
+                ((ImportMvProp) obj).values.add(v);
+            } else {
+                ((ImportNode) obj).addProp(new ImportProp(key, v));
+            }
+        }
+    }
+
+    private abstract class ImportItem {
+        final String name;
+        private ImportItem(String name) throws IOException {
+            if (name == null) {
+                throw new IOException("Invalid DIFF format: NULL key.");
+            }
+            this.name = name;
+        }
+
+        abstract void createItem(Node parent) throws RepositoryException;
+    }
+    
+    private class ImportNode extends ImportItem {
+        private String ntName;
+        private String uuid;
+
+        private List childN = new ArrayList();
+        private List childP = new ArrayList();
+
+        private ImportNode(String name) throws IOException {
+            super(name);
+        }
+
+        void addProp(ImportProp prop) {
+            if (prop.name.equals(JcrConstants.JCR_PRIMARYTYPE)) {
+                try {
+                    ntName = (prop.value == null) ? null : prop.value.getString();
+                } catch (RepositoryException e) {
+                    // should never get here. Value.getString() should always succeed.
+                    log.error(e.getMessage());
+                }
+            } else if (prop.name.equals(JcrConstants.JCR_UUID)) {
+                try {
+                    uuid = (prop.value == null) ? null : prop.value.getString();
+                } catch (RepositoryException e) {
+                    // should never get here. Value.getString() should always succeed.
+                    log.error(e.getMessage());
+                }
+            } else {
+                // regular property
+                childP.add(prop);
+            }
+        }
+
+        void addProp(ImportMvProp prop) {
+            childP.add(prop);
+        }
+
+        void addNode(ImportNode node) {
+            childN.add(node);
+        }
+
+        void createItem(Node parent) throws RepositoryException {
+            Node n;
+            if (uuid == null) {
+                n = (ntName == null) ? parent.addNode(name) : parent.addNode(name,  ntName);
+            } else {
+                n = importNode(parent, name, ntName, uuid);
+            }
+            // create all properties
+            for (Iterator it = childP.iterator(); it.hasNext();) {
+                ImportItem obj = (ImportItem) it.next();
+                obj.createItem(n);
+            }
+            // recursivly create all child nodes
+            for (Iterator it = childN.iterator(); it.hasNext();) {
+                ImportItem obj = (ImportItem) it.next();
+                obj.createItem(n);
+            }
+        }
+    }
+
+    private class ImportProp extends ImportItem  {
+        private final Value value;
+
+        private ImportProp(String name, Value v) throws IOException {
+            super(name);
+            this.value = v;
+        }
+
+        void createItem(Node parent) throws RepositoryException {
+            parent.setProperty(name, value);
+        }
+    }
+
+    private class ImportMvProp extends ImportItem  {
+        private List values = new ArrayList();
+
+        private ImportMvProp(String name) throws IOException {
+            super(name);
+        }
+
+        void createItem(Node parent) throws RepositoryException {
+            Value[] vls = (Value[]) values.toArray(new Value[values.size()]);
+            if (JcrConstants.JCR_MIXINTYPES.equals(name)) {
+                setMixins(parent, vls);
+            } else {
+                parent.setProperty(name, vls);            
+            }
+        }
+    }
+}

Propchange: jackrabbit/trunk/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/server/remoting/davex/JsonDiffHandler.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: jackrabbit/trunk/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/server/remoting/davex/JsonDiffHandler.java
------------------------------------------------------------------------------
    svn:keywords = author date id revision url

Added: jackrabbit/trunk/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/server/remoting/davex/JsonWriter.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/server/remoting/davex/JsonWriter.java?rev=740749&view=auto
==============================================================================
--- jackrabbit/trunk/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/server/remoting/davex/JsonWriter.java (added)
+++ jackrabbit/trunk/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/server/remoting/davex/JsonWriter.java Wed Feb  4 14:13:24 2009
@@ -0,0 +1,267 @@
+/*
+ * 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.jackrabbit.server.remoting.davex;
+
+import javax.jcr.Node;
+import javax.jcr.NodeIterator;
+import javax.jcr.Property;
+import javax.jcr.PropertyIterator;
+import javax.jcr.PropertyType;
+import javax.jcr.RepositoryException;
+import javax.jcr.Value;
+import java.io.Writer;
+import java.io.IOException;
+
+import org.apache.jackrabbit.JcrConstants;
+import org.apache.jackrabbit.commons.json.JsonUtil;
+
+/**
+ * <code>JsonWriter</code> traverses a tree of JCR items and writes a JSON object
+ * exposing nodes as JSON object members and properties as JSON pairs.
+ * <p/>
+ * <strong>Note</strong>: Using JSON.org library is deliberately avoided for the
+ * following reasons.
+ * <ul>
+ * <li>JSONObject does not preserve the order of members added, which is required
+ * for JCR remoting.</li>
+ * <li>JSONObject#numberToString:
+ * Double numbers get their trailing '.0' stripped away, which removes
+ * the ability to distinguish between JCR values of type {@link PropertyType#DOUBLE}
+ * and {@link PropertyType#LONG}.</li>
+ * </ul>
+ */
+class JsonWriter {
+
+    private final Writer writer;
+    
+    /**
+     * Create a new JsonItemWriter
+     * 
+     * @param writer Writer to which the generated JSON string is written.
+     */
+    JsonWriter(Writer writer) {
+        this.writer = writer;
+    }
+
+    /**
+     * 
+     * @param node
+     * @param maxLevels
+     * @throws RepositoryException
+     * @throws IOException
+     */
+    void write(Node node, int maxLevels) throws RepositoryException, IOException {
+        write(node, 0, maxLevels);
+    }
+
+    private void write(Node node, int currentLevel, int maxLevels)
+            throws RepositoryException, IOException {
+        // start of node info
+        writer.write('{');
+
+        // append the jcr properties as JSON pairs.
+        PropertyIterator props = node.getProperties();        
+        while (props.hasNext()) {
+            Property prop = props.nextProperty();
+            writeProperty(writer, prop);
+            // add separator: next json pair/member is either a property or
+            // a childnode or the special no-children-present pair.
+            writer.write(',');
+        }
+
+        // for jcr child nodes include member unless the max-depths is reached.
+        // in case there are no children at all, append a special pair.
+        final NodeIterator children = node.getNodes();
+        if (!children.hasNext()) {
+            // no child present at all -> add special property.
+            writeKeyValue(writer, "::NodeIteratorSize", "0", false);
+        } else {
+            // the child nodes
+            while (children.hasNext()) {
+                final Node n = children.nextNode();
+                String name = n.getName();
+                int index = n.getIndex();
+                if (index > 1) {
+                    writeKey(writer, name + "[" + index + "]");
+                } else {
+                    writeKey(writer, name);
+                }
+                if (maxLevels < 0 || currentLevel < maxLevels) {
+                    write(n, currentLevel + 1, maxLevels);
+                } else {
+                    /**
+                     * In order to be able to compute the set of child-node entries
+                     * upon Node creation -> add incomplete "node info" JSON
+                     * object for the child node omitting properties and child
+                     * information except for the jcr:uuid property (if present
+                     * at all).
+                     * the latter is required in order to build the correct SPI
+                     * ChildInfo for Node n.
+                     */
+                    writeChildInfo(writer, n);
+                }
+                if (children.hasNext()) {
+                    writer.write(',');
+                }
+            }
+        }
+
+        // end of node info
+        writer.write('}');
+    }
+
+    /**
+     * Write child info without including the complete node info.
+     * 
+     * @param w
+     * @param n
+     * @throws RepositoryException
+     * @throws IOException
+     */
+    private static void writeChildInfo(Writer w, Node n) throws RepositoryException, IOException {
+        // start child info
+        w.write('{');
+
+        // make sure the SPI childInfo can be built correctly on the
+        // client side -> pass uuid if present.
+        if (n.isNodeType(JcrConstants.MIX_REFERENCEABLE) &&
+                n.hasProperty(JcrConstants.JCR_UUID)) {
+            writeProperty(w, n.getProperty(JcrConstants.JCR_UUID));
+        }
+
+        // end child info
+        w.write('}');
+    }
+
+    /**
+     * Write a single property
+     *
+     * @param w
+     * @param p
+     * @throws javax.jcr.RepositoryException
+     * @throws java.io.IOException
+     */
+    private static void writeProperty(Writer w, Property p) throws RepositoryException, IOException {
+        // special handling for binaries: we dump the length and not the length
+        int type = p.getType();
+        if (type == PropertyType.BINARY) {
+            // mark binary properties with a leading ':'
+            // the value(s) reflect the jcr-values length instead of the binary data.
+            String key = ":" + p.getName();
+            if (p.getDefinition().isMultiple()) {
+                long[] binLengths = p.getLengths();
+                writeKeyArray(w, key, binLengths);
+            } else {
+                writeKeyValue(w, key, p.getLength());
+            }
+        } else {
+            boolean isMultiple = p.getDefinition().isMultiple();
+            if (type == PropertyType.NAME || type == PropertyType.PATH ||
+                    type == PropertyType.REFERENCE || type == PropertyType.DATE ||
+                    (isMultiple && p.getValues().length == 0)) {
+                /* special property types that have no correspondence in JSON
+                   are transported as String. the type is transported with an
+                   extra key-value pair, the key having a leading ':' the value
+                   reflects the type. 
+                   the same applies for multivalued properties consisting of an
+                   empty array -> property type guessing would not be possible.
+                 */
+                writeKeyValue(w, ":" +  p.getName(), PropertyType.nameFromValue(type), true);
+            }
+            /* append key-value pair containg the jcr value(s).
+               for String, Boolean, Double, Long -> types in json available */
+            if (isMultiple) {
+                writeKeyArray(w, p.getName(), p.getValues());
+            } else {
+                writeKeyValue(w, p.getName(), p.getValue());
+            }
+        }
+    }
+
+    private static void writeKeyValue(Writer w, String key, String value, boolean hasNext) throws IOException {
+        writeKey(w, key);
+        w.write(JsonUtil.getJsonString(value));
+        if (hasNext) {
+            w.write(',');           
+        }
+    }
+
+    private static void writeKeyValue(Writer w, String key, Value value) throws RepositoryException, IOException {
+        writeKey(w, key);
+        w.write(getJsonValue(value));
+    }
+
+    private static void writeKeyArray(Writer w, String key, Value[] values) throws RepositoryException, IOException {
+        writeKey(w, key);
+        w.write('[');
+        for (int i = 0; i < values.length; i++) {
+            if (i > 0) {
+                w.write(',');
+            }
+            w.write(getJsonValue(values[i]));
+        }
+        w.write(']');
+    }
+    
+    private static void writeKeyValue(Writer w, String key, long binLength) throws IOException {
+        writeKey(w, key);
+        w.write(binLength + "");
+    }
+    
+    private static void writeKeyArray(Writer w, String key, long[] binLengths) throws RepositoryException, IOException {
+        writeKey(w, key);
+        w.write('[');
+        for (int i = 0; i < binLengths.length; i++) {
+            String delim = (i == 0) ? "" : ",";
+            w.write(delim + binLengths[i]);
+        }
+        w.write(']');
+    }
+
+    /**
+     *
+     * @param w
+     * @param key
+     * @throws IOException
+     */
+    private static void writeKey(Writer w, String key) throws IOException {
+        w.write(JsonUtil.getJsonString(key));
+        w.write(':');
+    }
+
+    /**
+     * @param v
+     * @throws RepositoryException
+     * @throws IOException
+     */
+    private static String getJsonValue(Value v) throws RepositoryException, IOException {
+
+        switch (v.getType()) {
+            case PropertyType.BINARY:
+                // should never get here
+                throw new IllegalArgumentException();
+
+            case PropertyType.BOOLEAN:
+            case PropertyType.LONG:
+            case PropertyType.DOUBLE:
+                return v.getString();
+
+            default:
+                return JsonUtil.getJsonString(v.getString());
+        }
+    }
+}

Propchange: jackrabbit/trunk/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/server/remoting/davex/JsonWriter.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: jackrabbit/trunk/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/server/remoting/davex/JsonWriter.java
------------------------------------------------------------------------------
    svn:keywords = author date id revision url