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"); + } + +}
