You are viewing a plain text version of this content. The canonical link for it is here.
Posted to oak-commits@jackrabbit.apache.org by md...@apache.org on 2015/06/11 14:09:17 UTC
svn commit: r1684861 [3/8] - in /jackrabbit/oak/trunk: ./ oak-remote/
oak-remote/src/ oak-remote/src/main/ oak-remote/src/main/java/
oak-remote/src/main/java/org/ oak-remote/src/main/java/org/apache/
oak-remote/src/main/java/org/apache/jackrabbit/ oak-...
Added: jackrabbit/oak/trunk/oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/content/ContentRemoteSession.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/content/ContentRemoteSession.java?rev=1684861&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/content/ContentRemoteSession.java (added)
+++ jackrabbit/oak/trunk/oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/content/ContentRemoteSession.java Thu Jun 11 12:09:15 2015
@@ -0,0 +1,454 @@
+/*
+ * 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.oak.remote.content;
+
+import org.apache.jackrabbit.oak.api.Blob;
+import org.apache.jackrabbit.oak.api.CommitFailedException;
+import org.apache.jackrabbit.oak.api.ContentSession;
+import org.apache.jackrabbit.oak.api.PropertyValue;
+import org.apache.jackrabbit.oak.api.Result;
+import org.apache.jackrabbit.oak.api.Root;
+import org.apache.jackrabbit.oak.api.Tree;
+import org.apache.jackrabbit.oak.remote.RemoteBinaryFilters;
+import org.apache.jackrabbit.oak.remote.RemoteBinaryId;
+import org.apache.jackrabbit.oak.remote.RemoteCommitException;
+import org.apache.jackrabbit.oak.remote.RemoteOperation;
+import org.apache.jackrabbit.oak.remote.RemoteQueryParseException;
+import org.apache.jackrabbit.oak.remote.RemoteResults;
+import org.apache.jackrabbit.oak.remote.RemoteRevision;
+import org.apache.jackrabbit.oak.remote.RemoteSession;
+import org.apache.jackrabbit.oak.remote.RemoteTreeFilters;
+import org.apache.jackrabbit.oak.remote.RemoteValue;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.text.ParseException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import static org.apache.jackrabbit.oak.commons.PathUtils.denotesRoot;
+import static org.apache.jackrabbit.oak.commons.PathUtils.isAbsolute;
+import static org.apache.jackrabbit.oak.commons.PathUtils.isAncestor;
+
+class ContentRemoteSession implements RemoteSession {
+
+ private final ContentSession contentSession;
+
+ private final ContentRemoteRevisions contentRemoteRevisions;
+
+ private final ContentRemoteBinaries contentRemoteBinaries;
+
+ public ContentRemoteSession(ContentSession contentSession, ContentRemoteRevisions contentRemoteRevisions, ContentRemoteBinaries contentRemoteBinaries) {
+ this.contentSession = contentSession;
+ this.contentRemoteRevisions = contentRemoteRevisions;
+ this.contentRemoteBinaries = contentRemoteBinaries;
+ }
+
+ @Override
+ public ContentRemoteRevision readLastRevision() {
+ Root root = contentSession.getLatestRoot();
+ String revisionId = contentRemoteRevisions.put(contentSession.getAuthInfo(), root);
+ return new ContentRemoteRevision(revisionId, root);
+ }
+
+ @Override
+ public ContentRemoteRevision readRevision(String revisionId) {
+ Root root = contentRemoteRevisions.get(contentSession.getAuthInfo(), revisionId);
+
+ if (root == null) {
+ return null;
+ }
+
+ return new ContentRemoteRevision(revisionId, root);
+ }
+
+ @Override
+ public ContentRemoteTree readTree(RemoteRevision revision, String path, RemoteTreeFilters filters) {
+ ContentRemoteRevision contentRemoteRevision = null;
+
+ if (revision instanceof ContentRemoteRevision) {
+ contentRemoteRevision = (ContentRemoteRevision) revision;
+ }
+
+ if (contentRemoteRevision == null) {
+ throw new IllegalArgumentException("revision not provided");
+ }
+
+ if (path == null) {
+ throw new IllegalArgumentException("path not provided");
+ }
+
+ if (!isAbsolute(path)) {
+ throw new IllegalArgumentException("invalid path");
+ }
+
+ if (filters == null) {
+ throw new IllegalArgumentException("filters not provided");
+ }
+
+ Root root = contentRemoteRevision.getRoot();
+
+ if (root == null) {
+ throw new IllegalStateException("unable to locate the root");
+ }
+
+ Tree tree = root.getTree(path);
+
+ if (tree.exists()) {
+ return new ContentRemoteTree(tree, 0, filters, contentRemoteBinaries);
+ }
+
+ return null;
+ }
+
+ @Override
+ public ContentRemoteOperation createAddOperation(String path, Map<String, RemoteValue> properties) {
+ if (path == null) {
+ throw new IllegalArgumentException("path not provided");
+ }
+
+ if (!isAbsolute(path)) {
+ throw new IllegalArgumentException("invalid path");
+ }
+
+ if (denotesRoot(path)) {
+ throw new IllegalArgumentException("adding root node");
+ }
+
+ if (properties == null) {
+ throw new IllegalArgumentException("properties not provided");
+ }
+
+ List<ContentRemoteOperation> operations = new ArrayList<ContentRemoteOperation>();
+
+ operations.add(new AddContentRemoteOperation(path));
+
+ for (Map.Entry<String, RemoteValue> entry : properties.entrySet()) {
+ operations.add(createSetOperation(path, entry.getKey(), entry.getValue()));
+ }
+
+ return new AggregateContentRemoteOperation(operations);
+ }
+
+ @Override
+ public ContentRemoteOperation createRemoveOperation(String path) {
+ if (path == null) {
+ throw new IllegalArgumentException("path not provided");
+ }
+
+ if (!isAbsolute(path)) {
+ throw new IllegalArgumentException("invalid path");
+ }
+
+ if (denotesRoot(path)) {
+ throw new IllegalArgumentException("removing root node");
+ }
+
+ return new RemoveContentRemoteOperation(path);
+ }
+
+ @Override
+ public ContentRemoteOperation createSetOperation(String path, String name, RemoteValue value) {
+ if (path == null) {
+ throw new IllegalArgumentException("path not provided");
+ }
+
+ if (!isAbsolute(path)) {
+ throw new IllegalArgumentException("invalid path");
+ }
+
+ if (name == null) {
+ throw new IllegalArgumentException("name not provided");
+ }
+
+ if (name.isEmpty()) {
+ throw new IllegalArgumentException("name is empty");
+ }
+
+ if (value == null) {
+ throw new IllegalArgumentException("value not provided");
+ }
+
+ return new SetContentRemoteOperation(contentRemoteBinaries, path, name, value);
+ }
+
+ @Override
+ public ContentRemoteOperation createUnsetOperation(String path, String name) {
+ if (path == null) {
+ throw new IllegalArgumentException("path not provided");
+ }
+
+ if (!isAbsolute(path)) {
+ throw new IllegalArgumentException("invalid path");
+ }
+
+ if (name == null) {
+ throw new IllegalArgumentException("name not provided");
+ }
+
+ if (name.isEmpty()) {
+ throw new IllegalArgumentException("name is empty");
+ }
+
+ return new UnsetContentRemoteOperation(path, name);
+ }
+
+ @Override
+ public ContentRemoteOperation createCopyOperation(String source, String target) {
+ if (source == null) {
+ throw new IllegalArgumentException("source path not provided");
+ }
+
+ if (!isAbsolute(source)) {
+ throw new IllegalArgumentException("invalid source path");
+ }
+
+ if (target == null) {
+ throw new IllegalArgumentException("target path not provided");
+ }
+
+ if (!isAbsolute(target)) {
+ throw new IllegalArgumentException("invalid target path");
+ }
+
+ if (source.equals(target)) {
+ throw new IllegalArgumentException("same source and target path");
+ }
+
+ if (isAncestor(source, target)) {
+ throw new IllegalArgumentException("source path is an ancestor of target path");
+ }
+
+ return new CopyContentRemoteOperation(source, target);
+ }
+
+ @Override
+ public ContentRemoteOperation createMoveOperation(String source, String target) {
+ if (source == null) {
+ throw new IllegalArgumentException("source path not provided");
+ }
+
+ if (!isAbsolute(source)) {
+ throw new IllegalArgumentException("invalid source path");
+ }
+
+ if (target == null) {
+ throw new IllegalArgumentException("target path not provided");
+ }
+
+ if (!isAbsolute(target)) {
+ throw new IllegalArgumentException("invalid target path");
+ }
+
+ if (source.equals(target)) {
+ throw new IllegalArgumentException("same source and target path");
+ }
+
+ if (isAncestor(source, target)) {
+ throw new IllegalArgumentException("source path is an ancestor of target path");
+ }
+
+ return new MoveContentRemoteOperation(source, target);
+ }
+
+ @Override
+ public ContentRemoteOperation createAggregateOperation(final List<RemoteOperation> operations) {
+ if (operations == null) {
+ throw new IllegalArgumentException("operations not provided");
+ }
+
+ List<ContentRemoteOperation> contentRemoteOperations = new ArrayList<ContentRemoteOperation>();
+
+ for (RemoteOperation operation : operations) {
+ if (operation == null) {
+ throw new IllegalArgumentException("operation not provided");
+ }
+
+ ContentRemoteOperation contentRemoteOperation = null;
+
+ if (operation instanceof ContentRemoteOperation) {
+ contentRemoteOperation = (ContentRemoteOperation) operation;
+ }
+
+ if (contentRemoteOperation == null) {
+ throw new IllegalArgumentException("invalid operation");
+ }
+
+ contentRemoteOperations.add(contentRemoteOperation);
+ }
+
+ return new AggregateContentRemoteOperation(contentRemoteOperations);
+ }
+
+ @Override
+ public ContentRemoteRevision commit(RemoteRevision revision, RemoteOperation operation) throws RemoteCommitException {
+ ContentRemoteRevision contentRemoteRevision = null;
+
+ if (revision instanceof ContentRemoteRevision) {
+ contentRemoteRevision = (ContentRemoteRevision) revision;
+ }
+
+ if (contentRemoteRevision == null) {
+ throw new IllegalArgumentException("invalid revision");
+ }
+
+ ContentRemoteOperation contentRemoteOperation = null;
+
+ if (operation instanceof ContentRemoteOperation) {
+ contentRemoteOperation = (ContentRemoteOperation) operation;
+ }
+
+ if (contentRemoteOperation == null) {
+ throw new IllegalArgumentException("invalid operation");
+ }
+
+ Root root = contentRemoteRevision.getRoot();
+
+ if (root == null) {
+ throw new IllegalStateException("unable to locate the root");
+ }
+
+ contentRemoteOperation.apply(root);
+
+ try {
+ root.commit();
+ } catch (CommitFailedException e) {
+ throw new RemoteCommitException("unable to apply the changes", e);
+ }
+
+ return new ContentRemoteRevision(contentRemoteRevisions.put(contentSession.getAuthInfo(), root), root);
+ }
+
+ @Override
+ public ContentRemoteBinaryId readBinaryId(String binaryId) {
+ if (binaryId == null) {
+ throw new IllegalArgumentException("binary id not provided");
+ }
+
+ if (binaryId.isEmpty()) {
+ throw new IllegalArgumentException("invalid binary id");
+ }
+
+ Blob blob = contentRemoteBinaries.get(binaryId);
+
+ if (blob == null) {
+ return null;
+ }
+
+ return new ContentRemoteBinaryId(binaryId, blob);
+ }
+
+ @Override
+ public InputStream readBinary(RemoteBinaryId binaryId, RemoteBinaryFilters filters) {
+ ContentRemoteBinaryId contentRemoteBinaryId = null;
+
+ if (binaryId instanceof ContentRemoteBinaryId) {
+ contentRemoteBinaryId = (ContentRemoteBinaryId) binaryId;
+ }
+
+ if (contentRemoteBinaryId == null) {
+ throw new IllegalArgumentException("invalid binary id");
+ }
+
+ if (filters == null) {
+ throw new IllegalArgumentException("filters not provided");
+ }
+
+ return new ContentRemoteInputStream(contentRemoteBinaryId.asBlob().getNewStream(), filters);
+ }
+
+ @Override
+ public long readBinaryLength(RemoteBinaryId binaryId) {
+ ContentRemoteBinaryId contentRemoteBinaryId = null;
+
+ if (binaryId instanceof ContentRemoteBinaryId) {
+ contentRemoteBinaryId = (ContentRemoteBinaryId) binaryId;
+ }
+
+ if (contentRemoteBinaryId == null) {
+ throw new IllegalArgumentException("invalid binary id");
+ }
+
+ return contentRemoteBinaryId.asBlob().length();
+ }
+
+ @Override
+ public ContentRemoteBinaryId writeBinary(InputStream stream) {
+ if (stream == null) {
+ throw new IllegalArgumentException("stream not provided");
+ }
+
+ Blob blob;
+
+ try {
+ blob = contentSession.getLatestRoot().createBlob(stream);
+ } catch (IOException e) {
+ throw new RuntimeException("unable to write the binary object", e);
+ }
+
+ return new ContentRemoteBinaryId(contentRemoteBinaries.put(blob), blob);
+ }
+
+ @Override
+ public RemoteResults search(RemoteRevision revision, String query, String language, long offset, long limit) throws RemoteQueryParseException {
+ ContentRemoteRevision contentRemoteRevision = null;
+
+ if (revision instanceof ContentRemoteRevision) {
+ contentRemoteRevision = (ContentRemoteRevision) revision;
+ }
+
+ if (contentRemoteRevision == null) {
+ throw new IllegalArgumentException("invalid revision");
+ }
+
+ Root root = contentRemoteRevision.getRoot();
+
+ if (query == null) {
+ throw new IllegalArgumentException("query not provided");
+ }
+
+ if (language == null) {
+ throw new IllegalArgumentException("language not provided");
+ }
+
+ if (!root.getQueryEngine().getSupportedQueryLanguages().contains(language)) {
+ throw new IllegalArgumentException("language not supported");
+ }
+
+ if (offset < 0) {
+ throw new IllegalArgumentException("invalid offset");
+ }
+
+ if (limit < 0) {
+ throw new IllegalArgumentException("invalid limit");
+ }
+
+ Result results;
+
+ try {
+ results = root.getQueryEngine().executeQuery(query, language, limit, offset, new HashMap<String, PropertyValue>(), new HashMap<String, String>());
+ } catch (ParseException e) {
+ throw new RemoteQueryParseException("invalid query", e);
+ }
+
+ return new ContentRemoteResults(contentRemoteBinaries, results);
+ }
+
+}
Added: jackrabbit/oak/trunk/oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/content/ContentRemoteTree.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/content/ContentRemoteTree.java?rev=1684861&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/content/ContentRemoteTree.java (added)
+++ jackrabbit/oak/trunk/oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/content/ContentRemoteTree.java Thu Jun 11 12:09:15 2015
@@ -0,0 +1,335 @@
+/*
+ * 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.oak.remote.content;
+
+import com.google.common.base.Function;
+import com.google.common.base.Predicate;
+import com.google.common.collect.Iterables;
+import org.apache.jackrabbit.oak.api.Blob;
+import org.apache.jackrabbit.oak.api.PropertyState;
+import org.apache.jackrabbit.oak.api.Tree;
+import org.apache.jackrabbit.oak.api.Type;
+import org.apache.jackrabbit.oak.remote.RemoteTree;
+import org.apache.jackrabbit.oak.remote.RemoteTreeFilters;
+import org.apache.jackrabbit.oak.remote.RemoteValue;
+import org.apache.jackrabbit.oak.remote.RemoteValue.Supplier;
+import org.apache.jackrabbit.oak.remote.filter.Filters;
+import org.apache.jackrabbit.util.ISO8601;
+
+import java.io.InputStream;
+import java.util.Calendar;
+import java.util.HashMap;
+import java.util.Map;
+
+class ContentRemoteTree implements RemoteTree {
+
+ private final Tree tree;
+
+ private final int depth;
+
+ private final RemoteTreeFilters filters;
+
+ private final ContentRemoteBinaries contentRemoteBinaries;
+
+ public ContentRemoteTree(Tree tree, int depth, RemoteTreeFilters filters, ContentRemoteBinaries contentRemoteBinaries) {
+ this.tree = tree;
+ this.depth = depth;
+ this.filters = filters;
+ this.contentRemoteBinaries = contentRemoteBinaries;
+ }
+
+ @Override
+ public Map<String, RemoteValue> getProperties() {
+ Map<String, RemoteValue> properties = new HashMap<String, RemoteValue>();
+
+ for (PropertyState property : getFilteredProperties()) {
+ properties.put(property.getName(), getRemoteValue(property));
+ }
+
+ return properties;
+ }
+
+ private Iterable<? extends PropertyState> getFilteredProperties() {
+ return Iterables.filter(tree.getProperties(), getPropertyFilters());
+ }
+
+ private Predicate<? super PropertyState> getPropertyFilters() {
+ return new Predicate<PropertyState>() {
+
+ @Override
+ public boolean apply(PropertyState property) {
+ return new Filters(filters.getPropertyFilters()).matches(property.getName());
+ }
+
+ };
+ }
+
+ private RemoteValue getRemoteValue(PropertyState property) {
+ Type<?> type = property.getType();
+
+ if (type == Type.DATE) {
+ return RemoteValue.toDate(getDate(property.getValue(Type.DATE)));
+ }
+
+ if (type == Type.DATES) {
+ return RemoteValue.toMultiDate(getDates(property.getValue(Type.DATES)));
+ }
+
+ if (type == Type.BINARY) {
+ return getBinaryRemoteValue(property.getValue(Type.BINARY));
+ }
+
+ if (type == Type.BINARIES) {
+ return getBinaryRemoteValues(property.getValue(Type.BINARIES));
+ }
+
+ if (type == Type.BOOLEAN) {
+ return RemoteValue.toBoolean(property.getValue(Type.BOOLEAN));
+ }
+
+ if (type == Type.BOOLEANS) {
+ return RemoteValue.toMultiBoolean(property.getValue(Type.BOOLEANS));
+ }
+
+ if (type == Type.DECIMAL) {
+ return RemoteValue.toDecimal(property.getValue(Type.DECIMAL));
+ }
+
+ if (type == Type.DECIMALS) {
+ return RemoteValue.toMultiDecimal(property.getValue(Type.DECIMALS));
+ }
+
+ if (type == Type.DOUBLE) {
+ return RemoteValue.toDouble(property.getValue(Type.DOUBLE));
+ }
+
+ if (type == Type.DOUBLES) {
+ return RemoteValue.toMultiDouble(property.getValue(Type.DOUBLES));
+ }
+
+ if (type == Type.LONG) {
+ return RemoteValue.toLong(property.getValue(Type.LONG));
+ }
+
+ if (type == Type.LONGS) {
+ return RemoteValue.toMultiLong(property.getValue(Type.LONGS));
+ }
+
+ if (type == Type.NAME) {
+ return RemoteValue.toName(property.getValue(Type.NAME));
+ }
+
+ if (type == Type.NAMES) {
+ return RemoteValue.toMultiName(property.getValue(Type.NAMES));
+ }
+
+ if (type == Type.PATH) {
+ return RemoteValue.toPath(property.getValue(Type.PATH));
+ }
+
+ if (type == Type.PATHS) {
+ return RemoteValue.toMultiPath(property.getValue(Type.PATHS));
+ }
+
+ if (type == Type.REFERENCE) {
+ return RemoteValue.toReference(property.getValue(Type.REFERENCE));
+ }
+
+ if (type == Type.REFERENCES) {
+ return RemoteValue.toMultiReference(property.getValue(Type.REFERENCES));
+ }
+
+ if (type == Type.STRING) {
+ return RemoteValue.toText(property.getValue(Type.STRING));
+ }
+
+ if (type == Type.STRINGS) {
+ return RemoteValue.toMultiText(property.getValue(Type.STRINGS));
+ }
+
+ if (type == Type.URI) {
+ return RemoteValue.toUri(property.getValue(Type.URI));
+ }
+
+ if (type == Type.URIS) {
+ return RemoteValue.toMultiUri(property.getValue(Type.URIS));
+ }
+
+ if (type == Type.WEAKREFERENCE) {
+ return RemoteValue.toWeakReference(property.getValue(Type.WEAKREFERENCE));
+ }
+
+ if (type == Type.WEAKREFERENCES) {
+ return RemoteValue.toMultiWeakReference(property.getValue(Type.WEAKREFERENCES));
+ }
+
+ throw new IllegalArgumentException("unrecognized property type");
+ }
+
+ private long getDate(String date) {
+ Calendar calendar = ISO8601.parse(date);
+
+ if (calendar == null) {
+ throw new IllegalStateException("invalid date format");
+ }
+
+ return calendar.getTimeInMillis();
+ }
+
+ private Iterable<Long> getDates(Iterable<String> dates) {
+ return Iterables.transform(dates, new Function<String, Long>() {
+
+ @Override
+ public Long apply(String date) {
+ return getDate(date);
+ }
+
+ });
+ }
+
+ private RemoteValue getBinaryRemoteValue(Blob blob) {
+ if (getLength(blob) < filters.getBinaryThreshold()) {
+ return RemoteValue.toBinary(getBinary(blob));
+ } else {
+ return RemoteValue.toBinaryId(getBinaryId(blob));
+ }
+ }
+
+ private RemoteValue getBinaryRemoteValues(Iterable<Blob> blobs) {
+ if (getLength(blobs) < filters.getBinaryThreshold()) {
+ return RemoteValue.toMultiBinary(getBinaries(blobs));
+ } else {
+ return RemoteValue.toMultiBinaryId(getBinaryIds(blobs));
+ }
+ }
+
+ private long getLength(Blob blob) {
+ return blob.length();
+ }
+
+ private long getLength(Iterable<Blob> blobs) {
+ long length = 0;
+
+ for (Blob blob : blobs) {
+ length = length + blob.length();
+ }
+
+ return length;
+ }
+
+ private Supplier<InputStream> getBinary(final Blob blob) {
+ return new Supplier<InputStream>() {
+
+ @Override
+ public InputStream get() {
+ return blob.getNewStream();
+ }
+
+ };
+ }
+
+ private Iterable<Supplier<InputStream>> getBinaries(Iterable<Blob> blobs) {
+ return Iterables.transform(blobs, new Function<Blob, Supplier<InputStream>>() {
+
+ @Override
+ public Supplier<InputStream> apply(Blob blob) {
+ return getBinary(blob);
+ }
+
+ });
+ }
+
+ private String getBinaryId(Blob blob) {
+ return contentRemoteBinaries.put(blob);
+ }
+
+ private Iterable<String> getBinaryIds(Iterable<Blob> blobs) {
+ return Iterables.transform(blobs, new Function<Blob, String>() {
+
+ @Override
+ public String apply(Blob blob) {
+ return getBinaryId(blob);
+ }
+
+ });
+ }
+
+ @Override
+ public Map<String, RemoteTree> getChildren() {
+ Map<String, RemoteTree> children = new HashMap<String, RemoteTree>();
+
+ for (Tree child : getFilteredChildren()) {
+ if (depth < filters.getDepth()) {
+ children.put(child.getName(), new ContentRemoteTree(child, depth + 1, filters, contentRemoteBinaries));
+ } else {
+ children.put(child.getName(), null);
+ }
+ }
+
+ return children;
+ }
+
+ private Iterable<Tree> getFilteredChildren() {
+ Iterable<Tree> result = tree.getChildren();
+
+ if (filters.getChildrenStart() > 0) {
+ result = Iterables.skip(result, filters.getChildrenStart());
+ }
+
+ if (filters.getChildrenCount() >= 0) {
+ result = Iterables.limit(result, filters.getChildrenCount());
+ }
+
+ return Iterables.filter(result, getNodeFilters());
+ }
+
+ private Predicate<Tree> getNodeFilters() {
+ return new Predicate<Tree>() {
+
+ @Override
+ public boolean apply(Tree child) {
+ return new Filters(filters.getNodeFilters()).matches(child.getName());
+ }
+
+ };
+ }
+
+ @Override
+ public boolean hasMoreChildren() {
+ if (filters.getChildrenCount() < 0) {
+ return false;
+ }
+
+ int start = filters.getChildrenStart();
+
+ if (start < 0) {
+ start = 0;
+ }
+
+ int count = filters.getChildrenCount();
+
+ if (count < 0) {
+ count = 0;
+ }
+
+ int max = start + count;
+
+ return tree.getChildrenCount(max) > max;
+ }
+
+}
Added: jackrabbit/oak/trunk/oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/content/CopyContentRemoteOperation.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/content/CopyContentRemoteOperation.java?rev=1684861&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/content/CopyContentRemoteOperation.java (added)
+++ jackrabbit/oak/trunk/oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/content/CopyContentRemoteOperation.java Thu Jun 11 12:09:15 2015
@@ -0,0 +1,77 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jackrabbit.oak.remote.content;
+
+import org.apache.jackrabbit.oak.api.PropertyState;
+import org.apache.jackrabbit.oak.api.Root;
+import org.apache.jackrabbit.oak.api.Tree;
+import org.apache.jackrabbit.oak.remote.RemoteCommitException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+class CopyContentRemoteOperation implements ContentRemoteOperation {
+
+ private static final Logger logger = LoggerFactory.getLogger(CopyContentRemoteOperation.class);
+
+ private final String source;
+
+ private final String target;
+
+ public CopyContentRemoteOperation(String source, String target) {
+ this.source = source;
+ this.target = target;
+ }
+
+ @Override
+ public void apply(Root root) throws RemoteCommitException {
+ logger.debug("performing 'copy' operation on source={}, target={}", source, target);
+
+ Tree sourceTree = root.getTree(source);
+
+ if (!sourceTree.exists()) {
+ throw new RemoteCommitException("source tree does not exist");
+ }
+
+ Tree targetTree = root.getTree(target);
+
+ if (targetTree.exists()) {
+ throw new RemoteCommitException("target tree already exists");
+ }
+
+ Tree targetParentTree = targetTree.getParent();
+
+ if (!targetParentTree.exists()) {
+ throw new RemoteCommitException("parent of target tree does not exist");
+ }
+
+ copy(sourceTree, targetParentTree, targetTree.getName());
+ }
+
+ private void copy(Tree source, Tree targetParent, String targetName) {
+ Tree target = targetParent.addChild(targetName);
+
+ for (PropertyState property : source.getProperties()) {
+ target.setProperty(property);
+ }
+
+ for (Tree child : source.getChildren()) {
+ copy(child, target, child.getName());
+ }
+ }
+
+}
Added: jackrabbit/oak/trunk/oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/content/MoveContentRemoteOperation.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/content/MoveContentRemoteOperation.java?rev=1684861&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/content/MoveContentRemoteOperation.java (added)
+++ jackrabbit/oak/trunk/oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/content/MoveContentRemoteOperation.java Thu Jun 11 12:09:15 2015
@@ -0,0 +1,51 @@
+/*
+ * 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.oak.remote.content;
+
+import org.apache.jackrabbit.oak.api.Root;
+import org.apache.jackrabbit.oak.remote.RemoteCommitException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+class MoveContentRemoteOperation implements ContentRemoteOperation {
+
+ private static final Logger logger = LoggerFactory.getLogger(MoveContentRemoteOperation.class);
+
+ private final String source;
+
+ private final String target;
+
+ public MoveContentRemoteOperation(String source, String target) {
+ this.source = source;
+ this.target = target;
+ }
+
+ @Override
+ public void apply(Root root) throws RemoteCommitException {
+ logger.debug("performing 'move' operation on source={}, target={}", source, target);
+
+ boolean success = root.move(source, target);
+
+ if (success) {
+ return;
+ }
+
+ throw new RemoteCommitException("unable to move the tree");
+ }
+
+}
Added: jackrabbit/oak/trunk/oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/content/RemoveContentRemoteOperation.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/content/RemoveContentRemoteOperation.java?rev=1684861&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/content/RemoveContentRemoteOperation.java (added)
+++ jackrabbit/oak/trunk/oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/content/RemoveContentRemoteOperation.java Thu Jun 11 12:09:15 2015
@@ -0,0 +1,51 @@
+/*
+ * 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.oak.remote.content;
+
+import org.apache.jackrabbit.oak.api.Root;
+import org.apache.jackrabbit.oak.api.Tree;
+import org.apache.jackrabbit.oak.remote.RemoteCommitException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+class RemoveContentRemoteOperation implements ContentRemoteOperation {
+
+ private static final Logger logger = LoggerFactory.getLogger(RemoveContentRemoteOperation.class);
+
+ private final String path;
+
+ public RemoveContentRemoteOperation(String path) {
+ this.path = path;
+ }
+
+ @Override
+ public void apply(Root root) throws RemoteCommitException {
+ logger.debug("performing 'remove' operation on path={}", path);
+
+ Tree tree = root.getTree(path);
+
+ if (!tree.exists()) {
+ throw new RemoteCommitException("tree does not exists");
+ }
+
+ if (!tree.remove()) {
+ throw new RemoteCommitException("unable to remove the tree");
+ }
+ }
+
+}
Added: jackrabbit/oak/trunk/oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/content/SetContentRemoteOperation.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/content/SetContentRemoteOperation.java?rev=1684861&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/content/SetContentRemoteOperation.java (added)
+++ jackrabbit/oak/trunk/oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/content/SetContentRemoteOperation.java Thu Jun 11 12:09:15 2015
@@ -0,0 +1,59 @@
+/*
+ * 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.oak.remote.content;
+
+import org.apache.jackrabbit.oak.api.Root;
+import org.apache.jackrabbit.oak.api.Tree;
+import org.apache.jackrabbit.oak.remote.RemoteCommitException;
+import org.apache.jackrabbit.oak.remote.RemoteValue;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+class SetContentRemoteOperation implements ContentRemoteOperation {
+
+ private static final Logger logger = LoggerFactory.getLogger(SetContentRemoteOperation.class);
+
+ private final ContentRemoteBinaries binaries;
+
+ private final String path;
+
+ private final String name;
+
+ private final RemoteValue value;
+
+ public SetContentRemoteOperation(ContentRemoteBinaries binaries, String path, String name, RemoteValue value) {
+ this.binaries = binaries;
+ this.path = path;
+ this.name = name;
+ this.value = value;
+ }
+
+ @Override
+ public void apply(Root root) throws RemoteCommitException {
+ logger.debug("performing 'set' operation on path={}, name={}", path, name);
+
+ Tree tree = root.getTree(path);
+
+ if (!tree.exists()) {
+ throw new RemoteCommitException("tree does not exist");
+ }
+
+ value.whenType(new SetPropertyHandler(binaries, root, tree, name));
+ }
+
+}
Added: jackrabbit/oak/trunk/oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/content/SetPropertyHandler.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/content/SetPropertyHandler.java?rev=1684861&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/content/SetPropertyHandler.java (added)
+++ jackrabbit/oak/trunk/oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/content/SetPropertyHandler.java Thu Jun 11 12:09:15 2015
@@ -0,0 +1,242 @@
+/*
+ * 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.oak.remote.content;
+
+import com.google.common.base.Function;
+import com.google.common.collect.Iterables;
+import org.apache.jackrabbit.oak.api.Blob;
+import org.apache.jackrabbit.oak.api.Root;
+import org.apache.jackrabbit.oak.api.Tree;
+import org.apache.jackrabbit.oak.api.Type;
+import org.apache.jackrabbit.oak.remote.RemoteValue.Supplier;
+import org.apache.jackrabbit.oak.remote.RemoteValue.TypeHandler;
+import org.apache.jackrabbit.util.ISO8601;
+
+import java.io.InputStream;
+import java.math.BigDecimal;
+import java.util.Calendar;
+
+class SetPropertyHandler extends TypeHandler {
+
+ private final ContentRemoteBinaries binaries;
+
+ private final Root root;
+
+ private final Tree tree;
+
+ private final String name;
+
+ public SetPropertyHandler(ContentRemoteBinaries binaries, Root root, Tree tree, String name) {
+ this.binaries = binaries;
+ this.root = root;
+ this.tree = tree;
+ this.name = name;
+ }
+
+ @Override
+ public void isBinary(Supplier<InputStream> value) {
+ tree.setProperty(name, getBlob(root, value), Type.BINARY);
+ }
+
+ @Override
+ public void isMultiBinary(Iterable<Supplier<InputStream>> value) {
+ tree.setProperty(name, getBlobs(root, value), Type.BINARIES);
+ }
+
+ @Override
+ public void isBinaryId(String value) {
+ tree.setProperty(name, getBlobFromId(binaries, value), Type.BINARY);
+ }
+
+ @Override
+ public void isMultiBinaryId(Iterable<String> value) {
+ tree.setProperty(name, getBlobsFromIds(binaries, value), Type.BINARIES);
+ }
+
+ @Override
+ public void isBoolean(Boolean value) {
+ tree.setProperty(name, value, Type.BOOLEAN);
+ }
+
+ @Override
+ public void isMultiBoolean(Iterable<Boolean> value) {
+ tree.setProperty(name, value, Type.BOOLEANS);
+ }
+
+ @Override
+ public void isDate(Long value) {
+ tree.setProperty(name, getDate(value), Type.DATE);
+ }
+
+ @Override
+ public void isMultiDate(Iterable<Long> value) {
+ tree.setProperty(name, getDates(value), Type.DATES);
+ }
+
+ @Override
+ public void isDecimal(BigDecimal value) {
+ tree.setProperty(name, value, Type.DECIMAL);
+ }
+
+ @Override
+ public void isMultiDecimal(Iterable<BigDecimal> value) {
+ tree.setProperty(name, value, Type.DECIMALS);
+ }
+
+ @Override
+ public void isDouble(Double value) {
+ tree.setProperty(name, value, Type.DOUBLE);
+ }
+
+ @Override
+ public void isMultiDouble(Iterable<Double> value) {
+ tree.setProperty(name, value, Type.DOUBLES);
+ }
+
+ @Override
+ public void isLong(Long value) {
+ tree.setProperty(name, value, Type.LONG);
+ }
+
+ @Override
+ public void isMultiLong(Iterable<Long> value) {
+ tree.setProperty(name, value, Type.LONGS);
+ }
+
+ @Override
+ public void isName(String value) {
+ tree.setProperty(name, value, Type.NAME);
+ }
+
+ @Override
+ public void isMultiName(Iterable<String> value) {
+ tree.setProperty(name, value, Type.NAMES);
+ }
+
+ @Override
+ public void isPath(String value) {
+ tree.setProperty(name, value, Type.PATH);
+ }
+
+ @Override
+ public void isMultiPath(Iterable<String> value) {
+ tree.setProperty(name, value, Type.PATHS);
+ }
+
+ @Override
+ public void isReference(String value) {
+ tree.setProperty(name, value, Type.REFERENCE);
+ }
+
+ @Override
+ public void isMultiReference(Iterable<String> value) {
+ tree.setProperty(name, value, Type.REFERENCES);
+ }
+
+ @Override
+ public void isText(String value) {
+ tree.setProperty(name, value, Type.STRING);
+ }
+
+ @Override
+ public void isMultiText(Iterable<String> value) {
+ tree.setProperty(name, value, Type.STRINGS);
+ }
+
+ @Override
+ public void isUri(String value) {
+ tree.setProperty(name, value, Type.URI);
+ }
+
+ @Override
+ public void isMultiUri(Iterable<String> value) {
+ tree.setProperty(name, value, Type.URIS);
+ }
+
+ @Override
+ public void isWeakReference(String value) {
+ tree.setProperty(name, value, Type.WEAKREFERENCE);
+ }
+
+ @Override
+ public void isMultiWeakReference(Iterable<String> value) {
+ tree.setProperty(name, value, Type.WEAKREFERENCES);
+ }
+
+ private Blob getBlob(Root root, Supplier<InputStream> supplier) {
+ InputStream inputStream = supplier.get();
+
+ if (inputStream == null) {
+ throw new IllegalStateException("invalid input stream");
+ }
+
+ Blob blob;
+
+ try {
+ blob = root.createBlob(inputStream);
+ } catch (Exception e) {
+ throw new IllegalStateException("unable to create a blob", e);
+ }
+
+ return blob;
+ }
+
+ private Iterable<Blob> getBlobs(final Root root, Iterable<Supplier<InputStream>> suppliers) {
+ return Iterables.transform(suppliers, new Function<Supplier<InputStream>, Blob>() {
+
+ @Override
+ public Blob apply(Supplier<InputStream> supplier) {
+ return getBlob(root, supplier);
+ }
+
+ });
+ }
+
+ private Blob getBlobFromId(ContentRemoteBinaries binaries, String binaryId) {
+ return binaries.get(binaryId);
+ }
+
+ private Iterable<Blob> getBlobsFromIds(final ContentRemoteBinaries binaries, Iterable<String> binaryIds) {
+ return Iterables.transform(binaryIds, new Function<String, Blob>() {
+
+ @Override
+ public Blob apply(String binaryId) {
+ return getBlobFromId(binaries, binaryId);
+ }
+
+ });
+ }
+
+ private String getDate(Long time) {
+ Calendar calendar = Calendar.getInstance();
+ calendar.setTimeInMillis(time);
+ return ISO8601.format(calendar);
+ }
+
+ private Iterable<String> getDates(Iterable<Long> times) {
+ return Iterables.transform(times, new Function<Long, String>() {
+
+ @Override
+ public String apply(Long time) {
+ return getDate(time);
+ }
+
+ });
+ }
+
+}
Added: jackrabbit/oak/trunk/oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/content/UnsetContentRemoteOperation.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/content/UnsetContentRemoteOperation.java?rev=1684861&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/content/UnsetContentRemoteOperation.java (added)
+++ jackrabbit/oak/trunk/oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/content/UnsetContentRemoteOperation.java Thu Jun 11 12:09:15 2015
@@ -0,0 +1,56 @@
+/*
+ * 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.oak.remote.content;
+
+import org.apache.jackrabbit.oak.api.Root;
+import org.apache.jackrabbit.oak.api.Tree;
+import org.apache.jackrabbit.oak.remote.RemoteCommitException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+class UnsetContentRemoteOperation implements ContentRemoteOperation {
+
+ private static final Logger logger = LoggerFactory.getLogger(UnsetContentRemoteOperation.class);
+
+ private final String path;
+
+ private final String name;
+
+ public UnsetContentRemoteOperation(String path, String name) {
+ this.path = path;
+ this.name = name;
+ }
+
+ @Override
+ public void apply(Root root) throws RemoteCommitException {
+ logger.debug("performing 'unset' operation on path={}, name={}", path, name);
+
+ Tree tree = root.getTree(path);
+
+ if (!tree.exists()) {
+ throw new RemoteCommitException("tree does not exists");
+ }
+
+ if (!tree.hasProperty(name)) {
+ throw new RemoteCommitException("property does not exist");
+ }
+
+ tree.removeProperty(name);
+ }
+
+}
Added: jackrabbit/oak/trunk/oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/filter/Filter.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/filter/Filter.java?rev=1684861&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/filter/Filter.java (added)
+++ jackrabbit/oak/trunk/oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/filter/Filter.java Thu Jun 11 12:09:15 2015
@@ -0,0 +1,52 @@
+/*
+ * 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.oak.remote.filter;
+
+import java.util.regex.Pattern;
+
+class Filter {
+
+ private Pattern pattern;
+
+ public Filter(String filter) {
+ StringBuilder builder = new StringBuilder();
+
+ int star = filter.indexOf('*');
+
+ while (star != -1) {
+ if (star > 0 && filter.charAt(star - 1) == '\\') {
+ builder.append(Pattern.quote(filter.substring(0, star - 1)));
+ builder.append(Pattern.quote("*"));
+ } else {
+ builder.append(Pattern.quote(filter.substring(0, star)));
+ builder.append(".*");
+ }
+ filter = filter.substring(star + 1);
+ star = filter.indexOf('*');
+ }
+
+ builder.append(Pattern.quote(filter));
+
+ pattern = Pattern.compile(builder.toString());
+ }
+
+ public boolean matches(String name) {
+ return pattern.matcher(name).matches();
+ }
+
+}
\ No newline at end of file
Added: jackrabbit/oak/trunk/oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/filter/Filters.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/filter/Filters.java?rev=1684861&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/filter/Filters.java (added)
+++ jackrabbit/oak/trunk/oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/filter/Filters.java Thu Jun 11 12:09:15 2015
@@ -0,0 +1,77 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jackrabbit.oak.remote.filter;
+
+import java.util.HashSet;
+import java.util.Set;
+
+public class Filters {
+
+ private Set<Filter> includes = new HashSet<Filter>();
+
+ private Set<Filter> excludes = new HashSet<Filter>();
+
+ public Filters(Set<String> filters) {
+ if (filters == null) {
+ throw new IllegalArgumentException("filter set is null");
+ }
+
+ for (String filter : filters) {
+ if (filter == null) {
+ throw new IllegalArgumentException("filter is null");
+ }
+
+ if (filter.length() == 0) {
+ throw new IllegalArgumentException("include filter is an empty string");
+ }
+
+ if (filter.startsWith("-") && filter.length() == 1) {
+ throw new IllegalArgumentException("exclude filter is an empty string");
+ }
+
+ }
+
+ for (String filter : filters) {
+ if (filter.startsWith("-")) {
+ excludes.add(new Filter(filter.substring(1)));
+ } else {
+ includes.add(new Filter(filter));
+ }
+ }
+
+ if (includes.isEmpty()) {
+ includes.add(new Filter("*"));
+ }
+ }
+
+ public boolean matches(String name) {
+ for (Filter include : includes) {
+ if (include.matches(name)) {
+ for (Filter exclude : excludes) {
+ if (exclude.matches(name)) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+ }
+
+ return false;
+ }
+}
\ No newline at end of file
Added: jackrabbit/oak/trunk/oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/http/RemoteHandler.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/http/RemoteHandler.java?rev=1684861&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/http/RemoteHandler.java (added)
+++ jackrabbit/oak/trunk/oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/http/RemoteHandler.java Thu Jun 11 12:09:15 2015
@@ -0,0 +1,49 @@
+/*
+ * 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.oak.remote.http;
+
+import org.apache.jackrabbit.oak.remote.http.handler.Handler;
+import org.apache.jackrabbit.oak.remote.http.matcher.Matcher;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+
+class RemoteHandler implements Matcher, Handler {
+
+ private Matcher matcher;
+
+ private Handler handler;
+
+ public RemoteHandler(Matcher matcher, Handler handler) {
+ this.matcher = matcher;
+ this.handler = handler;
+ }
+
+ @Override
+ public void handle(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
+ handler.handle(request, response);
+ }
+
+ @Override
+ public boolean match(HttpServletRequest request) {
+ return matcher.match(request);
+ }
+
+}
Added: jackrabbit/oak/trunk/oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/http/RemoteServlet.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/http/RemoteServlet.java?rev=1684861&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/http/RemoteServlet.java (added)
+++ jackrabbit/oak/trunk/oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/http/RemoteServlet.java Thu Jun 11 12:09:15 2015
@@ -0,0 +1,110 @@
+/*
+ * 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.oak.remote.http;
+
+import org.apache.jackrabbit.oak.remote.RemoteRepository;
+import org.apache.jackrabbit.oak.remote.http.handler.Handler;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.util.Arrays;
+
+import static org.apache.jackrabbit.oak.remote.http.handler.Handlers.createGetBinaryHandler;
+import static org.apache.jackrabbit.oak.remote.http.handler.Handlers.createGetLastRevisionHandler;
+import static org.apache.jackrabbit.oak.remote.http.handler.Handlers.createGetLastTreeHandler;
+import static org.apache.jackrabbit.oak.remote.http.handler.Handlers.createGetRevisionTreeHandler;
+import static org.apache.jackrabbit.oak.remote.http.handler.Handlers.createHeadBinaryHandler;
+import static org.apache.jackrabbit.oak.remote.http.handler.Handlers.createHeadLastTreeHandler;
+import static org.apache.jackrabbit.oak.remote.http.handler.Handlers.createHeadRevisionTreeHandler;
+import static org.apache.jackrabbit.oak.remote.http.handler.Handlers.createNotFoundHandler;
+import static org.apache.jackrabbit.oak.remote.http.handler.Handlers.createPatchLastRevisionHandler;
+import static org.apache.jackrabbit.oak.remote.http.handler.Handlers.createPatchSpecificRevisionHandler;
+import static org.apache.jackrabbit.oak.remote.http.handler.Handlers.createPostBinaryHandler;
+import static org.apache.jackrabbit.oak.remote.http.handler.Handlers.createSearchLastRevisionHandler;
+import static org.apache.jackrabbit.oak.remote.http.handler.Handlers.createSearchSpecificRevisionHandler;
+import static org.apache.jackrabbit.oak.remote.http.matcher.Matchers.matchesRequest;
+
+public class RemoteServlet extends HttpServlet {
+
+ private static final Logger logger = LoggerFactory.getLogger(RemoteServlet.class);
+
+ private final RemoteRepository repository;
+
+ public RemoteServlet(RemoteRepository repository) {
+ this.repository = repository;
+ }
+
+ @Override
+ protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
+ request.setAttribute("repository", repository);
+
+ try {
+ firstMatching(readHandlers(), request, createNotFoundHandler()).handle(request, response);
+ } catch (ServletException e) {
+ logger.error("unable to serve the current request", e);
+ throw e;
+ } catch (IOException e) {
+ logger.error("I/O error while serving the current request", e);
+ throw e;
+ } catch (Exception e) {
+ logger.error("unexpected error while serving the current request", e);
+ throw new ServletException(e);
+ }
+ }
+
+ private Handler firstMatching(Iterable<RemoteHandler> handlers, HttpServletRequest request, Handler otherwise) {
+ for (RemoteHandler handler : handlers) {
+ if (handler.match(request)) {
+ return handler;
+ }
+ }
+
+ return otherwise;
+ }
+
+ private Iterable<RemoteHandler> readHandlers() {
+ return handlers(
+ handler("get", "/revisions/last", createGetLastRevisionHandler()),
+ handler("get", "/revisions/last/tree/.*", createGetLastTreeHandler()),
+ handler("head", "/revisions/last/tree/.*", createHeadLastTreeHandler()),
+ handler("get", "/revisions/[^/]+/tree/.*", createGetRevisionTreeHandler()),
+ handler("head", "/revisions/[^/]+/tree/.*", createHeadRevisionTreeHandler()),
+ handler("head", "/binaries/.*", createHeadBinaryHandler()),
+ handler("get", "/binaries/.*", createGetBinaryHandler()),
+ handler("post", "/binaries", createPostBinaryHandler()),
+ handler("patch", "/revisions/last/tree", createPatchLastRevisionHandler()),
+ handler("patch", "/revisions/[^/]+/tree", createPatchSpecificRevisionHandler()),
+ handler("get", "/revisions/last/tree", createSearchLastRevisionHandler()),
+ handler("get", "/revisions/[^/]+/tree", createSearchSpecificRevisionHandler())
+ );
+ }
+
+ private Iterable<RemoteHandler> handlers(RemoteHandler... handlers) {
+ return Arrays.asList(handlers);
+ }
+
+ private RemoteHandler handler(String method, String path, Handler handler) {
+ return new RemoteHandler(matchesRequest(method, path), handler);
+ }
+
+}
Added: jackrabbit/oak/trunk/oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/http/handler/AuthenticationWrapperHandler.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/http/handler/AuthenticationWrapperHandler.java?rev=1684861&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/http/handler/AuthenticationWrapperHandler.java (added)
+++ jackrabbit/oak/trunk/oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/http/handler/AuthenticationWrapperHandler.java Thu Jun 11 12:09:15 2015
@@ -0,0 +1,182 @@
+/*
+ * 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.oak.remote.http.handler;
+
+import org.apache.jackrabbit.oak.remote.RemoteCredentials;
+import org.apache.jackrabbit.oak.remote.RemoteLoginException;
+import org.apache.jackrabbit.oak.remote.RemoteRepository;
+import org.apache.jackrabbit.oak.remote.RemoteSession;
+import org.apache.jackrabbit.util.Base64;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+
+import static org.apache.jackrabbit.oak.remote.http.handler.ResponseUtils.sendInternalServerError;
+
+class AuthenticationWrapperHandler implements Handler {
+
+ private static final Logger logger = LoggerFactory.getLogger(AuthenticationWrapperHandler.class);
+
+ private final Handler authenticated;
+
+ private final Handler notAuthenticated;
+
+ public AuthenticationWrapperHandler(Handler authenticated, Handler notAuthenticated) {
+ this.authenticated = authenticated;
+ this.notAuthenticated = notAuthenticated;
+ }
+
+ @Override
+ public void handle(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
+ RemoteSession session = (RemoteSession) request.getAttribute("session");
+
+ if (session != null) {
+ authenticated.handle(request, response);
+ return;
+ }
+
+ RemoteRepository repository = (RemoteRepository) request.getAttribute("repository");
+
+ if (repository == null) {
+ sendInternalServerError(response, "repository not found");
+ return;
+ }
+
+ RemoteCredentials credentials = extractCredentials(request, repository);
+
+ if (credentials == null) {
+ notAuthenticated.handle(request, response);
+ return;
+ }
+
+ try {
+ session = repository.login(credentials);
+ } catch (RemoteLoginException e) {
+ logger.warn("unable to authenticate to the repository", e);
+ notAuthenticated.handle(request, response);
+ return;
+ }
+
+ request.setAttribute("session", session);
+
+ authenticated.handle(request, response);
+ }
+
+ private RemoteCredentials extractCredentials(HttpServletRequest request, RemoteRepository repository) {
+ String authorization = request.getHeader("Authorization");
+
+ if (authorization == null) {
+ return null;
+ }
+
+ String scheme = getScheme(authorization);
+
+ if (!scheme.equalsIgnoreCase("basic")) {
+ return null;
+ }
+
+ String token = getToken(authorization);
+
+ if (token == null) {
+ return null;
+ }
+
+ String decoded;
+
+ try {
+ decoded = Base64.decode(token);
+ } catch (IllegalArgumentException e) {
+ return null;
+ }
+
+ String user = getUser(decoded);
+
+ if (user == null) {
+ return null;
+ }
+
+ String password = getPassword(decoded);
+
+ if (password == null) {
+ return null;
+ }
+
+ return repository.createBasicCredentials(user, password.toCharArray());
+ }
+
+ private String getScheme(String authorization) {
+ int index = authorization.indexOf(' ');
+
+ if (index < 0) {
+ return authorization;
+ }
+
+ return authorization.substring(0, index);
+ }
+
+ private String getToken(String authorization) {
+ int index = authorization.indexOf(' ');
+
+ if (index < 0) {
+ return null;
+ }
+
+ while (index < authorization.length()) {
+ if (authorization.charAt(index) != ' ') {
+ break;
+ }
+
+ index += 1;
+ }
+
+ if (index < authorization.length()) {
+ return authorization.substring(index);
+ }
+
+ return null;
+ }
+
+ private String getUser(String both) {
+ int index = both.indexOf(':');
+
+ if (index < 0) {
+ return null;
+ }
+
+ return both.substring(0, index);
+ }
+
+ private String getPassword(String both) {
+ int index = both.indexOf(':');
+
+ if (index < 0) {
+ return null;
+ }
+
+ if (index + 1 < both.length()) {
+ return both.substring(index + 1);
+ }
+
+ return null;
+ }
+
+}
Added: jackrabbit/oak/trunk/oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/http/handler/GetBinaryHandler.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/http/handler/GetBinaryHandler.java?rev=1684861&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/http/handler/GetBinaryHandler.java (added)
+++ jackrabbit/oak/trunk/oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/http/handler/GetBinaryHandler.java Thu Jun 11 12:09:15 2015
@@ -0,0 +1,319 @@
+/*
+ * 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.oak.remote.http.handler;
+
+import com.google.common.io.ByteStreams;
+import org.apache.jackrabbit.oak.remote.RemoteBinaryFilters;
+import org.apache.jackrabbit.oak.remote.RemoteBinaryId;
+import org.apache.jackrabbit.oak.remote.RemoteSession;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.StringTokenizer;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import static org.apache.jackrabbit.oak.remote.http.handler.ResponseUtils.sendBadRequest;
+import static org.apache.jackrabbit.oak.remote.http.handler.ResponseUtils.sendInternalServerError;
+import static org.apache.jackrabbit.oak.remote.http.handler.ResponseUtils.sendNotFound;
+
+class GetBinaryHandler implements Handler {
+
+ private static final String CONTENT_RANGE_HEADER = "Content-Range";
+
+ private static final String RANGE_HEADER = "Range";
+
+ private static final Pattern RANGE_HEADER_PATTERN = Pattern.compile("^\\s*bytes\\s*=\\s*(.*)\\s*$");
+
+ private static final Pattern RANGE_PATTERN = Pattern.compile("^\\s*(\\d*)\\s*(?:\\s*-\\s*(\\d*))?\\s*$");
+
+ private static final String MULTIPART_DELIMITER = "MULTIPART-DELIMITER";
+
+ private static final Pattern REQUEST_PATTERN = Pattern.compile("^/binaries/(.*)$");
+
+ @Override
+ public void handle(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
+ RemoteSession session = (RemoteSession) request.getAttribute("session");
+
+ if (session == null) {
+ sendInternalServerError(response, "session not found");
+ return;
+ }
+
+ String providedBinaryId = readBinaryId(request);
+
+ if (providedBinaryId == null) {
+ sendBadRequest(response, "unable to read the provided binary ID");
+ return;
+ }
+
+ RemoteBinaryId binaryId = session.readBinaryId(providedBinaryId);
+
+ if (binaryId == null) {
+ sendNotFound(response, "binary ID not found");
+ return;
+ }
+
+ List<RemoteBinaryFilters> contentRanges = parseRequestRanges(request, session, binaryId);
+
+ if (contentRanges == null) {
+ handleFile(response, session, binaryId);
+ } else if (contentRanges.size() == 1) {
+ handleSingleRange(response, session, binaryId, contentRanges.get(0));
+ } else {
+ handleMultipleRanges(response, session, binaryId, contentRanges);
+ }
+ }
+
+ /**
+ * RFC7233
+ * <p/>
+ * This handler sends a 200 OK http status, the Content-Length header and
+ * the entire file/binary content. This is used when the request Range
+ * header is missing or it contains a malformed value.
+ */
+ private void handleFile(HttpServletResponse response, RemoteSession session, RemoteBinaryId binaryId) throws IOException {
+
+ InputStream in = session.readBinary(binaryId, new RemoteBinaryFilters());
+
+ long length = session.readBinaryLength(binaryId);
+
+ response.setStatus(HttpServletResponse.SC_OK);
+ response.setContentType("application/octet-stream");
+ response.setContentLength((int) length);
+
+ OutputStream out = response.getOutputStream();
+
+ ByteStreams.copy(in, out);
+
+ out.close();
+ }
+
+ /**
+ * RFC7233
+ * <p/>
+ * This handler sends a 206 Partial Content http status, the Content-Length
+ * header, the Content-Range header and the requested binary fragment. This
+ * is used when the request Range header contains only one range.
+ */
+ private void handleSingleRange(HttpServletResponse response, RemoteSession session, RemoteBinaryId binaryId, RemoteBinaryFilters range) throws IOException {
+ InputStream in = session.readBinary(binaryId, range);
+
+ long fileLength = session.readBinaryLength(binaryId);
+ long rangeStart = range.getStart();
+ long rangeEnd = rangeStart + range.getCount() - 1;
+
+ response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
+ response.setHeader(CONTENT_RANGE_HEADER, String.format("%d-%d/%d", rangeStart, rangeEnd, fileLength));
+ response.setContentType("application/octet-stream");
+ response.setContentLength((int) (rangeEnd - rangeStart + 1));
+
+ OutputStream out = response.getOutputStream();
+
+ ByteStreams.copy(in, out);
+
+ out.close();
+ }
+
+ /**
+ * RFC7233
+ * <p/>
+ * This handler sends a 206 Partial Content http status, the Content-Length
+ * header, Content-Type multipart/byteranges The payload contains all the
+ * requested binary fragments.
+ * <p/>
+ * This handler is used when multiple ranges are requested.
+ */
+ private void handleMultipleRanges(HttpServletResponse response, RemoteSession session, RemoteBinaryId binaryId, List<RemoteBinaryFilters> ranges) throws IOException {
+
+ String header;
+
+ long rangeStart, rangeEnd, fileLength, contentLength;
+
+ fileLength = session.readBinaryLength(binaryId);
+
+ // Compute response content length
+ // Create multipart headers
+
+ contentLength = 0;
+
+ List<String> multipartHeaders = new ArrayList<String>(ranges.size());
+
+ for (RemoteBinaryFilters range : ranges) {
+ rangeStart = range.getStart();
+ rangeEnd = rangeStart + range.getCount() - 1;
+
+ header = String.format("\n" +
+ "--%s\n" +
+ "Content-Type: application/octet-stream" +
+ "Content-Content-Range: %d-%d/%d\n\n",
+ MULTIPART_DELIMITER, rangeStart, rangeEnd, fileLength);
+
+ multipartHeaders.add(header);
+
+ contentLength += header.getBytes().length;
+ contentLength += range.getCount();
+ }
+
+ // Send response status and headers
+
+ response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
+ response.setContentLength((int) contentLength);
+ response.setContentType("multipart/byteranges; boundary=" + MULTIPART_DELIMITER);
+
+ // Send requested ranges
+
+ RemoteBinaryFilters range;
+
+ InputStream in;
+
+ OutputStream out = response.getOutputStream();
+
+ Iterator<RemoteBinaryFilters> rangeIt = ranges.iterator();
+ Iterator<String> headerIt = multipartHeaders.iterator();
+
+ while (rangeIt.hasNext() && headerIt.hasNext()) {
+ range = rangeIt.next();
+ header = headerIt.next();
+
+ out.write(header.getBytes());
+ in = session.readBinary(binaryId, range);
+ ByteStreams.copy(in, out);
+ }
+
+ out.close();
+ }
+
+ /**
+ * Extract binary id from request path and return it
+ */
+ private String readBinaryId(HttpServletRequest request) {
+ Matcher matcher = REQUEST_PATTERN.matcher(request.getPathInfo());
+
+ if (matcher.matches()) {
+ return matcher.group(1);
+ }
+
+ throw new IllegalStateException("handler bound at the wrong path");
+ }
+
+ /**
+ * This method parses the request Range header a list of ranges as
+ * RemoteBinaryFilters ( or null when the header is missing or contains
+ * invalid/malformed values
+ */
+ private List<RemoteBinaryFilters> parseRequestRanges(HttpServletRequest request, RemoteSession session, RemoteBinaryId binaryId) {
+
+ // Check header exists
+ String headerValue = request.getHeader(RANGE_HEADER);
+
+ if (headerValue == null) {
+ return null;
+ }
+
+ // Check header is bytes=*
+ Matcher matcher = RANGE_HEADER_PATTERN.matcher(headerValue);
+
+ if (!matcher.matches()) {
+ return null;
+ }
+
+ // Iterate requested ranges
+ headerValue = matcher.group(1);
+
+ StringTokenizer tokenizer = new StringTokenizer(headerValue, ",");
+
+ List<RemoteBinaryFilters> ranges = new LinkedList<RemoteBinaryFilters>();
+
+ RemoteBinaryFilters range;
+
+ long fileLength = session.readBinaryLength(binaryId);
+
+ while (tokenizer.hasMoreTokens()) {
+ range = parseRange(tokenizer.nextToken(), fileLength);
+
+ if (range == null) {
+ return null;
+ }
+
+ ranges.add(range);
+ }
+
+ return ranges;
+ }
+
+ /**
+ * Parse a range extracted from the Range header and return a wrapped
+ * RemoteBinaryFilters instance for the range or null if the range is not
+ * valid or malformed.
+ * <p/>
+ * The returned RemoteBinaryFilters object will never return -1 in
+ * getCount.
+ */
+ private RemoteBinaryFilters parseRange(String range, long fileLength) {
+ Matcher matcher = RANGE_PATTERN.matcher(range);
+
+ if (!matcher.matches()) {
+ return null;
+ }
+
+ final long start;
+ final long end;
+
+ // Content-Range: X
+ if (matcher.group(2) == null || matcher.group(2).isEmpty()) {
+ start = Long.parseLong(matcher.group(1));
+ end = fileLength - 1;
+ }
+ // Content-Range: -X
+ else if (matcher.group(1).isEmpty()) {
+ end = fileLength - 1;
+ start = end - Long.parseLong(matcher.group(2)) + 1;
+ }
+ // Content-Range: X-Y
+ else {
+ start = Long.parseLong(matcher.group(1));
+ end = Long.parseLong(matcher.group(2));
+ }
+
+ // Simple range validation
+ if (start < 0 || end < 0 || start > end || end >= fileLength || start >= fileLength) {
+ return null;
+ }
+
+ return new RemoteBinaryFilters() {
+ @Override
+ public long getStart() {
+ return start;
+ }
+
+ @Override
+ public long getCount() {
+ return end - start + 1;
+ }
+ };
+ }
+}
Added: jackrabbit/oak/trunk/oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/http/handler/GetLastRevisionHandler.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/http/handler/GetLastRevisionHandler.java?rev=1684861&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/http/handler/GetLastRevisionHandler.java (added)
+++ jackrabbit/oak/trunk/oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/http/handler/GetLastRevisionHandler.java Thu Jun 11 12:09:15 2015
@@ -0,0 +1,60 @@
+/*
+ * 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.oak.remote.http.handler;
+
+import com.fasterxml.jackson.core.JsonEncoding;
+import com.fasterxml.jackson.core.JsonFactory;
+import com.fasterxml.jackson.core.JsonGenerator;
+import org.apache.jackrabbit.oak.remote.RemoteRevision;
+import org.apache.jackrabbit.oak.remote.RemoteSession;
+
+import javax.servlet.ServletOutputStream;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+
+import static org.apache.jackrabbit.oak.remote.http.handler.ResponseUtils.sendInternalServerError;
+
+class GetLastRevisionHandler implements Handler {
+
+ @Override
+ public void handle(HttpServletRequest request, HttpServletResponse response) throws IOException {
+ RemoteSession session = (RemoteSession) request.getAttribute("session");
+
+ if (session == null) {
+ sendInternalServerError(response, "session not found");
+ return;
+ }
+
+ RemoteRevision revision = session.readLastRevision();
+
+ response.setStatus(HttpServletResponse.SC_OK);
+ response.setContentType("application/json");
+
+ ServletOutputStream stream = response.getOutputStream();
+
+ JsonGenerator generator = new JsonFactory().createJsonGenerator(stream, JsonEncoding.UTF8);
+ generator.writeStartObject();
+ generator.writeStringField("revision", revision.asString());
+ generator.writeEndObject();
+ generator.flush();
+
+ stream.close();
+ }
+
+}
Added: jackrabbit/oak/trunk/oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/http/handler/GetLastTreeHandler.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/http/handler/GetLastTreeHandler.java?rev=1684861&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/http/handler/GetLastTreeHandler.java (added)
+++ jackrabbit/oak/trunk/oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/http/handler/GetLastTreeHandler.java Thu Jun 11 12:09:15 2015
@@ -0,0 +1,46 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jackrabbit.oak.remote.http.handler;
+
+import org.apache.jackrabbit.oak.remote.RemoteRevision;
+import org.apache.jackrabbit.oak.remote.RemoteSession;
+
+import javax.servlet.http.HttpServletRequest;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+class GetLastTreeHandler extends GetTreeHandler {
+
+ private static final Pattern REQUEST_PATTERN = Pattern.compile("^/revisions/last/tree(/.*)$");
+
+ protected String readPath(HttpServletRequest request) {
+ Matcher matcher = REQUEST_PATTERN.matcher(request.getPathInfo());
+
+ if (matcher.matches()) {
+ return matcher.group(1);
+ }
+
+ throw new IllegalStateException("handler bound at the wrong path");
+ }
+
+ @Override
+ protected RemoteRevision readRevision(HttpServletRequest request, RemoteSession session) {
+ return session.readLastRevision();
+ }
+
+}
Added: jackrabbit/oak/trunk/oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/http/handler/GetRevisionTreeHandler.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/http/handler/GetRevisionTreeHandler.java?rev=1684861&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/http/handler/GetRevisionTreeHandler.java (added)
+++ jackrabbit/oak/trunk/oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/http/handler/GetRevisionTreeHandler.java Thu Jun 11 12:09:15 2015
@@ -0,0 +1,53 @@
+/*
+ * 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.oak.remote.http.handler;
+
+import org.apache.jackrabbit.oak.remote.RemoteRevision;
+import org.apache.jackrabbit.oak.remote.RemoteSession;
+
+import javax.servlet.http.HttpServletRequest;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+class GetRevisionTreeHandler extends GetTreeHandler {
+
+ private static final Pattern REQUEST_PATTERN = Pattern.compile("^/revisions/([^/]+)/tree(/.*)$");
+
+ @Override
+ protected String readPath(HttpServletRequest request) {
+ Matcher matcher = REQUEST_PATTERN.matcher(request.getPathInfo());
+
+ if (matcher.matches()) {
+ return matcher.group(2);
+ }
+
+ throw new IllegalStateException("handler bound at the wrong path");
+ }
+
+ @Override
+ protected RemoteRevision readRevision(HttpServletRequest request, RemoteSession session) {
+ Matcher matcher = REQUEST_PATTERN.matcher(request.getPathInfo());
+
+ if (matcher.matches()) {
+ return session.readRevision(matcher.group(1));
+ }
+
+ throw new IllegalStateException("handler bound at the wrong path");
+ }
+
+}