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