diff --git oak-remote/pom.xml oak-remote/pom.xml new file mode 100644 index 0000000..a321f0e --- /dev/null +++ oak-remote/pom.xml @@ -0,0 +1,94 @@ + + + + + + 4.0.0 + + + org.apache.jackrabbit + oak-parent + 1.1-SNAPSHOT + ../oak-parent/pom.xml + + + oak-remote + Oak Remote API + + + + com.google.guava + guava + + + javax.jcr + jcr + 2.0 + + + org.apache.jackrabbit + oak-core + 1.1-SNAPSHOT + + + org.apache.jackrabbit + oak-commons + 1.1-SNAPSHOT + + + org.apache.jackrabbit + jackrabbit-jcr-commons + 2.9.0 + + + com.fasterxml.jackson.core + jackson-core + 2.0.0 + + + org.eclipse.jetty + jetty-server + 8.1.10.v20130312 + + + org.eclipse.jetty + jetty-servlet + 8.1.10.v20130312 + + + junit + junit + 4.12 + test + + + org.hamcrest + hamcrest-core + 1.3 + test + + + org.mockito + mockito-all + 1.10.19 + test + + + \ No newline at end of file diff --git oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/RemoteBinaryFilters.java oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/RemoteBinaryFilters.java new file mode 100644 index 0000000..13bec14 --- /dev/null +++ oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/RemoteBinaryFilters.java @@ -0,0 +1,43 @@ +/* + * 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; + +/** + * Represents a set of filters that can be applied when a binary object is read + * from the repository. + */ +public class RemoteBinaryFilters { + + /** + * Return the starting offset into the binary object. This method returns + * {@code 0} by default, meaning that the binary object should be read from + * the beginning. + */ + public long getStart() { + return 0; + } + + /** + * Return the number of bytes to read. This method returns {@code -1} by + * default, meaning that the binary object should be read until the end. + */ + public long getCount() { + return -1; + } + +} diff --git oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/RemoteBinaryId.java oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/RemoteBinaryId.java new file mode 100644 index 0000000..30f1d6f --- /dev/null +++ oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/RemoteBinaryId.java @@ -0,0 +1,37 @@ +/* + * 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; + +/** + * Represents the identifier of a binary object. + *

+ * Binary objects stored in the repository are immutable. The same binary ID is + * guaranteed to return the same binary object over time. + */ +public interface RemoteBinaryId { + + /** + * Returns a string representation of the binary ID. This representation can + * be used as a reference by an external system and converted to a {@code + * RemoteBinaryId} object using {@link RemoteSession#readBinaryId(String)}. + * + * @return A string representation of the binary ID. + */ + String asString(); + +} diff --git oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/RemoteCredentials.java oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/RemoteCredentials.java new file mode 100644 index 0000000..1eec995 --- /dev/null +++ oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/RemoteCredentials.java @@ -0,0 +1,35 @@ +/* + * 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; + +/** + * This interface is a marker interface to be used to represent both an + * authentication strategy and the information to enable that authentication + * strategy. + *

+ * In example, a {@code RemoteCredentials} object created to represent an + * authentication strategy based on user name and password, may encapsulate the + * user name and password to enable this authentication strategy when a session + * to the repository is created. + *

+ * To create instances of this interface, take a look at the methods defined in + * {@link RemoteRepository}. + */ +public interface RemoteCredentials { + +} diff --git oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/RemoteOperation.java oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/RemoteOperation.java new file mode 100644 index 0000000..6080c26 --- /dev/null +++ oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/RemoteOperation.java @@ -0,0 +1,30 @@ +/* + * 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; + +/** + * An operation to modify content inside the repository. + *

+ * This is just a marker interface. Factory methods for {@code RemoteOperation} + * instances are found in {@link RemoteSession}. The interface doesn't currently + * expose any method, as its instances are supposed to be consumed by the same + * {@code RemoteSession} (or by another instance of the same implementation) + * that created them. + */ +public interface RemoteOperation { +} diff --git oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/RemoteRepository.java oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/RemoteRepository.java new file mode 100644 index 0000000..a1d539f --- /dev/null +++ oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/RemoteRepository.java @@ -0,0 +1,70 @@ +/* + * 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; + +import java.util.Set; + +/** + * The remote interface exposed by a repository. + *

+ * Most methods require authentication to be used. As such, a client of this + * interface is required to create an instance of {@code RemoteCredentials} and + * use this instance to login into the repository and obtain a {@code + * RemoteSession} object. + */ +public interface RemoteRepository { + + /** + * Create a {@code RemoteCredentials} object representing an authentication + * strategy based on a user name and a password. This kind of credentials + * delegates authentication to the repository, using a user identified by + * the user name and password provided to this method. + * + * @param user User name. + * @param password Password. + * @return A {@code RemoteCredentials} object representing an authentication + * strategy based on a user name and password. + */ + RemoteCredentials createBasicCredentials(String user, char[] password); + + /** + * Create a {@code RemoteCredentials} object representing an impersonation + * authentication strategy. If this authentication strategy is used, the + * repository will not make any attempt to perform authentication. It will + * instead trust the information provided by the {@code RemoteCredentials} + * and will create a {@code RemoteSession} bound to the principals specified + * to this method. + * + * @param principals The set of principals to impersonate into. + * @return A {@code RemoteCredentials} object representing an authentication + * strategy based on impersonation. + */ + RemoteCredentials createImpersonationCredentials(Set principals); + + /** + * Create a remote session exposing some repository operations. + * + * @param credentials An object representing an authentication strategy to + * be used when invoking repository operations. + * @return An instance of a remote session to invoke repository operations. + * This method can return {@code null} if it's not possible to authenticate + * the provided credentials. + */ + RemoteSession login(RemoteCredentials credentials); + +} diff --git oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/RemoteRevision.java oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/RemoteRevision.java new file mode 100644 index 0000000..bd1e954 --- /dev/null +++ oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/RemoteRevision.java @@ -0,0 +1,42 @@ +/* + * 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; + +/** + * A repository revision represents an immutable state of the repository. + *

+ * Having a revision allows you to perform repeatable reads, because it is + * assured that the state referenced by the revision is immutable. + *

+ * The revision also allows the system to reference a known state of the + * repository when changes are committed. Since a revision references an + * immutable state, committing some changes will create a new state of the + * repository that will be referenced by a new revision. + */ +public interface RemoteRevision { + + /** + * Returns a string representation of the revision. This representation can + * be used as a reference by an external system and converted to a {@code + * RemoteRevision} object using {@link RemoteSession#readRevision(String)}. + * + * @return A string representation of the revision. + */ + String asString(); + +} diff --git oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/RemoteSession.java oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/RemoteSession.java new file mode 100644 index 0000000..1bd792a --- /dev/null +++ oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/RemoteSession.java @@ -0,0 +1,202 @@ +/* + * 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; + +import java.io.InputStream; +import java.util.List; +import java.util.Map; + +/** + * Collection of operations available on a remote repository once the user + * correctly logged in. + *

+ * Operations working on pure content, like reading a tree or committing + * changes, requires a revision. A revision represents a snapshot of the + * repository in a specified point in time. This concept enables repeatable + * reads and consistent writes. + *

+ * When binary data is involved, this interface exposes methods to read and + * write arbitrary binary data from and to the repository. A binary data is + * considered an immutable collection of bytes that can be referenced by an + * identifier. + */ +public interface RemoteSession { + + /** + * Read the latest revision in the repository. + *

+ * This operation is always meant to succeed, because the repository will + * always have an initial revision to return to the caller. + * + * @return The latest revision in the repository. + */ + RemoteRevision readLastRevision(); + + /** + * Read a revision given a string representation of the revision itself. + *

+ * This operation may fail for a number of reasons. In example, the string + * passed to this method is not a valid revision, or this string represents + * a revision that was valid in the past but it is no more valid. + * + * @param revision The string representation of the revision. + * @return The revision represented by the string passed to this method, or + * {@code null} if the string representation is invalid. + */ + RemoteRevision readRevision(String revision); + + /** + * Read a sub-tree from the repository at the given revision. Some filters + * may be applied to the tree to avoid reading unwanted information. + * + * @param revision The revision representing the state of the repository to + * read from. + * @param path The path of the root of the subtree to read. + * @param filters Filters to apply to the returned tree. + * @return The tree requested by the given path, filtered according to the + * provided filters. The method can return {@code null} if the root of the + * tree is not found in the repository for the given revision. + */ + RemoteTree readTree(RemoteRevision revision, String path, RemoteTreeFilters filters); + + /** + * Create an operation to represent the addition of a new node in the + * repository. + * + * @param path Path of the new node to create. + * @param properties Initial set of properties attached to the new node. + * @return An operation representing the addition of a new node. + */ + RemoteOperation createAddOperation(String path, Map properties); + + /** + * Create an operation representing the removal of an existing node from the + * repository. + * + * @param path Path of the node to remove. + * @return An operation representing the removal of an existing node. + */ + RemoteOperation createRemoveOperation(String path); + + /** + * Create an operation representing the creation or modification of a + * property of an existing node. + * + * @param path Path of the node where the property is or will be attached + * to. + * @param name Name of the property to set. + * @param value Value of the property. + * @return An operation representing the creation or modification of a + * property of an existing node. + */ + RemoteOperation createSetOperation(String path, String name, RemoteValue value); + + /** + * Create an operation to represent the removal of an existing property from + * an existing node in the repository. + * + * @param path Path of the node where the property is attached to. + * @param name Name of the property to remove. + * @return An operation representing the removal of a property. + */ + RemoteOperation createUnsetOperation(String path, String name); + + /** + * Create an operation to represent the copy of a subtree into another + * location into the repository. + * + * @param source Path of the root of the subtree to copy. + * @param target Path where the subtree should be copied to. + * @return An operation representing a copy of a subtree. + */ + RemoteOperation createCopyOperation(String source, String target); + + /** + * Create an operation to represent the move of a subtree into another + * location into the repository. + * + * @param source Path of the root of the source subtree to move. + * @param target Path where the subtree should be moved to. + * @return An operation representing a move of a subtree. + */ + RemoteOperation createMoveOperation(String source, String target); + + /** + * Create an operation that represents the aggregation of multiple, simpler + * operations. The aggregated operations are applied in the same sequence + * provided by this method. + * + * @param operations Sequence of operations to aggregate. + * @return An operation that, when executed, will execute the provided + * operations in the provided sequence. + */ + RemoteOperation createAggregateOperation(List operations); + + /** + * Commit some changes to the repository. The changes are represented by an + * operation. The operation will be applied on the repository state + * represented by the given revision. + * + * @param revision Revision where the changes should be applied to. + * @param operation Operation to change the state of the repository. + * @return A new revision representing the new state of the repository where + * the changes are applied. This method can return {@code null} if the + * operations can't be applied because of conflicting changes. + */ + RemoteRevision commit(RemoteRevision revision, RemoteOperation operation); + + /** + * Read a binary ID given a string representation of the binary ID itself. + *

+ * This operations may fail for a number of reasons. In example, the string + * doesn't represent a valid binary ID, or the string represents a binary ID + * that was valid in the past but is no more valid. + * + * @param binaryId String representation of the binary ID. + * @return The binary ID read from the repository. This method may return + * {@code null} if the string representation of the binary ID is not valid. + */ + RemoteBinaryId readBinaryId(String binaryId); + + /** + * Read a binary object from the repository according to the given filters. + *

+ * In the case of a binary object, filters are really simple. At most, it is + * possible to read just a portion of the binary object instead of reading + * it in its entirety. + * + * @param binaryId Binary ID referring to the binary object to read. + * @param filters Filters to apply to the returned binary object. + * @return A stream representing the filtered content of the binary object. + * This method can return {@code null} if the binary object referenced by + * the provided binary ID doesn't exist. + */ + InputStream readBinary(RemoteBinaryId binaryId, RemoteBinaryFilters filters); + + /** + * Write a binary object into the repository and return a binary ID + * referencing to it. + * + * @param stream Stream representing the binary object to write. + * @return Binary ID referencing the binary object written in the + * repository. This method may return {@code null} if it was not possible to + * write the binary object into the repository. + */ + RemoteBinaryId writeBinary(InputStream stream); + +} diff --git oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/RemoteTree.java oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/RemoteTree.java new file mode 100644 index 0000000..0b8c37b --- /dev/null +++ oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/RemoteTree.java @@ -0,0 +1,74 @@ +/* + * 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; + +import java.util.Map; + +/** + * This interface is a recursive data structure representing a view on a + * repository tree. + *

+ * The purpose of the remote tree is not to represent exactly the content as + * stored in the repository, but to provide a view on that content fulfilling + * the filtering options provided by the client when the tree was accessed. + */ +public interface RemoteTree { + + /** + * Read the properties associated to the root of this remote tree. The root + * of this remote tree is represented by the instance of {@code RemoteTree} + * this method is invoked on. + * + * @return the properties associated to the root of this remote tree. + */ + Map getProperties(); + + /** + * Read the children associated to the root of this remote tree. The root of + * this remote tree is represented by the instance of {@code RemoteTree} + * this method is invoked on. The children of this remote tree are + * themselves remote trees. + *

+ * The remote tree may be truncated at some point (e.g. to avoid very deep + * remote trees to be returned), and this is the reason why the values of + * this {@code Map} can be {@code null}. When a {@code null} value is met, + * the consumer of this interface must assume that there is another subtree + * rooted under the corresponding key, but it is not returned to fulfill the + * filtering options provided when this tree was read. + * + * @return The children associated to the root of this remote tree. + */ + Map getChildren(); + + /** + * Return a flag to indicate that this remote tree actually has more + * children than the one returned by {@link #getChildren()}. + *

+ * This flag is important when the repository tree is read using very strict + * filtering options regarding the maximum number of children to return. If + * this method returns {@code true}, a consumer of this interface must + * assume that there are more children than the one attached to the root of + * this tree. They could be retrieved by varying the relevant filtering + * options and performing another read for this subtree. + * + * @return {@true} if this remote tree is not exposing the full set of + * children as stored in the repository, {@code false} otherwise. + */ + boolean hasMoreChildren(); + +} diff --git oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/RemoteTreeFilters.java oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/RemoteTreeFilters.java new file mode 100644 index 0000000..860a7d3 --- /dev/null +++ oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/RemoteTreeFilters.java @@ -0,0 +1,85 @@ +/* + * 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; + +import java.util.Set; + +import static com.google.common.collect.Sets.newHashSet; + +/** + * Represents a set of filters that can be applied when a subtree is read from + * the repository. + */ +public class RemoteTreeFilters { + + /** + * Return the depth of the tree to read. This method returns {@code 0} by + * default, meaning that only the root of the subtree will be returned. The + * default value makes read operation for a subtree look like the read + * operation for a single node. + */ + public int getDepth() { + return 0; + } + + /** + * Return the property filters. This method returns {@code {"*"}} by + * default, meaning that every property will be returned for every node + * included in the subtree. + */ + public Set getPropertyFilters() { + return newHashSet("*"); + } + + /** + * Return the node filters. This method returns a value of {@code {"*"}} by + * default, meaning that every descendant node of the root of the tree wil + * be returned. + */ + public Set getNodeFilters() { + return newHashSet("*"); + } + + /** + * Return the binary threshold. This method returns {@code 0} by default, + * meaning that by default binary properties will be returned as references + * to binary objects, instead of being returned as proper binary objects. + */ + public long getBinaryThreshold() { + return 0; + } + + /** + * Return the start index for children. This method returns {@code 0} by + * default, meaning that children will be read from the beginning when + * reading the root node and every descendant. + */ + public int getChildrenStart() { + return 0; + } + + /** + * Return the maximum number of children to return. This method returns + * {@code -1} by default, meaning that every children will be returned when + * reading the root node and every descendant. + */ + public int getChildrenCount() { + return -1; + } + +} diff --git oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/RemoteValue.java oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/RemoteValue.java new file mode 100644 index 0000000..321408d --- /dev/null +++ oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/RemoteValue.java @@ -0,0 +1,1431 @@ +/* + * 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; + +import java.io.InputStream; +import java.math.BigDecimal; + +/** + * Represents a value that can be assigned to a property in the repository. + * Client of the remote repository provides values as instances of this class. + */ +public class RemoteValue { + + /** + * A generic interface to represent a supplier of an item. + *

+ * In the specific, it is used by values whose underlying implementation is + * an {@code InputStream}. To enable multiple traversals of {@code + * InputStream}s, the value is wrapped by this interface to effectively have + * a factory over the underlying {@code InputStream}. + * + * @param Type of the item this object is able to create. + */ + public static interface Supplier { + + T get(); + + } + + /** + * This class helps executing logic that depends on the type of a remote + * value. Instead of manually branching code depending on the result of + * {@code isText}, {@code isBoolean} and so on, a handler can be implemented + * to provide different logic for different types. + */ + public static class TypeHandler { + + public void isBinary(Supplier value) { + throw new IllegalStateException("case not handled"); + } + + public void isMultiBinary(Iterable> value) { + throw new IllegalStateException("case not handled"); + } + + public void isBinaryId(String value) { + throw new IllegalStateException("case not handled"); + } + + public void isMultiBinaryId(Iterable value) { + throw new IllegalStateException("case not handled"); + } + + public void isBoolean(Boolean value) { + throw new IllegalStateException("case not handled"); + } + + public void isMultiBoolean(Iterable value) { + throw new IllegalStateException("case not handled"); + } + + public void isDate(Long value) { + throw new IllegalStateException("case not handled"); + } + + public void isMultiDate(Iterable value) { + throw new IllegalStateException("case not handled"); + } + + public void isDecimal(BigDecimal value) { + throw new IllegalStateException("case not handled"); + } + + public void isMultiDecimal(Iterable value) { + throw new IllegalStateException("case not handled"); + } + + public void isDouble(Double value) { + throw new IllegalStateException("case not handled"); + } + + public void isMultiDouble(Iterable value) { + throw new IllegalStateException("case not handled"); + } + + public void isLong(Long value) { + throw new IllegalStateException("case not handled"); + } + + public void isMultiLong(Iterable value) { + throw new IllegalStateException("case not handled"); + } + + public void isName(String value) { + throw new IllegalStateException("case not handled"); + } + + public void isMultiName(Iterable value) { + throw new IllegalStateException("case not handled"); + } + + public void isPath(String value) { + throw new IllegalStateException("case not handled"); + } + + public void isMultiPath(Iterable value) { + throw new IllegalStateException("case not handled"); + } + + public void isReference(String value) { + throw new IllegalStateException("case not handled"); + } + + public void isMultiReference(Iterable value) { + throw new IllegalStateException("case not handled"); + } + + public void isText(String value) { + throw new IllegalStateException("case not handled"); + } + + public void isMultiText(Iterable value) { + throw new IllegalStateException("case not handled"); + } + + public void isUri(String value) { + throw new IllegalStateException("case not handled"); + } + + public void isMultiUri(Iterable value) { + throw new IllegalStateException("case not handled"); + } + + public void isWeakReference(String value) { + throw new IllegalStateException("case not handled"); + } + + public void isMultiWeakReference(Iterable value) { + throw new IllegalStateException("case not handled"); + } + + public void isUnknown() { + throw new IllegalStateException("case not handled"); + } + } + + /** + * Create a remote value of type string. + * + * @param value The string wrapped by the remote value. + * @return A remote value of type string wrapping the provided value. + */ + public static RemoteValue toText(final String value) { + return new RemoteValue() { + + @Override + public boolean isText() { + return true; + } + + @Override + public String asText() { + return value; + } + + }; + } + + /** + * Create a remote multi-value of type string. + * + * @param value The collection of strings wrapped by the remote value. + * @return A remote multi-value of type string wrapping the provided + * collection of strings. + */ + public static RemoteValue toMultiText(final Iterable value) { + return new RemoteValue() { + + @Override + public boolean isMultiText() { + return true; + } + + @Override + public Iterable asMultiText() { + return value; + } + + }; + } + + /** + * Create a remote value of type binary. + * + * @param value The factory of input streams wrapped by the remote value. + * @return A remote value of type binary wrapping the provided factory of + * input streams. + */ + public static RemoteValue toBinary(final Supplier value) { + return new RemoteValue() { + + @Override + public boolean isBinary() { + return true; + } + + @Override + public Supplier asBinary() { + return value; + } + + }; + } + + /** + * Create a remote multi-value of type binary. + * + * @param value The collection of factories of input streams wrapped by the + * remote value. + * @return A remote multi-value of type binary wrapping the provided + * collection of input streams. + */ + public static RemoteValue toMultiBinary(final Iterable> value) { + return new RemoteValue() { + + @Override + public boolean isMultiBinary() { + return true; + } + + @Override + public Iterable> asMultiBinary() { + return value; + } + + }; + } + + /** + * Create a remote value of type binary ID. + * + * @param value The binary ID wrapped by the remote value. + * @return A remote value wrapping the provided binary ID. + */ + public static RemoteValue toBinaryId(final String value) { + return new RemoteValue() { + + @Override + public boolean isBinaryId() { + return true; + } + + @Override + public String asBinaryId() { + return value; + } + + }; + } + + /** + * Create a remote multi-value of type binary ID. + * + * @param value The collection of binary IDs wrapped by the remote value. + * @return A remote multi-value wrapping the provided collection of binary + * IDs. + */ + public static RemoteValue toMultiBinaryId(final Iterable value) { + return new RemoteValue() { + + @Override + public boolean isMultiBinaryId() { + return true; + } + + @Override + public Iterable asMultiBinaryId() { + return value; + } + + }; + } + + /** + * Create a remote value of type long. + * + * @param value The long to wrap in a remote value. + * @return A remote value of type long wrapping the provided long value. + */ + public static RemoteValue toLong(final long value) { + return new RemoteValue() { + + @Override + public boolean isLong() { + return true; + } + + + @Override + public Long asLong() { + return value; + } + + }; + } + + /** + * Create a remote multi-value of type long. + * + * @param value The collection of long values to wrap in a remote value. + * @return A remote multi-value of type long wrapping the provided + * collection of long values. + */ + public static RemoteValue toMultiLong(final Iterable value) { + return new RemoteValue() { + + @Override + public boolean isMultiLong() { + return true; + } + + @Override + public Iterable asMultiLong() { + return value; + } + + }; + } + + /** + * Create a remote value of type double. + * + * @param value The double value to wrap into a remote value. + * @return A remote value wrapping the provided remote value. + */ + public static RemoteValue toDouble(final double value) { + return new RemoteValue() { + + @Override + public boolean isDouble() { + return true; + } + + @Override + public Double asDouble() { + return value; + } + + }; + } + + /** + * Create a remote multi-value of type double. + * + * @param value The collection of double values to wrap into a remote + * value. + * @return A remote multi-value of type double wrapping the provided + * collection of double values. + */ + public static RemoteValue toMultiDouble(final Iterable value) { + return new RemoteValue() { + + @Override + public boolean isMultiDouble() { + return true; + } + + @Override + public Iterable asMultiDouble() { + return value; + } + + }; + } + + /** + * Create a remote value of type date. + * + * @param value The date to wrap into a remote value. The date is expressed + * in milliseconds since January 1, 1970, 00:00:00 GMT. + * @return A remote value of type date wrapping the provided date. + */ + public static RemoteValue toDate(final long value) { + return new RemoteValue() { + + @Override + public boolean isDate() { + return true; + } + + @Override + public Long asDate() { + return value; + } + + }; + } + + /** + * Create a remote multi-value of type date. + * + * @param value The collection of dates to wrap into a remote value. Every + * date is expressed in milliseconds since January 1, 1970, + * 00:00:00 GMT. + * @return A remote multi-value of type date wrapping the provided + * collection of dates. + */ + public static RemoteValue toMultiDate(final Iterable value) { + return new RemoteValue() { + + @Override + public boolean isMultiDate() { + return true; + } + + @Override + public Iterable asMultiDate() { + return value; + } + + }; + } + + /** + * Create a remote value of type boolean. + * + * @param value The boolean value to wrap into a remote value. + * @return A remote value wrapping the provided boolean value. + */ + public static RemoteValue toBoolean(final boolean value) { + return new RemoteValue() { + + @Override + public boolean isBoolean() { + return true; + } + + @Override + public Boolean asBoolean() { + return value; + } + + }; + } + + /** + * Create a remote multi-value of type boolean. + * + * @param value The collection of boolean values to wrap into a remote + * value. + * @return A remote value wrapping the provided collection of boolean + * values. + */ + public static RemoteValue toMultiBoolean(final Iterable value) { + return new RemoteValue() { + + @Override + public boolean isMultiBoolean() { + return true; + } + + @Override + public Iterable asMultiBoolean() { + return value; + } + + }; + } + + /** + * Create a remote value of type name. + * + * @param value The name to wrap into a remote value. A name is represented + * as a string. + * @return A remote value of type name wrapping the provided string. + */ + public static RemoteValue toName(final String value) { + return new RemoteValue() { + + @Override + public boolean isName() { + return true; + } + + @Override + public String asName() { + return value; + } + + }; + } + + /** + * Create a remote multi-value of type name. + * + * @param value The collection of names to wrap into a remote value. Every + * name is represented by a string. + * @return A remote multi-value of type name wrapping the provided + * collection of strings. + */ + public static RemoteValue toMultiName(final Iterable value) { + return new RemoteValue() { + + @Override + public boolean isMultiName() { + return true; + } + + @Override + public Iterable asMultiName() { + return value; + } + + }; + } + + /** + * Create a remote value of type path. + * + * @param value The path to wrap into the remote value. A path is + * represented by a string. + * @return A remote value of type path wrapping the provided string. + */ + public static RemoteValue toPath(final String value) { + return new RemoteValue() { + + @Override + public boolean isPath() { + return true; + } + + @Override + public String asPath() { + return value; + } + + }; + } + + /** + * Create a remote multi-value of type path. + * + * @param value The collection of paths to wrap into a remote value. Every + * path is represented by a string. + * @return A remote multi-value of type path wrapping the provided strings. + */ + public static RemoteValue toMultiPath(final Iterable value) { + return new RemoteValue() { + + @Override + public boolean isMultiPath() { + return true; + } + + @Override + public Iterable asMultiPath() { + return value; + } + + }; + } + + /** + * Create a remote value of type reference. + * + * @param value The reference to wrap in a remote value. The reference is + * represented by a string. + * @return A remote value of type reference wrapping the provided string. + */ + public static RemoteValue toReference(final String value) { + return new RemoteValue() { + + @Override + public boolean isReference() { + return true; + } + + @Override + public String asReference() { + return value; + } + + }; + } + + /** + * Create a remote multi-value of type reference. + * + * @param value The collection of references to wrap in a remote value. + * Every reference is represented by a string. + * @return A remote multi-value of type reference wrapping the provided + * collection of strings. + */ + public static RemoteValue toMultiReference(final Iterable value) { + return new RemoteValue() { + + @Override + public boolean isMultiReference() { + return true; + } + + @Override + public Iterable asMultiReference() { + return value; + } + + }; + } + + /** + * Create a remote value of type weak reference. + * + * @param value The weak reference to wrap into a remote value. The weak + * reference is represented by a string value. + * @return A remote value of type weak reference wrapping the provided + * string value. + */ + public static RemoteValue toWeakReference(final String value) { + return new RemoteValue() { + + @Override + public boolean isWeakReference() { + return true; + } + + @Override + public String asWeakReference() { + return value; + } + + }; + } + + /** + * Create a remote multi-value of type weak reference. + * + * @param value The collection of weak references to wrap into a remote + * value. Every weak reference is represented by a string. + * @return A remote multi-value of type weak reference wrapping the provided + * collection of strings. + */ + public static RemoteValue toMultiWeakReference(final Iterable value) { + return new RemoteValue() { + + @Override + public boolean isMultiWeakReference() { + return true; + } + + @Override + public Iterable asMultiWeakReference() { + return value; + } + + }; + } + + /** + * Create a remote value of type URI. + * + * @param value The string representation of the URI to wrap into a remote + * value. + * @return A remote value of type URI wrapping the provided string. + */ + public static RemoteValue toUri(final String value) { + return new RemoteValue() { + + @Override + public boolean isUri() { + return true; + } + + @Override + public String asUri() { + return value; + } + + }; + } + + /** + * Create a remote multi-value of type URI. + * + * @param value The collection of URIs to wrap into the remote value. Every + * URI is represented by a string. + * @return A remote multi-value of type URI wrapping the provided collection + * of strings. + */ + public static RemoteValue toMultiUri(final Iterable value) { + return new RemoteValue() { + + @Override + public boolean isMultiUri() { + return true; + } + + @Override + public Iterable asMultiUri() { + return value; + } + + }; + } + + /** + * Create a remote value of type decimal. + * + * @param value The decimal to wrap into a remote value. + * @return A remote value of type decimal wrapping the provided decimal + * value. + */ + public static RemoteValue toDecimal(final BigDecimal value) { + return new RemoteValue() { + + @Override + public boolean isDecimal() { + return true; + } + + @Override + public BigDecimal asDecimal() { + return value; + } + + }; + } + + /** + * Create a remote multi-value of type decimal. + * + * @param value The collection of decimals to wrap into a remote value. + * @return A remote multi-value of type decimal wrapping the provided + * collection of decimals. + */ + public static RemoteValue toMultiDecimal(final Iterable value) { + return new RemoteValue() { + + @Override + public boolean isMultiDecimal() { + return true; + } + + @Override + public Iterable asMultiDecimal() { + return value; + } + + }; + } + + private RemoteValue() { + + } + + /** + * Check if this remote value is of type string. + * + * @return {@code true} if this remote value is of type string, {@code + * false} otherwise. + */ + public boolean isText() { + return false; + } + + /** + * Read the value of this remote string value. + * + * @return The string wrapped by this remote value if this remote value is + * of type string, {@code null} otherwise. + */ + public String asText() { + return null; + } + + /** + * Check if this remote value is a multi-value of type string. + * + * @return {@code true} if this remote value is a multi-value of type + * string, {@code false} otherwise. + */ + public boolean isMultiText() { + return false; + } + + /** + * Read the value of this remote string multi-value. + * + * @return The collection of strings wrapped by this remote value if this + * remote value is a multi-value of type string, {@code null} otherwise. + */ + public Iterable asMultiText() { + return null; + } + + /** + * Check if this remote value is of type binary. + * + * @return {@code true} if this remote value is of type binary, {@code + * false} otherwise. + */ + public boolean isBinary() { + return false; + } + + /** + * Read the value of this remote boolean value. + * + * @return The value of this remote value if this remote value is of type + * binary, {@code null} otherwise. + */ + public Supplier asBinary() { + return null; + } + + /** + * Check if this remote value is a multi-value of type binary. + * + * @return {@code true} if this remote value is a multi-value of type + * binary, {@code false} otherwise. + */ + public boolean isMultiBinary() { + return false; + } + + /** + * Read the value of this remote binary multi-value. + * + * @return The value of this remote value if this remote value is a + * multi-value of type binary, {@code null} otherwise. + */ + public Iterable> asMultiBinary() { + return null; + } + + /** + * Check if this remote value is of type binary ID. + * + * @return {@code true} if this remote value is of type binary ID, {@code + * false} otherwise. + */ + public boolean isBinaryId() { + return false; + } + + /** + * Read the value of this remote binary ID multi-value. + * + * @return The value of this remote value if this remote value is of type + * binary ID, {@code null} otherwise. + */ + public String asBinaryId() { + return null; + } + + /** + * Check if this remote value is a multi-value of type binary ID. + * + * @return {@code true} if this remote value is a multi-value of type binary + * ID, {@code false} otherwise. + */ + public boolean isMultiBinaryId() { + return false; + } + + /** + * Return the value of this remote binary ID multi-value. + * + * @return The value of this remote value if this remote value is a + * multi-value of type binary ID, {@code null} otherwise. + */ + public Iterable asMultiBinaryId() { + return null; + } + + /** + * Check if this remote value is of type long. + * + * @return {@code true} if this remote value is of type long, {@code false} + * otherwise. + */ + public boolean isLong() { + return false; + } + + /** + * Read the value of this remote long multi-value. + * + * @return The value of this remote value if this remote value is of type + * long, {@code null} otherwise. + */ + public Long asLong() { + return null; + } + + /** + * Check if this remote value is a multi-value of type long. + * + * @return {@code true} if this value is a multi-value of type long, {@code + * false} otherwise. + */ + public boolean isMultiLong() { + return false; + } + + /** + * Read the value of this remote multi-value of type long. + * + * @return The value of this remote value if this remote value is a + * multi-value of type long, {@code null} otherwise. + */ + public Iterable asMultiLong() { + return null; + } + + /** + * Check if this remote value is of type double. + * + * @return {@code true} if this remote value is of type long, {@code false} + * otherwise. + */ + public boolean isDouble() { + return false; + } + + /** + * Read the value of this remote value of type double. + * + * @return The value of this remote value if this remote value is of type + * double, {@code null} otherwise. + */ + public Double asDouble() { + return null; + } + + /** + * Check if this remote value is a multi-value of type double. + * + * @return {@code true} if this remote value is a multi-value of type + * double, {@code false} otherwise. + */ + public boolean isMultiDouble() { + return false; + } + + /** + * Read the value of this remote multi-value of type double. + * + * @return The value of this remote value if this remote value is a + * multi-value of type double, {@code null} otherwise. + */ + public Iterable asMultiDouble() { + return null; + } + + /** + * Check if this remote value is of type date. + * + * @return {@code true} if this remote value is of type date, {@code false} + * otherwise. + */ + public boolean isDate() { + return false; + } + + /** + * Read the value of this remote multi-value of type date. + * + * @return The value of this remote value if this remote value is of type + * date, {@code null} otherwise. + */ + public Long asDate() { + return null; + } + + /** + * Check if this remote value is a multi-value of type date. + * + * @return {@code true} if this remote value is a multi-value of type date, + * {@code false} otherwise. + */ + public boolean isMultiDate() { + return false; + } + + /** + * Read the value of this remote multi-value of type date. + * + * @return The value of this remote value if this remote value is a + * multi-value of type date, {@code null} otherwise. + */ + public Iterable asMultiDate() { + return null; + } + + /** + * Check if this remote value is of type boolean. + * + * @return {@code true} if this remote value is fo type boolean, {@code + * false} otherwise. + */ + public boolean isBoolean() { + return false; + } + + /** + * Read the value of this remote value of type boolean. + * + * @return The value of this remote value if this remote value is of type + * boolean, false otherwise. + */ + public Boolean asBoolean() { + return null; + } + + /** + * Check if this remote value is a multi-value of type boolean. + * + * @return {@code true} if this remote value is a multi-value of type + * boolean, {@code false} otherwise. + */ + public boolean isMultiBoolean() { + return false; + } + + /** + * Read the value of this remote multi-value of type boolean. + * + * @return The value of this remote value if this remote value is a + * multi-value of type boolean, {@code null} otherwise. + */ + public Iterable asMultiBoolean() { + return null; + } + + /** + * Check if this remote value is of type name. + * + * @return {@code true} if this remote value is of type name, {@code false} + * otherwise. + */ + public boolean isName() { + return false; + } + + /** + * Read the value of this remote value of type name. + * + * @return The value of this remote value if this remote value is of type + * name, {@code null} otherwise. + */ + public String asName() { + return null; + } + + /** + * Check if this remote value is a multi-value of type name. + * + * @return {@code true} if this remote value is a multi-value of type name, + * {@code false} otherwise. + */ + public boolean isMultiName() { + return false; + } + + /** + * Read the value of this remote multi-value of type name. + * + * @return The value of this remote value if this remote value is a + * multi-value of type name, {@code null} otherwise. + */ + public Iterable asMultiName() { + return null; + } + + /** + * Check if this value is of type path. + * + * @return {@code true} if this remote value is of type path, {@code false} + * otherwise. + */ + public boolean isPath() { + return false; + } + + /** + * Read the value of this remote value of type path. + * + * @return The value of this remote value if this remote value is of type + * path, {@code null} otherwise. + */ + public String asPath() { + return null; + } + + /** + * Check if this remote value is a multi-value of type path. + * + * @return {@code true} if this remote value is a multi-value of type path, + * {@code false} otherwise. + */ + public boolean isMultiPath() { + return false; + } + + /** + * Read the value of this remote multi-value of type path. + * + * @return The value of this remote value if this remote value is a + * multi-value of type path, {@code null} otherwise. + */ + public Iterable asMultiPath() { + return null; + } + + /** + * Check if this remote value is of type reference. + * + * @return {@code true} if this remote value is of type reference, {@code + * false} otherwise. + */ + public boolean isReference() { + return false; + } + + /** + * Read the value of this remote value of type reference. + * + * @return The value of this remote value if this remote value is of type + * reference, {@code null} otherwise. + */ + public String asReference() { + return null; + } + + /** + * Check if this remote value is a multi-value of type reference. + * + * @return {@code true} if this remote value is a multi-value of type + * reference, {@code false} otherwise. + */ + public boolean isMultiReference() { + return false; + } + + /** + * Read the value of this remote multi-value of type reference. + * + * @return The value of this remote value if this remote value is a + * multi-value of type reference, {@code null} otherwise. + */ + public Iterable asMultiReference() { + return null; + } + + /** + * Check if this remote value is of type weak reference. + * + * @return {@code true} if this remote value is fo type weak reference, + * {@code false} otherwise. + */ + public boolean isWeakReference() { + return false; + } + + /** + * Read the value of this remote value of type weak reference. + * + * @return The value of this remote value if this remote value is of type + * weak reference, {@code null} otherwise. + */ + public String asWeakReference() { + return null; + } + + /** + * Check if this remote value is a multi-value of type weak reference. + * + * @return {@code true} if this remote value is a multi-value of type weak + * reference, {@code false} otherwise. + */ + public boolean isMultiWeakReference() { + return false; + } + + /** + * Read the value of this remote multi-value of type weak reference. + * + * @return The value of this remote value if this remote value is a + * multi-value of type weak reference, {@code null} otherwise. + */ + public Iterable asMultiWeakReference() { + return null; + } + + /** + * Check if this remote value is of type decimal. + * + * @return {@code true} if this remote value is of type decimal, {@code + * false} otherwise. + */ + public boolean isDecimal() { + return false; + } + + /** + * Read the value of this remote value of type decimal. + * + * @return The value of this remote value if this remote value is of type + * decimal, {@code null} otherwise. + */ + public BigDecimal asDecimal() { + return null; + } + + /** + * Check if this remote value is a multi-value of type decimal. + * + * @return {@code true} if this remote value is a multi-value of type + * decimal, {@code false} otherwise. + */ + public boolean isMultiDecimal() { + return false; + } + + /** + * Read the value of this remote multi-value of type decimal. + * + * @return The value of this remote value if this remote value is a + * multi-value of type decimal, {@code null} otherwise. + */ + public Iterable asMultiDecimal() { + return null; + } + + /** + * Check if this remote value is of type URI. + * + * @return {@code true} if this remote value is of type URI, {@code false} + * otherwise. + */ + public boolean isUri() { + return false; + } + + /** + * Read the value of this remote value of type URI. + * + * @return The value of this remote value if this remote value is of type + * URI, {@code null} otherwise. + */ + public String asUri() { + return null; + } + + /** + * Check if this remote value is a multi-value of type URI. + * + * @return {@code true} if this remote value is a multi-value of type URI, + * {@code false} otherwise. + */ + public boolean isMultiUri() { + return false; + } + + /** + * Read the value of this remote multi-value of type URI. + * + * @return The value of this remote value if this remote value is a + * multi-value of type URI, {@code null} otherwise. + */ + public Iterable asMultiUri() { + return null; + } + + /** + * Calls a method of the provided handler according to the type of this + * remote value. + * + * @param handler Handler containing logic to be executing according to the + * type of this remote value. + */ + public void whenType(TypeHandler handler) { + if (isBinary()) { + handler.isBinary(asBinary()); + return; + } + + if (isMultiBinary()) { + handler.isMultiBinary(asMultiBinary()); + return; + } + + if (isBinaryId()) { + handler.isBinaryId(asBinaryId()); + return; + } + + if (isMultiBinaryId()) { + handler.isMultiBinaryId(asMultiBinaryId()); + return; + } + + if (isBoolean()) { + handler.isBoolean(asBoolean()); + return; + } + + if (isMultiBoolean()) { + handler.isMultiBoolean(asMultiBoolean()); + return; + } + + if (isDate()) { + handler.isDate(asDate()); + return; + } + + if (isMultiDate()) { + handler.isMultiDate(asMultiDate()); + return; + } + + if (isDecimal()) { + handler.isDecimal(asDecimal()); + return; + } + + if (isMultiDecimal()) { + handler.isMultiDecimal(asMultiDecimal()); + return; + } + + if (isDouble()) { + handler.isDouble(asDouble()); + return; + } + + if (isMultiDouble()) { + handler.isMultiDouble(asMultiDouble()); + return; + } + + if (isLong()) { + handler.isLong(asLong()); + return; + } + + if (isMultiLong()) { + handler.isMultiLong(asMultiLong()); + return; + } + + if (isName()) { + handler.isName(asName()); + return; + } + + if (isMultiName()) { + handler.isMultiName(asMultiName()); + return; + } + + if (isPath()) { + handler.isPath(asPath()); + return; + } + + if (isMultiPath()) { + handler.isMultiPath(asMultiPath()); + return; + } + + if (isReference()) { + handler.isReference(asReference()); + return; + } + + if (isMultiReference()) { + handler.isMultiReference(asMultiReference()); + return; + } + + if (isText()) { + handler.isText(asText()); + return; + } + + if (isMultiText()) { + handler.isMultiText(asMultiText()); + return; + } + + if (isUri()) { + handler.isUri(asUri()); + return; + } + + if (isMultiUri()) { + handler.isMultiUri(asMultiUri()); + return; + } + + if (isWeakReference()) { + handler.isWeakReference(asWeakReference()); + return; + } + + if (isMultiWeakReference()) { + handler.isMultiWeakReference(asMultiWeakReference()); + return; + } + + handler.isUnknown(); + } + +} diff --git oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/content/AddContentRemoteOperation.java oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/content/AddContentRemoteOperation.java new file mode 100644 index 0000000..6a16b7c --- /dev/null +++ oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/content/AddContentRemoteOperation.java @@ -0,0 +1,48 @@ +/* + * 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; + +class AddContentRemoteOperation implements ContentRemoteOperation { + + private final String path; + + public AddContentRemoteOperation(String path) { + this.path = path; + } + + @Override + public void apply(Root root) { + Tree tree = root.getTree(path); + + if (tree.exists()) { + throw new IllegalStateException("node already exists"); + } + + Tree parent = tree.getParent(); + + if (!parent.exists()) { + throw new IllegalStateException("parent node does not exist"); + } + + parent.addChild(tree.getName()); + } + +} diff --git oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/content/AggregateContentRemoteOperation.java oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/content/AggregateContentRemoteOperation.java new file mode 100644 index 0000000..e7fe349 --- /dev/null +++ oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/content/AggregateContentRemoteOperation.java @@ -0,0 +1,50 @@ +/* + * 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.RemoteOperation; + +import java.util.List; + +class AggregateContentRemoteOperation implements ContentRemoteOperation { + + private final List operations; + + public AggregateContentRemoteOperation(List operations) { + this.operations = operations; + } + + @Override + public void apply(Root root) { + for (RemoteOperation operation : operations) { + ContentRemoteOperation contentRemoteOperation = null; + + if (operation instanceof ContentRemoteOperation) { + contentRemoteOperation = (ContentRemoteOperation) operation; + } + + if (contentRemoteOperation == null) { + throw new IllegalStateException("invalid operation"); + } + + contentRemoteOperation.apply(root); + } + } + +} diff --git oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/content/BasicContentRemoteCredentials.java oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/content/BasicContentRemoteCredentials.java new file mode 100644 index 0000000..2e0d8a7 --- /dev/null +++ oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/content/BasicContentRemoteCredentials.java @@ -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.content; + +import org.apache.jackrabbit.oak.api.ContentRepository; +import org.apache.jackrabbit.oak.api.ContentSession; + +import javax.jcr.SimpleCredentials; + +class BasicContentRemoteCredentials implements ContentRemoteCredentials { + + private final String user; + + private final char[] password; + + public BasicContentRemoteCredentials(String user, char[] password) { + this.user = user; + this.password = password; + } + + @Override + public ContentSession login(ContentRepository repository) { + ContentSession session; + + try { + session = repository.login(new SimpleCredentials(user, password), null); + } catch (Exception e) { + session = null; + } + + return session; + } + +} diff --git oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/content/ContentRemoteBinaryId.java oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/content/ContentRemoteBinaryId.java new file mode 100644 index 0000000..952c1a0 --- /dev/null +++ oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/content/ContentRemoteBinaryId.java @@ -0,0 +1,43 @@ +/* + * 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.remote.RemoteBinaryId; + +class ContentRemoteBinaryId implements RemoteBinaryId { + + private final String reference; + + private final Blob blob; + + public ContentRemoteBinaryId(String reference, Blob blob) { + this.reference = reference; + this.blob = blob; + } + + public Blob asBlob() { + return blob; + } + + @Override + public String asString() { + return reference; + } + +} diff --git oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/content/ContentRemoteCredentials.java oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/content/ContentRemoteCredentials.java new file mode 100644 index 0000000..fe2d63f --- /dev/null +++ oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/content/ContentRemoteCredentials.java @@ -0,0 +1,28 @@ +/* + * 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.ContentRepository; +import org.apache.jackrabbit.oak.api.ContentSession; +import org.apache.jackrabbit.oak.remote.RemoteCredentials; + +interface ContentRemoteCredentials extends RemoteCredentials { + + ContentSession login(ContentRepository repository); + +} diff --git oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/content/ContentRemoteInputStream.java oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/content/ContentRemoteInputStream.java new file mode 100644 index 0000000..57b630a --- /dev/null +++ oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/content/ContentRemoteInputStream.java @@ -0,0 +1,72 @@ +/* + * 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.remote.RemoteBinaryFilters; + +import java.io.IOException; +import java.io.InputStream; + +class ContentRemoteInputStream extends InputStream { + + private final InputStream stream; + + private final long start; + + private final long count; + + private long index = 0; + + public ContentRemoteInputStream(InputStream stream, RemoteBinaryFilters filters) { + this.stream = stream; + + long startFilter = filters.getStart(); + + if (startFilter > 0) { + this.start = startFilter; + } else { + this.start = 0; + } + + this.count = filters.getCount(); + } + + @Override + public int read() throws IOException { + while (index < start - 1) { + long skipped = stream.skip(index); + + if (skipped <= 0) { + return -1; + } + + index = index + skipped; + } + + if (count >= 0 && index >= start + count) { + return -1; + } + + int result = stream.read(); + + index = index + 1; + + return result; + } + +} diff --git oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/content/ContentRemoteOperation.java oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/content/ContentRemoteOperation.java new file mode 100644 index 0000000..dd97a30 --- /dev/null +++ oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/content/ContentRemoteOperation.java @@ -0,0 +1,27 @@ +/* + * 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.RemoteOperation; + +interface ContentRemoteOperation extends RemoteOperation { + + void apply(Root root); + +} diff --git oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/content/ContentRemoteRepository.java oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/content/ContentRemoteRepository.java new file mode 100644 index 0000000..d8284f0 --- /dev/null +++ oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/content/ContentRemoteRepository.java @@ -0,0 +1,155 @@ +/* + * 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.ContentRepository; +import org.apache.jackrabbit.oak.api.ContentSession; +import org.apache.jackrabbit.oak.api.Root; +import org.apache.jackrabbit.oak.remote.RemoteCredentials; +import org.apache.jackrabbit.oak.remote.RemoteOperation; +import org.apache.jackrabbit.oak.remote.RemoteRepository; +import org.apache.jackrabbit.oak.remote.RemoteRevision; +import org.apache.jackrabbit.oak.remote.RemoteSession; + +import java.util.Set; + +public class ContentRemoteRepository implements RemoteRepository { + + private final ContentRepository contentRepository; + + private final ContentRemoteRevisions contentRemoteRevisions; + + public ContentRemoteRepository(ContentRepository contentRepository) { + this.contentRemoteRevisions = new ContentRemoteRevisions(); + this.contentRepository = contentRepository; + } + + @Override + public RemoteCredentials createBasicCredentials(final String user, final char[] password) { + return new BasicContentRemoteCredentials(user, password); + } + + @Override + public RemoteCredentials createImpersonationCredentials(Set principals) { + throw new UnsupportedOperationException("not implemented"); + } + + @Override + public RemoteSession login(RemoteCredentials remoteCredentials) { + ContentRemoteCredentials contentRemoteCredentials = null; + + if (remoteCredentials instanceof ContentRemoteCredentials) { + contentRemoteCredentials = (ContentRemoteCredentials) remoteCredentials; + } + + if (contentRemoteCredentials == null) { + throw new IllegalArgumentException("invalid credentials"); + } + + final ContentSession contentSession = contentRemoteCredentials.login(contentRepository); + + if (contentSession == null) { + return null; + } + + // TODO - remove this subclass of ContentRemoteSession as soon as the + // Oak API exposes a way to handle revisions/snapshots. + + return new ContentRemoteSession(contentSession) { + + @Override + public RemoteRevision readLastRevision() { + final Root root = contentSession.getLatestRoot(); + + final String revisionId = contentRemoteRevisions.put(contentSession.getAuthInfo(), root); + + return new ContentRemoteRevision() { + + @Override + public Root readRoot(ContentSession session) { + return root; + } + + @Override + public String asString() { + return revisionId; + } + + }; + } + + @Override + public RemoteRevision readRevision(final String revisionId) { + final Root root = contentRemoteRevisions.get(contentSession.getAuthInfo(), revisionId); + + if (root == null) { + return null; + } + + return new ContentRemoteRevision() { + + @Override + public Root readRoot(ContentSession session) { + return root; + } + + @Override + public String asString() { + return revisionId; + } + + }; + } + + @Override + public RemoteRevision commit(RemoteRevision revision, RemoteOperation operation) { + ContentRemoteRevision contentRemoteRevision = null; + + if (revision instanceof ContentRemoteRevision) { + contentRemoteRevision = (ContentRemoteRevision) revision; + } + + if (contentRemoteRevision == null) { + throw new IllegalArgumentException("invalid revision"); + } + + final Root root = contentRemoteRevision.readRoot(contentSession); + + super.commit(revision, operation); + + final String revisionId = contentRemoteRevisions.put(contentSession.getAuthInfo(), root); + + return new ContentRemoteRevision() { + + @Override + public Root readRoot(ContentSession session) { + return root; + } + + @Override + public String asString() { + return revisionId; + } + + }; + } + + }; + } + +} \ No newline at end of file diff --git oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/content/ContentRemoteRevision.java oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/content/ContentRemoteRevision.java new file mode 100644 index 0000000..42683ef --- /dev/null +++ oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/content/ContentRemoteRevision.java @@ -0,0 +1,28 @@ +/* + * 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.ContentSession; +import org.apache.jackrabbit.oak.api.Root; +import org.apache.jackrabbit.oak.remote.RemoteRevision; + +interface ContentRemoteRevision extends RemoteRevision { + + Root readRoot(ContentSession session); + +} diff --git oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/content/ContentRemoteRevisions.java oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/content/ContentRemoteRevisions.java new file mode 100644 index 0000000..7a7b612 --- /dev/null +++ oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/content/ContentRemoteRevisions.java @@ -0,0 +1,106 @@ +/* + * 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.collect.Maps; +import org.apache.jackrabbit.oak.api.AuthInfo; +import org.apache.jackrabbit.oak.api.Root; + +import java.security.Principal; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.UUID; + +public class ContentRemoteRevisions { + + private class Key { + + private final String revisionId; + + private final Set principals; + + private final String user; + + private Key(AuthInfo authInfo, String revisionId) { + this.user = authInfo.getUserID(); + this.principals = authInfo.getPrincipals(); + this.revisionId = revisionId; + } + + @Override + public boolean equals(Object object) { + if (object == null) { + return false; + } + + if (object == this) { + return true; + } + + if (getClass() != object.getClass()) { + return false; + } + + Key other = (Key) object; + + if (!Objects.equals(revisionId, other.revisionId)) { + return false; + } + + if (!Objects.equals(user, other.user)) { + return false; + } + + if (!Objects.equals(principals, other.principals)) { + return false; + } + + return true; + } + + @Override + public int hashCode() { + return Objects.hash(revisionId, user, principals); + } + + } + + private Map roots; + + public ContentRemoteRevisions() { + this.roots = Maps.newHashMap(); + } + + public Key key(AuthInfo authInfo, String revisionId) { + return new Key(authInfo, revisionId); + } + + public Root get(AuthInfo authInfo, String revisionId) { + return roots.get(key(authInfo, revisionId)); + } + + public String put(AuthInfo authInfo, Root root) { + String revisionId = UUID.randomUUID().toString(); + + roots.put(key(authInfo, revisionId), root); + + return revisionId; + } + +} diff --git oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/content/ContentRemoteSession.java oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/content/ContentRemoteSession.java new file mode 100644 index 0000000..22064dd --- /dev/null +++ oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/content/ContentRemoteSession.java @@ -0,0 +1,358 @@ +/* + * 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.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.RemoteOperation; +import org.apache.jackrabbit.oak.remote.RemoteRevision; +import org.apache.jackrabbit.oak.remote.RemoteSession; +import org.apache.jackrabbit.oak.remote.RemoteTree; +import org.apache.jackrabbit.oak.remote.RemoteTreeFilters; +import org.apache.jackrabbit.oak.remote.RemoteValue; + +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +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; + + public ContentRemoteSession(ContentSession contentSession) { + this.contentSession = contentSession; + } + + @Override + public RemoteRevision readLastRevision() { + throw new UnsupportedOperationException("not implemented"); + } + + @Override + public RemoteRevision readRevision(String revision) { + throw new UnsupportedOperationException("not implemented"); + } + + @Override + public RemoteTree 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.readRoot(contentSession); + + if (root == null) { + return null; + } + + Tree tree = root.getTree(path); + + if (tree.exists()) { + return new ContentRemoteTree(tree, 0, filters); + } + + return null; + } + + @Override + public RemoteOperation createAddOperation(String path, Map 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 operations = new ArrayList(); + + operations.add(new AddContentRemoteOperation(path)); + + for (Map.Entry entry : properties.entrySet()) { + operations.add(createSetOperation(path, entry.getKey(), entry.getValue())); + } + + return new AggregateContentRemoteOperation(operations); + } + + @Override + public RemoteOperation 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 RemoteOperation 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(path, name, value); + } + + @Override + public RemoteOperation 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 RemoteOperation 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 RemoteOperation 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 RemoteOperation createAggregateOperation(final List operations) { + if (operations == null) { + throw new IllegalArgumentException("operations not provided"); + } + + return new AggregateContentRemoteOperation(operations); + } + + @Override + public RemoteRevision commit(RemoteRevision revision, RemoteOperation operation) { + 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.readRoot(contentSession); + + try { + contentRemoteOperation.apply(root); + } catch (Exception e) { + return null; + } + + try { + root.commit(); + } catch (CommitFailedException e) { + return null; + } + + return null; + } + + @Override + public RemoteBinaryId readBinaryId(String binaryId) { + if (binaryId == null) { + throw new IllegalArgumentException("binary id not provided"); + } + + if (binaryId.isEmpty()) { + throw new IllegalArgumentException("invalid binary id"); + } + + Blob blob = contentSession.getLatestRoot().getBlob(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 RemoteBinaryId writeBinary(InputStream stream) { + if (stream == null) { + throw new IllegalArgumentException("stream not provided"); + } + + Blob blob; + + try { + blob = contentSession.getLatestRoot().createBlob(stream); + } catch (IOException e) { + blob = null; + } + + if (blob == null) { + return null; + } + + return new ContentRemoteBinaryId(blob.getReference(), blob); + } + +} diff --git oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/content/ContentRemoteTree.java oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/content/ContentRemoteTree.java new file mode 100644 index 0000000..3c13415 --- /dev/null +++ oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/content/ContentRemoteTree.java @@ -0,0 +1,332 @@ +/* + * 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; + + public ContentRemoteTree(Tree tree, int depth, RemoteTreeFilters filters) { + this.tree = tree; + this.depth = depth; + this.filters = filters; + } + + @Override + public Map getProperties() { + Map properties = new HashMap(); + + for (PropertyState property : getFilteredProperties()) { + properties.put(property.getName(), getRemoteValue(property)); + } + + return properties; + } + + private Iterable getFilteredProperties() { + return Iterables.filter(tree.getProperties(), getPropertyFilters()); + } + + private Predicate getPropertyFilters() { + return new Predicate() { + + @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 getDates(Iterable dates) { + return Iterables.transform(dates, new Function() { + + @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 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 blobs) { + long length = 0; + + for (Blob blob : blobs) { + length = length + blob.length(); + } + + return length; + } + + private Supplier getBinary(final Blob blob) { + return new Supplier() { + + @Override + public InputStream get() { + return blob.getNewStream(); + } + + }; + } + + private Iterable> getBinaries(Iterable blobs) { + return Iterables.transform(blobs, new Function>() { + + @Override + public Supplier apply(Blob blob) { + return getBinary(blob); + } + + }); + } + + private String getBinaryId(Blob blob) { + return blob.getReference(); + } + + private Iterable getBinaryIds(Iterable blobs) { + return Iterables.transform(blobs, new Function() { + + @Override + public String apply(Blob blob) { + return getBinaryId(blob); + } + + }); + } + + @Override + public Map getChildren() { + Map children = new HashMap(); + + for (Tree child : getFilteredChildren()) { + if (depth < filters.getDepth()) { + children.put(child.getName(), new ContentRemoteTree(child, depth + 1, filters)); + } else { + children.put(child.getName(), null); + } + } + + return children; + } + + private Iterable getFilteredChildren() { + Iterable 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 getNodeFilters() { + return new Predicate() { + + @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; + } + +} diff --git oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/content/CopyContentRemoteOperation.java oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/content/CopyContentRemoteOperation.java new file mode 100644 index 0000000..c8c7683 --- /dev/null +++ oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/content/CopyContentRemoteOperation.java @@ -0,0 +1,70 @@ +/* + * 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; + +class CopyContentRemoteOperation implements ContentRemoteOperation { + + 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) { + Tree sourceTree = root.getTree(source); + + if (!sourceTree.exists()) { + throw new IllegalStateException("source tree does not exist"); + } + + Tree targetTree = root.getTree(target); + + if (targetTree.exists()) { + throw new IllegalStateException("target tree already exists"); + } + + Tree targetParentTree = targetTree.getParent(); + + if (!targetParentTree.exists()) { + throw new IllegalStateException("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()); + } + } + +} diff --git oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/content/MoveContentRemoteOperation.java oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/content/MoveContentRemoteOperation.java new file mode 100644 index 0000000..fac5de2 --- /dev/null +++ oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/content/MoveContentRemoteOperation.java @@ -0,0 +1,44 @@ +/* + * 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; + +class MoveContentRemoteOperation implements ContentRemoteOperation { + + 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) { + boolean success = root.move(source, target); + + if (success) { + return; + } + + throw new IllegalArgumentException("unable to move the tree"); + } + +} diff --git oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/content/RemoveContentRemoteOperation.java oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/content/RemoveContentRemoteOperation.java new file mode 100644 index 0000000..b6d1b28 --- /dev/null +++ oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/content/RemoveContentRemoteOperation.java @@ -0,0 +1,44 @@ +/* + * 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; + +class RemoveContentRemoteOperation implements ContentRemoteOperation { + + private final String path; + + public RemoveContentRemoteOperation(String path) { + this.path = path; + } + + @Override + public void apply(Root root) { + Tree tree = root.getTree(path); + + if (!tree.exists()) { + throw new IllegalStateException("tree does not exists"); + } + + if (!tree.remove()) { + throw new IllegalStateException("unable to remove the tree"); + } + } + +} diff --git oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/content/SetContentRemoteOperation.java oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/content/SetContentRemoteOperation.java new file mode 100644 index 0000000..06a92ce --- /dev/null +++ oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/content/SetContentRemoteOperation.java @@ -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.content; + +import org.apache.jackrabbit.oak.api.Root; +import org.apache.jackrabbit.oak.api.Tree; +import org.apache.jackrabbit.oak.remote.RemoteValue; + +class SetContentRemoteOperation implements ContentRemoteOperation { + + private final String path; + + private final String name; + + private final RemoteValue value; + + public SetContentRemoteOperation(String path, String name, RemoteValue value) { + this.path = path; + this.name = name; + this.value = value; + } + + @Override + public void apply(Root root) { + Tree tree = root.getTree(path); + + if (!tree.exists()) { + throw new IllegalStateException("tree does not exist"); + } + + value.whenType(new SetPropertyHandler(root, tree, name)); + } + +} diff --git oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/content/SetPropertyHandler.java oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/content/SetPropertyHandler.java new file mode 100644 index 0000000..fda588e --- /dev/null +++ oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/content/SetPropertyHandler.java @@ -0,0 +1,239 @@ +/* + * 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 Root root; + + private final Tree tree; + + private final String name; + + public SetPropertyHandler(Root root, Tree tree, String name) { + this.root = root; + this.tree = tree; + this.name = name; + } + + @Override + public void isBinary(Supplier value) { + tree.setProperty(name, getBlob(root, value), Type.BINARY); + } + + @Override + public void isMultiBinary(Iterable> value) { + tree.setProperty(name, getBlobs(root, value), Type.BINARIES); + } + + @Override + public void isBinaryId(String value) { + tree.setProperty(name, getBlobFromId(root, value), Type.BINARY); + } + + @Override + public void isMultiBinaryId(Iterable value) { + tree.setProperty(name, getBlobsFromIds(root, value), Type.BINARIES); + } + + @Override + public void isBoolean(Boolean value) { + tree.setProperty(name, value, Type.BOOLEAN); + } + + @Override + public void isMultiBoolean(Iterable 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 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 value) { + tree.setProperty(name, value, Type.DECIMALS); + } + + @Override + public void isDouble(Double value) { + tree.setProperty(name, value, Type.DOUBLE); + } + + @Override + public void isMultiDouble(Iterable value) { + tree.setProperty(name, value, Type.DOUBLES); + } + + @Override + public void isLong(Long value) { + tree.setProperty(name, value, Type.LONG); + } + + @Override + public void isMultiLong(Iterable value) { + tree.setProperty(name, value, Type.LONGS); + } + + @Override + public void isName(String value) { + tree.setProperty(name, value, Type.NAME); + } + + @Override + public void isMultiName(Iterable value) { + tree.setProperty(name, value, Type.NAMES); + } + + @Override + public void isPath(String value) { + tree.setProperty(name, value, Type.PATH); + } + + @Override + public void isMultiPath(Iterable value) { + tree.setProperty(name, value, Type.PATHS); + } + + @Override + public void isReference(String value) { + tree.setProperty(name, value, Type.REFERENCE); + } + + @Override + public void isMultiReference(Iterable value) { + tree.setProperty(name, value, Type.REFERENCES); + } + + @Override + public void isText(String value) { + tree.setProperty(name, value, Type.STRING); + } + + @Override + public void isMultiText(Iterable value) { + tree.setProperty(name, value, Type.STRINGS); + } + + @Override + public void isUri(String value) { + tree.setProperty(name, value, Type.URI); + } + + @Override + public void isMultiUri(Iterable value) { + tree.setProperty(name, value, Type.URIS); + } + + @Override + public void isWeakReference(String value) { + tree.setProperty(name, value, Type.WEAKREFERENCE); + } + + @Override + public void isMultiWeakReference(Iterable value) { + tree.setProperty(name, value, Type.WEAKREFERENCES); + } + + private Blob getBlob(Root root, Supplier 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 getBlobs(final Root root, Iterable> suppliers) { + return Iterables.transform(suppliers, new Function, Blob>() { + + @Override + public Blob apply(Supplier supplier) { + return getBlob(root, supplier); + } + + }); + } + + private Blob getBlobFromId(Root root, String binaryId) { + return root.getBlob(binaryId); + } + + private Iterable getBlobsFromIds(final Root root, Iterable binaryIds) { + return Iterables.transform(binaryIds, new Function() { + + @Override + public Blob apply(String binaryId) { + return getBlobFromId(root, binaryId); + } + + }); + } + + private String getDate(Long time) { + Calendar calendar = Calendar.getInstance(); + calendar.setTimeInMillis(time); + return ISO8601.format(calendar); + } + + private Iterable getDates(Iterable times) { + return Iterables.transform(times, new Function() { + + @Override + public String apply(Long time) { + return getDate(time); + } + + }); + } + +} diff --git oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/content/UnsetContentRemoteOperation.java oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/content/UnsetContentRemoteOperation.java new file mode 100644 index 0000000..20ef5f0 --- /dev/null +++ oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/content/UnsetContentRemoteOperation.java @@ -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.content; + +import org.apache.jackrabbit.oak.api.Root; +import org.apache.jackrabbit.oak.api.Tree; + +class UnsetContentRemoteOperation implements ContentRemoteOperation { + + 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) { + Tree tree = root.getTree(path); + + if (!tree.exists()) { + throw new IllegalStateException("tree does not exists"); + } + + if (!tree.hasProperty(name)) { + throw new IllegalStateException("property does not exist"); + } + + tree.removeProperty(name); + } + +} diff --git oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/filter/Filter.java oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/filter/Filter.java new file mode 100644 index 0000000..1b37681 --- /dev/null +++ oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/filter/Filter.java @@ -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 diff --git oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/filter/Filters.java oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/filter/Filters.java new file mode 100644 index 0000000..baf77a5 --- /dev/null +++ oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/filter/Filters.java @@ -0,0 +1,77 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jackrabbit.oak.remote.filter; + +import java.util.HashSet; +import java.util.Set; + +public class Filters { + + private Set includes = new HashSet(); + + private Set excludes = new HashSet(); + + public Filters(Set 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 diff --git oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/http/RemoteHandler.java oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/http/RemoteHandler.java new file mode 100644 index 0000000..63071a7 --- /dev/null +++ oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/http/RemoteHandler.java @@ -0,0 +1,47 @@ +/* + * 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.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +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 Exception { + handler.handle(request, response); + } + + @Override + public boolean match(HttpServletRequest request) { + return matcher.match(request); + } + +} diff --git oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/http/RemoteServer.java oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/http/RemoteServer.java new file mode 100644 index 0000000..901ca07 --- /dev/null +++ oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/http/RemoteServer.java @@ -0,0 +1,64 @@ +/* + * 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.eclipse.jetty.server.Handler; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.servlet.ServletHandler; +import org.eclipse.jetty.servlet.ServletHolder; + +import java.net.InetSocketAddress; + +public class RemoteServer { + + private final Server server; + + public RemoteServer(RemoteRepository repository, String host, int port) { + this.server = createServer(repository, new InetSocketAddress(host, port)); + } + + private Server createServer(RemoteRepository repository, InetSocketAddress address) { + Server server = new Server(address); + + server.setHandler(createHandler(repository)); + + return server; + } + + private Handler createHandler(RemoteRepository repository) { + ServletHandler handler = new ServletHandler(); + + handler.addServletWithMapping(new ServletHolder(new RemoteServlet(repository)), "/*"); + + return handler; + } + + public void start() throws Exception { + server.start(); + } + + public void stop() throws Exception { + server.stop(); + } + + public void join() throws Exception { + server.join(); + } + +} \ No newline at end of file diff --git oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/http/RemoteServlet.java oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/http/RemoteServlet.java new file mode 100644 index 0000000..0ac9e71 --- /dev/null +++ oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/http/RemoteServlet.java @@ -0,0 +1,85 @@ +/* + * 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 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.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.createNotFoundHandler; +import static org.apache.jackrabbit.oak.remote.http.matcher.Matchers.matchesRequest; + +public class RemoteServlet extends HttpServlet { + + 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) { + throw e; + } catch (IOException e) { + throw e; + } catch (Exception e) { + throw new ServletException(e); + } + } + + private Handler firstMatching(Iterable handlers, HttpServletRequest request, Handler otherwise) { + for (RemoteHandler handler : handlers) { + if (handler.match(request)) { + return handler; + } + } + + return otherwise; + } + + private Iterable readHandlers() { + return handlers( + handler("get", "/revisions/last", createGetLastRevisionHandler()), + handler("get", "/revisions/last/tree/.*", createGetLastTreeHandler()), + handler("get", "/revisions/[^/]+/tree/.*", createGetRevisionTreeHandler()) + ); + } + + private Iterable handlers(RemoteHandler... handlers) { + return Arrays.asList(handlers); + } + + private RemoteHandler handler(String method, String path, Handler handler) { + return new RemoteHandler(matchesRequest(method, path), handler); + } + +} diff --git oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/http/handler/AuthenticationWrapperHandler.java oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/http/handler/AuthenticationWrapperHandler.java new file mode 100644 index 0000000..cb829e0 --- /dev/null +++ oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/http/handler/AuthenticationWrapperHandler.java @@ -0,0 +1,161 @@ +/* + * 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.RemoteRepository; +import org.apache.jackrabbit.oak.remote.RemoteSession; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.util.Base64; + +class AuthenticationWrapperHandler implements Handler { + + 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 Exception { + RemoteSession session = (RemoteSession) request.getAttribute("session"); + + if (session == null) { + session = login(request); + } + + if (session != null) { + request.setAttribute("session", session); + } + + if (session != null) { + authenticated.handle(request, response); + } else { + notAuthenticated.handle(request, response); + } + } + + private RemoteSession login(HttpServletRequest request) { + RemoteRepository repository = (RemoteRepository) request.getAttribute("repository"); + + if (repository == null) { + return null; + } + + 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 = new String(Base64.getDecoder().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.login(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; + } + +} diff --git oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/http/handler/ForbiddenHandler.java oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/http/handler/ForbiddenHandler.java new file mode 100644 index 0000000..99ce358 --- /dev/null +++ oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/http/handler/ForbiddenHandler.java @@ -0,0 +1,30 @@ +/* + * 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 javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +class ForbiddenHandler implements Handler { + + @Override + public void handle(HttpServletRequest request, HttpServletResponse response) throws Exception { + response.setStatus(HttpServletResponse.SC_FORBIDDEN); + } + +} diff --git oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/http/handler/GetLastRevisionHandler.java oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/http/handler/GetLastRevisionHandler.java new file mode 100644 index 0000000..671ce80 --- /dev/null +++ oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/http/handler/GetLastRevisionHandler.java @@ -0,0 +1,57 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.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; + +class GetLastRevisionHandler implements Handler { + + @Override + public void handle(HttpServletRequest request, HttpServletResponse response) throws Exception { + RemoteSession session = (RemoteSession) request.getAttribute("session"); + + if (session == null) { + response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); + 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(); + } + +} diff --git oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/http/handler/GetLastTreeHandler.java oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/http/handler/GetLastTreeHandler.java new file mode 100644 index 0000000..e80b282 --- /dev/null +++ oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/http/handler/GetLastTreeHandler.java @@ -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 mapped to the wrong path"); + } + + @Override + protected RemoteRevision readRevision(HttpServletRequest request, RemoteSession session) { + return session.readLastRevision(); + } + +} diff --git oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/http/handler/GetRevisionTreeHandler.java oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/http/handler/GetRevisionTreeHandler.java new file mode 100644 index 0000000..2474398 --- /dev/null +++ oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/http/handler/GetRevisionTreeHandler.java @@ -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 mapped to 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 mapped to the wrong path"); + } + +} diff --git oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/http/handler/GetTreeHandler.java oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/http/handler/GetTreeHandler.java new file mode 100644 index 0000000..cda60ab --- /dev/null +++ oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/http/handler/GetTreeHandler.java @@ -0,0 +1,465 @@ +/* + * 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 com.google.common.collect.Sets; +import com.google.common.io.BaseEncoding; +import com.google.common.io.ByteStreams; +import org.apache.jackrabbit.oak.remote.RemoteRevision; +import org.apache.jackrabbit.oak.remote.RemoteSession; +import org.apache.jackrabbit.oak.remote.RemoteTree; +import org.apache.jackrabbit.oak.remote.RemoteTreeFilters; +import org.apache.jackrabbit.oak.remote.RemoteValue; + +import javax.servlet.ServletOutputStream; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.io.InputStream; +import java.math.BigDecimal; +import java.util.Map; +import java.util.Set; + +abstract class GetTreeHandler implements Handler { + + protected abstract String readPath(HttpServletRequest request); + + protected abstract RemoteRevision readRevision(HttpServletRequest request, RemoteSession session); + + @Override + public void handle(HttpServletRequest request, HttpServletResponse response) throws Exception { + RemoteSession session = (RemoteSession) request.getAttribute("session"); + + if (session == null) { + response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); + return; + } + + RemoteRevision revision = readRevision(request, session); + + if (revision == null) { + response.setStatus(HttpServletResponse.SC_GONE); + return; + } + + RemoteTree tree = session.readTree(revision, readPath(request), readFilters(request)); + + response.setStatus(HttpServletResponse.SC_OK); + response.setContentType("application/json"); + + ServletOutputStream stream = response.getOutputStream(); + + JsonGenerator generator = new JsonFactory().createJsonGenerator(stream, JsonEncoding.UTF8); + renderResponse(generator, revision, tree); + generator.flush(); + + stream.close(); + } + + private void renderResponse(JsonGenerator generator, RemoteRevision revision, RemoteTree tree) throws IOException { + generator.writeStartObject(); + generator.writeFieldName("revision"); + generator.writeString(revision.asString()); + generator.writeFieldName("tree"); + renderTree(generator, tree); + generator.writeEndObject(); + } + + private void renderTree(JsonGenerator generator, RemoteTree tree) throws IOException { + if (tree == null) { + generator.writeNull(); + } else { + generator.writeStartObject(); + generator.writeFieldName("properties"); + renderProperties(generator, tree.getProperties()); + generator.writeFieldName("children"); + renderChildren(generator, tree.getChildren()); + generator.writeFieldName("hasMoreChildren"); + generator.writeBoolean(tree.hasMoreChildren()); + generator.writeEndObject(); + } + } + + private void renderChildren(JsonGenerator generator, Map children) throws IOException { + generator.writeStartObject(); + + for (Map.Entry entry : children.entrySet()) { + generator.writeFieldName(entry.getKey()); + renderTree(generator, entry.getValue()); + } + + generator.writeEndObject(); + } + + private void renderProperties(JsonGenerator generator, Map properties) throws IOException { + generator.writeStartObject(); + + for (Map.Entry entry : properties.entrySet()) { + generator.writeFieldName(entry.getKey()); + renderProperty(generator, entry.getValue()); + } + + generator.writeEndObject(); + } + + private void renderProperty(final JsonGenerator generator, RemoteValue value) throws IOException { + if (value.isBinary()) { + renderValue(generator, "binary", value.asBinary(), getBinaryWriter()); + } + + if (value.isMultiBinary()) { + renderMultiValue(generator, "binaries", value.asMultiBinary(), getBinaryWriter()); + } + + if (value.isBinaryId()) { + renderValue(generator, "binaryId", value.asBinaryId(), getStringWriter()); + } + + if (value.isMultiBinaryId()) { + renderMultiValue(generator, "binaryIds", value.asMultiBinaryId(), getStringWriter()); + } + + if (value.isBoolean()) { + renderValue(generator, "boolean", value.asBoolean(), getBooleanWriter()); + } + + if (value.isMultiBoolean()) { + renderMultiValue(generator, "booleans", value.asMultiBoolean(), getBooleanWriter()); + } + + if (value.isDate()) { + renderValue(generator, "date", value.asDate(), getLongWriter()); + } + + if (value.isMultiDate()) { + renderMultiValue(generator, "dates", value.asMultiDate(), getLongWriter()); + } + + if (value.isDecimal()) { + renderValue(generator, "decimal", value.asDecimal(), getDecimalWriter()); + } + + if (value.isMultiDecimal()) { + renderMultiValue(generator, "decimals", value.asMultiDecimal(), getDecimalWriter()); + } + + if (value.isDouble()) { + renderValue(generator, "double", value.asDouble(), getDoubleWriter()); + } + + if (value.isMultiDouble()) { + renderMultiValue(generator, "doubles", value.asMultiDouble(), getDoubleWriter()); + } + + if (value.isLong()) { + renderValue(generator, "long", value.asLong(), getLongWriter()); + } + + if (value.isMultiLong()) { + renderMultiValue(generator, "longs", value.asMultiLong(), getLongWriter()); + } + + if (value.isName()) { + renderValue(generator, "name", value.asName(), getStringWriter()); + } + + if (value.isMultiName()) { + renderMultiValue(generator, "names", value.asMultiName(), getStringWriter()); + } + + if (value.isPath()) { + renderValue(generator, "path", value.asPath(), getStringWriter()); + } + + if (value.isMultiPath()) { + renderMultiValue(generator, "paths", value.asMultiPath(), getStringWriter()); + } + + if (value.isReference()) { + renderValue(generator, "reference", value.asReference(), getStringWriter()); + } + + if (value.isMultiReference()) { + renderMultiValue(generator, "references", value.asMultiReference(), getStringWriter()); + } + + if (value.isText()) { + renderValue(generator, "string", value.asText(), getStringWriter()); + } + + if (value.isMultiText()) { + renderMultiValue(generator, "strings", value.asMultiText(), getStringWriter()); + } + + if (value.isUri()) { + renderValue(generator, "uri", value.asUri(), getStringWriter()); + } + + if (value.isMultiUri()) { + renderMultiValue(generator, "uris", value.asMultiUri(), getStringWriter()); + } + + if (value.isWeakReference()) { + renderValue(generator, "weakReference", value.asWeakReference(), getStringWriter()); + } + + if (value.isMultiWeakReference()) + renderMultiValue(generator, "weakReferences", value.asMultiWeakReference(), getStringWriter()); + } + + private interface GeneratorWriter { + + void write(JsonGenerator generator, T value) throws IOException; + + } + + private GeneratorWriter> getBinaryWriter() { + return new GeneratorWriter>() { + + @Override + public void write(JsonGenerator generator, RemoteValue.Supplier value) throws IOException { + generator.writeString(BaseEncoding.base64().encode(ByteStreams.toByteArray(value.get()))); + } + + }; + } + + private GeneratorWriter getStringWriter() { + return new GeneratorWriter() { + + @Override + public void write(JsonGenerator generator, String value) throws IOException { + generator.writeString(value); + } + + }; + } + + private GeneratorWriter getBooleanWriter() { + return new GeneratorWriter() { + + @Override + public void write(JsonGenerator generator, Boolean value) throws IOException { + generator.writeBoolean(value); + } + + }; + } + + private GeneratorWriter getLongWriter() { + return new GeneratorWriter() { + + @Override + public void write(JsonGenerator generator, Long value) throws IOException { + generator.writeNumber(value); + } + + }; + } + + private GeneratorWriter getDecimalWriter() { + return new GeneratorWriter() { + + @Override + public void write(JsonGenerator generator, BigDecimal value) throws IOException { + generator.writeString(value.toString()); + } + + }; + } + + private GeneratorWriter getDoubleWriter() { + return new GeneratorWriter() { + + @Override + public void write(JsonGenerator generator, Double value) throws IOException { + generator.writeNumber(value); + } + + }; + } + + private void renderValue(JsonGenerator generator, String type, T value, GeneratorWriter writer) throws IOException { + generator.writeStartObject(); + generator.writeStringField("type", type); + generator.writeFieldName("value"); + + writer.write(generator, value); + + generator.writeEndObject(); + } + + private void renderMultiValue(JsonGenerator generator, String type, Iterable values, GeneratorWriter writer) throws IOException { + generator.writeStartObject(); + generator.writeStringField("type", type); + generator.writeArrayFieldStart("value"); + + for (T value : values) { + writer.write(generator, value); + } + + generator.writeEndArray(); + generator.writeEndObject(); + } + + private RemoteTreeFilters readFilters(final HttpServletRequest request) { + return new RemoteTreeFilters() { + + @Override + public int getDepth() { + Integer depth = readDepth(request); + + if (depth == null) { + return super.getDepth(); + } + + return depth; + } + + @Override + public Set getPropertyFilters() { + Set propertyFilters = readPropertyFilters(request); + + if (propertyFilters == null) { + return super.getPropertyFilters(); + } + + return propertyFilters; + } + + @Override + public Set getNodeFilters() { + Set nodeFilters = readNodeFilters(request); + + if (nodeFilters == null) { + return super.getNodeFilters(); + } + + return nodeFilters; + } + + @Override + public long getBinaryThreshold() { + Long binaryThreshold = readBinaryThreshold(request); + + if (binaryThreshold == null) { + return super.getBinaryThreshold(); + } + + return binaryThreshold; + } + + @Override + public int getChildrenStart() { + Integer childrenStart = readChildrenStart(request); + + if (childrenStart == null) { + return super.getChildrenStart(); + } + + return childrenStart; + } + + @Override + public int getChildrenCount() { + Integer childrenCount = readChildrenCount(request); + + if (childrenCount == null) { + return super.getChildrenCount(); + } + + return childrenCount; + } + + }; + } + + private Integer readDepth(HttpServletRequest request) { + return readIntegerParameter(request, "depth"); + } + + private Set readPropertyFilters(HttpServletRequest request) { + return readSetParameter(request, "properties"); + } + + private Set readNodeFilters(HttpServletRequest request) { + return readSetParameter(request, "children"); + } + + private Long readBinaryThreshold(HttpServletRequest request) { + return readLongParameter(request, "binaries"); + } + + private Integer readChildrenStart(HttpServletRequest request) { + return readIntegerParameter(request, "childrenStart"); + } + + private Integer readChildrenCount(HttpServletRequest request) { + return readIntegerParameter(request, "childrenCount"); + } + + private Integer readIntegerParameter(HttpServletRequest request, String name) { + String value = request.getParameter(name); + + if (value == null) { + return null; + } + + Integer result; + + try { + result = Integer.parseInt(value, 10); + } catch (NumberFormatException e) { + result = null; + } + + return result; + } + + private Long readLongParameter(HttpServletRequest request, String name) { + String value = request.getParameter(name); + + if (value == null) { + return null; + } + + Long result; + + try { + result = Long.parseLong(value, 10); + } catch (NumberFormatException e) { + result = null; + } + + return result; + } + + private Set readSetParameter(HttpServletRequest request, String name) { + String[] values = request.getParameterValues(name); + + if (values == null) { + return null; + } + + return Sets.newHashSet(values); + } + +} diff --git oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/http/handler/Handler.java oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/http/handler/Handler.java new file mode 100644 index 0000000..4eec8d7 --- /dev/null +++ oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/http/handler/Handler.java @@ -0,0 +1,27 @@ +/* + * 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 javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +public interface Handler { + + void handle(HttpServletRequest request, HttpServletResponse response) throws Exception; + +} diff --git oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/http/handler/Handlers.java oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/http/handler/Handlers.java new file mode 100644 index 0000000..481b532 --- /dev/null +++ oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/http/handler/Handlers.java @@ -0,0 +1,76 @@ +/* + * 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; + +/** + * A collection of handlers used to respond to some requests handled by the + * remote servlet. + */ +public class Handlers { + + private Handlers() { + } + + /** + * Create an handler that will return the last revision available to the + * server. + * + * @return An instance of {@code Handler}. + */ + public static Handler createGetLastRevisionHandler() { + return withAuthentication(new GetLastRevisionHandler()); + } + + /** + * Create an handler that will return a repository sub-tree at a specific + * revision. + * + * @return An instance of {@code Handler}. + */ + public static Handler createGetRevisionTreeHandler() { + return withAuthentication(new GetRevisionTreeHandler()); + } + + /** + * Create an handler that will return a repository sub-tree at the latest + * known state. + * + * @return An instance of {@code Handler}. + */ + public static Handler createGetLastTreeHandler() { + return withAuthentication(new GetLastTreeHandler()); + } + + /** + * Create an handler that will return a 404 response to the client. + * + * @return An instance of {@code Handler}. + */ + public static Handler createNotFoundHandler() { + return new NotFoundHandler(); + } + + private static Handler withAuthentication(Handler authenticated) { + return new AuthenticationWrapperHandler(authenticated, createForbiddenHandler()); + } + + private static Handler createForbiddenHandler() { + return new ForbiddenHandler(); + } + +} diff --git oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/http/handler/NotFoundHandler.java oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/http/handler/NotFoundHandler.java new file mode 100644 index 0000000..538d65e --- /dev/null +++ oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/http/handler/NotFoundHandler.java @@ -0,0 +1,30 @@ +/* + * 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 javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +class NotFoundHandler implements Handler { + + @Override + public void handle(HttpServletRequest request, HttpServletResponse response) throws Exception { + response.setStatus(HttpServletResponse.SC_NOT_FOUND); + } + +} diff --git oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/http/matcher/AllMatcher.java oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/http/matcher/AllMatcher.java new file mode 100644 index 0000000..aebb192 --- /dev/null +++ oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/http/matcher/AllMatcher.java @@ -0,0 +1,41 @@ +/* + * 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.matcher; + +import javax.servlet.http.HttpServletRequest; + +class AllMatcher implements Matcher { + + private final Matcher[] matchers; + + public AllMatcher(Matcher... matchers) { + this.matchers = matchers; + } + + @Override + public boolean match(HttpServletRequest request) { + for (Matcher matcher : matchers) { + if (!matcher.match(request)) { + return false; + } + } + + return true; + } + +} diff --git oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/http/matcher/Matcher.java oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/http/matcher/Matcher.java new file mode 100644 index 0000000..a551833 --- /dev/null +++ oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/http/matcher/Matcher.java @@ -0,0 +1,37 @@ +/* + * 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.matcher; + +import javax.servlet.http.HttpServletRequest; + +/** + * A predicate over an HTTP request. This predicate can be used to check if some + * preconditions on the request are met. + */ +public interface Matcher { + + /** + * Check if the preconditions on the given request are met. + * + * @param request Request to check. + * @return {@code true} if the preconditions are met, {@code false} + * otherwise. + */ + boolean match(HttpServletRequest request); + +} diff --git oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/http/matcher/Matchers.java oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/http/matcher/Matchers.java new file mode 100644 index 0000000..c30fd6c --- /dev/null +++ oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/http/matcher/Matchers.java @@ -0,0 +1,102 @@ +/* + * 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.matcher; + +import java.util.regex.Pattern; + +/** + * Collection of matchers for HTTP requests. + */ +public class Matchers { + + private Matchers() { + } + + /** + * Create a matcher that will be satisfied when given requests have a method + * matching the one provided as a parameter. + * + * @param method Method that requests must have for the matcher to be + * satisfied. + * @return An instance of {@code Matcher}. + */ + public static Matcher matchesMethod(String method) { + if (method == null) { + throw new IllegalArgumentException("method not provided"); + } + + return new MethodMatcher(method); + } + + /** + * Create a matcher that will be satisfied when given requests have a patch + * matching the pattern provided as a parameter. + * + * @param pattern The pattern to use when checking the requests given to the + * matcher. + * @return An instance of {@code Matcher}. + */ + public static Matcher matchesPath(String pattern) { + if (pattern == null) { + throw new IllegalArgumentException("pattern not provided"); + } + + return new PathMatcher(Pattern.compile(pattern)); + } + + /** + * Create a matcher that will be satisfied when the given requests satisfies + * every matcher provided as parameters. Calling this method is equivalent + * as checking every provided matcher individually and chaining each result + * as a short-circuit and. + * + * @param matchers The matchers that have to be satisfied for the returned + * matcher to be satisfied. + * @return An instance of {@code Matcher}. + */ + public static Matcher matchesAll(Matcher... matchers) { + if (matchers == null) { + if (matchers == null) { + throw new IllegalArgumentException("matchers not provided"); + } + } + + for (Matcher matcher : matchers) { + if (matcher == null) { + throw new IllegalArgumentException("invalid matcher"); + } + } + + return new AllMatcher(matchers); + } + + /** + * Create a matcher that will be satisifed when the given requests match the + * provided method and path. + * + * @param method The method that requests must have for the matcher to be + * satisfied. + * @param path The pattern to use when checking the requests given to the + * matcher. + * @return An instance of {@code Matcher}. + */ + public static Matcher matchesRequest(String method, String path) { + return matchesAll(matchesMethod(method), matchesPath(path)); + } + +} diff --git oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/http/matcher/MethodMatcher.java oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/http/matcher/MethodMatcher.java new file mode 100644 index 0000000..6ce44ff --- /dev/null +++ oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/http/matcher/MethodMatcher.java @@ -0,0 +1,35 @@ +/* + * 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.matcher; + +import javax.servlet.http.HttpServletRequest; + +class MethodMatcher implements Matcher { + + private final String method; + + public MethodMatcher(String method) { + this.method = method; + } + + @Override + public boolean match(HttpServletRequest request) { + return request.getMethod().toLowerCase().equals(method.toLowerCase()); + } + +} diff --git oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/http/matcher/PathMatcher.java oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/http/matcher/PathMatcher.java new file mode 100644 index 0000000..d8d04af --- /dev/null +++ oak-remote/src/main/java/org/apache/jackrabbit/oak/remote/http/matcher/PathMatcher.java @@ -0,0 +1,42 @@ +/* + * 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.matcher; + +import javax.servlet.http.HttpServletRequest; +import java.util.regex.Pattern; + +class PathMatcher implements Matcher { + + private final Pattern pattern; + + public PathMatcher(Pattern pattern) { + this.pattern = pattern; + } + + @Override + public boolean match(HttpServletRequest request) { + String requestPath = request.getPathInfo(); + + if (requestPath == null) { + return false; + } + + return pattern.matcher(requestPath).matches(); + } + +} diff --git oak-remote/src/test/java/org/apache/jackrabbit/oak/remote/content/AddContentRemoteOperationTest.java oak-remote/src/test/java/org/apache/jackrabbit/oak/remote/content/AddContentRemoteOperationTest.java new file mode 100644 index 0000000..e0f8053 --- /dev/null +++ oak-remote/src/test/java/org/apache/jackrabbit/oak/remote/content/AddContentRemoteOperationTest.java @@ -0,0 +1,78 @@ +/* + * 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.junit.Test; + +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +public class AddContentRemoteOperationTest { + + public AddContentRemoteOperation createOperation(String path) { + return new AddContentRemoteOperation(path); + } + + @Test + public void testAddNode() { + Tree parent = mock(Tree.class); + doReturn(true).when(parent).exists(); + + Tree tree = mock(Tree.class); + doReturn(false).when(tree).exists(); + doReturn(parent).when(tree).getParent(); + doReturn("test").when(tree).getName(); + + Root root = mock(Root.class); + doReturn(tree).when(root).getTree("/test"); + + createOperation("/test").apply(root); + + verify(parent).addChild("test"); + } + + @Test(expected = IllegalStateException.class) + public void testAddNodeWithExistingTree() { + Tree tree = mock(Tree.class); + doReturn(true).when(tree).exists(); + + Root root = mock(Root.class); + doReturn(tree).when(root).getTree("/test"); + + createOperation("/test").apply(root); + } + + @Test(expected = IllegalStateException.class) + public void testAddNodeWithNonExistingParent() { + Tree parent = mock(Tree.class); + doReturn(false).when(parent).exists(); + + Tree tree = mock(Tree.class); + doReturn(false).when(tree).exists(); + doReturn(parent).when(tree).getParent(); + + Root root = mock(Root.class); + doReturn(tree).when(root).getTree("/test"); + + createOperation("/test").apply(root); + } + +} diff --git oak-remote/src/test/java/org/apache/jackrabbit/oak/remote/content/ContentRemoteRepositoryTest.java oak-remote/src/test/java/org/apache/jackrabbit/oak/remote/content/ContentRemoteRepositoryTest.java new file mode 100644 index 0000000..9f2c8e3 --- /dev/null +++ oak-remote/src/test/java/org/apache/jackrabbit/oak/remote/content/ContentRemoteRepositoryTest.java @@ -0,0 +1,86 @@ +/* + * 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.collect.Sets; +import org.apache.jackrabbit.oak.api.ContentRepository; +import org.apache.jackrabbit.oak.api.ContentSession; +import org.apache.jackrabbit.oak.remote.RemoteCredentials; +import org.junit.Ignore; +import org.junit.Test; + +import javax.jcr.Credentials; +import javax.security.auth.login.LoginException; + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class ContentRemoteRepositoryTest { + + private ContentRemoteRepository createRepository() { + return createRepository(mock(ContentRepository.class)); + } + + private ContentRemoteRepository createRepository(ContentRepository repository) { + return new ContentRemoteRepository(repository); + } + + @Test + public void testCreateBasicCredentials() { + assertNotNull(createRepository().createBasicCredentials("admin", "admin".toCharArray())); + } + + @Test + @Ignore + public void testCreateImpersonationCredentials() { + assertNotNull(createRepository().createImpersonationCredentials(Sets.newHashSet("admin"))); + } + + @Test(expected = IllegalArgumentException.class) + public void testLoginWithNullCredentials() { + createRepository().login(null); + } + + @Test(expected = IllegalArgumentException.class) + public void testLoginWithInvalidCredentials() { + createRepository().login(mock(RemoteCredentials.class)); + } + + @Test + public void testSuccessfulLoginWithBasicCredentials() throws Exception { + ContentRepository repository = mock(ContentRepository.class); + when(repository.login(any(Credentials.class), anyString())).thenReturn(mock(ContentSession.class)); + + ContentRemoteRepository remoteRepository = createRepository(repository); + assertNotNull(remoteRepository.login(remoteRepository.createBasicCredentials("admin", "admin".toCharArray()))); + } + + @Test + public void testUnsuccessfulLoginWithBasicCredentials() throws Exception { + ContentRepository repository = mock(ContentRepository.class); + when(repository.login(any(Credentials.class), anyString())).thenThrow(LoginException.class); + + ContentRemoteRepository remoteRepository = createRepository(repository); + assertNull(remoteRepository.login(remoteRepository.createBasicCredentials("admin", "admin".toCharArray()))); + } + +} diff --git oak-remote/src/test/java/org/apache/jackrabbit/oak/remote/content/ContentRemoteSessionTest.java oak-remote/src/test/java/org/apache/jackrabbit/oak/remote/content/ContentRemoteSessionTest.java new file mode 100644 index 0000000..cd337b5 --- /dev/null +++ oak-remote/src/test/java/org/apache/jackrabbit/oak/remote/content/ContentRemoteSessionTest.java @@ -0,0 +1,480 @@ +/* + * 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.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.RemoteOperation; +import org.apache.jackrabbit.oak.remote.RemoteRevision; +import org.apache.jackrabbit.oak.remote.RemoteTreeFilters; +import org.apache.jackrabbit.oak.remote.RemoteValue; +import org.junit.Ignore; +import org.junit.Test; + +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.HashMap; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyString; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class ContentRemoteSessionTest { + + private ContentRemoteSession createSession() { + return createSession(mock(ContentSession.class)); + } + + private ContentRemoteSession createSession(ContentSession session) { + return new ContentRemoteSession(session); + } + + @Test + @Ignore + public void testReadLastRevision() { + assertNotNull(createSession().readLastRevision()); + } + + @Test + @Ignore + public void testReadLastRevisionAsString() { + assertNotNull(createSession().readLastRevision().asString()); + } + + @Test + @Ignore + public void testReadRevision() { + ContentRemoteSession session = createSession(); + assertNotNull(session.readRevision(session.readLastRevision().asString())); + } + + @Test + @Ignore + public void testReadRevisionAsString() { + ContentRemoteSession session = createSession(); + String revision = session.readLastRevision().asString(); + assertEquals(revision, session.readRevision(revision).asString()); + } + + @Test(expected = IllegalArgumentException.class) + public void testReadTreeWithNullRevision() { + createSession().readTree(null, "/", new RemoteTreeFilters()); + } + + @Test(expected = IllegalArgumentException.class) + public void testReadTreeWithInvalidRevision() { + createSession().readTree(mock(RemoteRevision.class), "/", new RemoteTreeFilters()); + } + + @Test(expected = IllegalArgumentException.class) + public void testReadTreeWithNullPath() { + ContentRemoteRevision revision = mock(ContentRemoteRevision.class); + createSession().readTree(revision, null, new RemoteTreeFilters()); + } + + @Test(expected = IllegalArgumentException.class) + public void testReadTreeWithInvalidPath() { + ContentRemoteRevision revision = mock(ContentRemoteRevision.class); + createSession().readTree(revision, "invalid", new RemoteTreeFilters()); + } + + @Test(expected = IllegalArgumentException.class) + public void testReadTreeWithNullFilters() { + ContentRemoteRevision revision = mock(ContentRemoteRevision.class); + createSession().readTree(revision, "/", null); + } + + @Test + public void testReadNonExistingTree() { + Tree tree = mock(Tree.class); + when(tree.exists()).thenReturn(false); + + Root root = mock(Root.class); + when(root.getTree(anyString())).thenReturn(tree); + + ContentRemoteRevision revision = mock(ContentRemoteRevision.class); + doReturn(root).when(revision).readRoot(any(ContentSession.class)); + + assertNull(createSession().readTree(revision, "/", new RemoteTreeFilters())); + } + + @Test + public void testReadExistingTree() { + Tree tree = mock(Tree.class); + when(tree.exists()).thenReturn(true); + + Root root = mock(Root.class); + when(root.getTree(anyString())).thenReturn(tree); + + ContentRemoteRevision revision = mock(ContentRemoteRevision.class); + doReturn(root).when(revision).readRoot(any(ContentSession.class)); + + assertNotNull(createSession().readTree(revision, "/", new RemoteTreeFilters())); + } + + @Test + public void testCreateAddOperation() { + assertNotNull(createSession().createAddOperation("/test", new HashMap())); + } + + @Test(expected = IllegalArgumentException.class) + public void testCreateAddOperationWithNullPath() { + createSession().createAddOperation(null, new HashMap()); + } + + @Test(expected = IllegalArgumentException.class) + public void testCreateAddOperationWithInvalidPath() { + createSession().createAddOperation("invalid", new HashMap()); + } + + @Test(expected = IllegalArgumentException.class) + public void testCreateAddOperationWithRootPath() { + createSession().createAddOperation("/", new HashMap()); + } + + @Test(expected = IllegalArgumentException.class) + public void createAddOperationWithNullProperties() { + createSession().createAddOperation("/test", null); + } + + @Test + public void testCreateRemoveOperation() { + assertNotNull(createSession().createRemoveOperation("/test")); + } + + @Test(expected = IllegalArgumentException.class) + public void testCreateRemoveOperationWithNullPath() { + createSession().createRemoveOperation(null); + } + + @Test(expected = IllegalArgumentException.class) + public void testCreateRemoveOperationWithInvalidPath() { + createSession().createRemoveOperation("invalid"); + } + + @Test(expected = IllegalArgumentException.class) + public void testCreateRemoveOperationWithRootPath() { + createSession().createRemoveOperation("/"); + } + + @Test + public void testCreateSetOperation() { + assertNotNull(createSession().createSetOperation("/test", "name", RemoteValue.toText("value"))); + } + + @Test(expected = IllegalArgumentException.class) + public void testCreateSetOperationWithNullPath() { + createSession().createSetOperation(null, "name", RemoteValue.toText("value")); + } + + @Test(expected = IllegalArgumentException.class) + public void testCreateSetOperationWithInvalidPath() { + createSession().createSetOperation("invalid", "name", RemoteValue.toText("value")); + } + + @Test(expected = IllegalArgumentException.class) + public void testCreateSetOperationWithNullName() { + createSession().createSetOperation("/test", null, RemoteValue.toText("value")); + } + + @Test(expected = IllegalArgumentException.class) + public void testCreateSetOperationWithEmptyName() { + createSession().createSetOperation("/test", "", RemoteValue.toText("value")); + } + + @Test(expected = IllegalArgumentException.class) + public void testCreateSetOperationWithNullValue() { + createSession().createSetOperation("/test", "name", null); + } + + @Test + public void testCreateUnsetOperation() { + assertNotNull(createSession().createUnsetOperation("/test", "name")); + } + + @Test(expected = IllegalArgumentException.class) + public void testCreateUnsetOperationWithNullPath() { + createSession().createUnsetOperation(null, "name"); + } + + @Test(expected = IllegalArgumentException.class) + public void testCreateUnsetOperationWithInvalidPath() { + createSession().createUnsetOperation("invalid", "name"); + } + + @Test(expected = IllegalArgumentException.class) + public void testCreateUnsetOperationWithNullName() { + createSession().createUnsetOperation("/test", null); + } + + @Test(expected = IllegalArgumentException.class) + public void testCreateUnsetOperationWithEmptyName() { + createSession().createUnsetOperation("/test", ""); + } + + @Test + public void createCopyOperation() { + assertNotNull(createSession().createCopyOperation("/source", "/target")); + } + + @Test(expected = IllegalArgumentException.class) + public void createCopyOperationWithNullSourcePath() { + createSession().createCopyOperation(null, "/target"); + } + + @Test(expected = IllegalArgumentException.class) + public void createCopyOperationWithInvalidSourcePath() { + createSession().createCopyOperation("invalid", "/target"); + } + + @Test(expected = IllegalArgumentException.class) + public void createCopyOperationWithNullTargetPath() { + createSession().createCopyOperation("/source", null); + } + + @Test(expected = IllegalArgumentException.class) + public void createCopyOperationWithInvalidTargetPath() { + createSession().createCopyOperation("/source", "invalid"); + } + + @Test(expected = IllegalArgumentException.class) + public void createCopyOperationWithSameSourceAndTargetPath() { + createSession().createCopyOperation("/same", "/same"); + } + + @Test(expected = IllegalArgumentException.class) + public void createCopyOperationWithSourceAncestorOfTarget() { + createSession().createCopyOperation("/source", "/source/target"); + } + + @Test + public void createMoveOperation() { + assertNotNull(createSession().createMoveOperation("/source", "/target")); + } + + @Test(expected = IllegalArgumentException.class) + public void createMoveOperationWithNullSourcePath() { + createSession().createMoveOperation(null, "/target"); + } + + @Test(expected = IllegalArgumentException.class) + public void createMoveOperationWithInvalidSourcePath() { + createSession().createMoveOperation("invalid", "/target"); + } + + @Test(expected = IllegalArgumentException.class) + public void createMoveOperationWithNullTargetPath() { + createSession().createMoveOperation("/source", null); + } + + @Test(expected = IllegalArgumentException.class) + public void createMoveOperationWithInvalidTargetPath() { + createSession().createMoveOperation("/source", "invalid"); + } + + @Test(expected = IllegalArgumentException.class) + public void createMoveOperationWithSameSourceAndTargetPath() { + createSession().createMoveOperation("/same", "/same"); + } + + @Test(expected = IllegalArgumentException.class) + public void createMoveOperationWithSourceAncestorOfTarget() { + createSession().createMoveOperation("/source", "/source/target"); + } + + @Test + public void createAggregateOperation() { + assertNotNull(createSession().createAggregateOperation(new ArrayList())); + } + + @Test(expected = IllegalArgumentException.class) + public void createAggregateOperationWithNullList() { + createSession().createAggregateOperation(null); + } + + @Test + @Ignore + public void testCommit() { + Root root = mock(Root.class); + + ContentRemoteOperation operation = mock(ContentRemoteOperation.class); + + ContentRemoteRevision revision = mock(ContentRemoteRevision.class); + doReturn(root).when(revision).readRoot(any(ContentSession.class)); + + assertNotNull(createSession().commit(revision, operation)); + } + + @Test(expected = IllegalArgumentException.class) + public void testCommitWithNullRevision() { + createSession().commit(null, mock(ContentRemoteOperation.class)); + } + + @Test(expected = IllegalArgumentException.class) + public void testCommitWithInvalidRevision() { + createSession().commit(mock(RemoteRevision.class), mock(ContentRemoteOperation.class)); + } + + @Test(expected = IllegalArgumentException.class) + public void testCommitWithNullOperation() { + createSession().commit(mock(ContentRemoteRevision.class), null); + } + + @Test(expected = IllegalArgumentException.class) + public void testCommitWithInvalidOperation() { + createSession().commit(mock(ContentRemoteRevision.class), mock(RemoteOperation.class)); + } + + @Test + public void testCommitWithOperationThrowingException() { + ContentRemoteOperation operation = mock(ContentRemoteOperation.class); + doThrow(IllegalStateException.class).when(operation).apply(any(Root.class)); + + assertNull(createSession().commit(mock(ContentRemoteRevision.class), operation)); + } + + @Test + public void testCommitWithConflictingCommit() throws Exception { + Root root = mock(Root.class); + doThrow(CommitFailedException.class).when(root).commit(); + + ContentRemoteRevision revision = mock(ContentRemoteRevision.class); + doReturn(root).when(revision).readRoot(any(ContentSession.class)); + + assertNull(createSession().commit(revision, mock(ContentRemoteOperation.class))); + } + + @Test + public void testReadBinaryId() { + Blob blob = mock(Blob.class); + + Root root = mock(Root.class); + doReturn(blob).when(root).getBlob(anyString()); + + ContentSession session = mock(ContentSession.class); + doReturn(root).when(session).getLatestRoot(); + + ContentRemoteSession remoteSession = createSession(session); + assertNotNull(remoteSession.readBinaryId("id")); + } + + @Test + public void testReadBinaryIdAsString() { + Blob blob = mock(Blob.class); + + Root root = mock(Root.class); + doReturn(blob).when(root).getBlob(anyString()); + + ContentSession session = mock(ContentSession.class); + doReturn(root).when(session).getLatestRoot(); + + ContentRemoteSession remoteSession = createSession(session); + assertEquals("id", remoteSession.readBinaryId("id").asString()); + } + + @Test(expected = IllegalArgumentException.class) + public void testReadBinaryIdWithNullReference() { + createSession().readBinaryId(null); + } + + @Test(expected = IllegalArgumentException.class) + public void testReadBinaryIdWithEmptyReference() { + createSession().readBinaryId(""); + } + + @Test + public void testReadBinaryIdWithInvalidReference() { + Root root = mock(Root.class); + doReturn(null).when(root).getBlob(anyString()); + + ContentSession session = mock(ContentSession.class); + doReturn(root).when(session).getLatestRoot(); + + ContentRemoteSession remoteSession = createSession(session); + assertNull(remoteSession.readBinaryId("id")); + } + + @Test + public void testReadBinary() { + Blob blob = mock(Blob.class); + + ContentRemoteBinaryId binaryId = mock(ContentRemoteBinaryId.class); + doReturn(blob).when(binaryId).asBlob(); + + assertNotNull(createSession().readBinary(binaryId, new RemoteBinaryFilters())); + } + + @Test(expected = IllegalArgumentException.class) + public void testReadBinaryWithNullId() { + createSession().readBinary(null, new RemoteBinaryFilters()); + } + + @Test(expected = IllegalArgumentException.class) + public void testReadBinaryWithInvalidId() { + createSession().readBinary(mock(RemoteBinaryId.class), new RemoteBinaryFilters()); + } + + @Test(expected = IllegalArgumentException.class) + public void testReadBinaryWithNullFilters() { + createSession().readBinary(mock(ContentRemoteBinaryId.class), null); + } + + @Test + public void testWriteBinary() throws Exception { + Blob blob = mock(Blob.class); + + Root root = mock(Root.class); + doReturn(blob).when(root).createBlob(any(InputStream.class)); + + ContentSession session = mock(ContentSession.class); + doReturn(root).when(session).getLatestRoot(); + + ContentRemoteSession remoteSession = createSession(session); + assertNotNull(remoteSession.writeBinary(mock(InputStream.class))); + } + + @Test(expected = IllegalArgumentException.class) + public void testWriteBinaryWithNullStream() { + createSession().writeBinary(null); + } + + @Test + public void testWriteBlobFailure() throws Exception { + Root root = mock(Root.class); + doThrow(IOException.class).when(root).createBlob(any(InputStream.class)); + + ContentSession session = mock(ContentSession.class); + doReturn(root).when(session).getLatestRoot(); + + ContentRemoteSession remoteSession = createSession(session); + assertNull(remoteSession.writeBinary(mock(InputStream.class))); + } + +} diff --git oak-remote/src/test/java/org/apache/jackrabbit/oak/remote/content/ContentRemoteTreeTest.java oak-remote/src/test/java/org/apache/jackrabbit/oak/remote/content/ContentRemoteTreeTest.java new file mode 100644 index 0000000..a2c6ff9 --- /dev/null +++ oak-remote/src/test/java/org/apache/jackrabbit/oak/remote/content/ContentRemoteTreeTest.java @@ -0,0 +1,939 @@ +/* + * 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.collect.Sets; +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.util.ISO8601; +import org.junit.Test; + +import java.io.InputStream; +import java.math.BigDecimal; +import java.util.Calendar; +import java.util.Map; +import java.util.Set; + +import static com.google.common.collect.Iterables.getOnlyElement; +import static com.google.common.collect.Sets.newHashSet; +import static java.util.Arrays.asList; +import static java.util.Collections.singletonList; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; + +public class ContentRemoteTreeTest { + + private ContentRemoteTree createTree(Tree tree) { + return new ContentRemoteTree(tree, 0, new RemoteTreeFilters()); + } + + private ContentRemoteTree createTree(Tree tree, RemoteTreeFilters filters) { + return new ContentRemoteTree(tree, 0, filters); + } + + @Test + public void testGetBinaryProperty() { + InputStream stream = mock(InputStream.class); + + Blob blob = mock(Blob.class); + doReturn(stream).when(blob).getNewStream(); + + PropertyState property = mock(PropertyState.class); + doReturn("name").when(property).getName(); + doReturn(Type.BINARY).when(property).getType(); + doReturn(blob).when(property).getValue(Type.BINARY); + + Tree tree = mock(Tree.class); + doReturn(singletonList(property)).when(tree).getProperties(); + + ContentRemoteTree remoteTree = createTree(tree, new RemoteTreeFilters() { + + @Override + public long getBinaryThreshold() { + return Long.MAX_VALUE; + } + + }); + + Map properties = remoteTree.getProperties(); + + assertTrue(properties.containsKey("name")); + assertTrue(properties.get("name").isBinary()); + assertEquals(stream, properties.get("name").asBinary().get()); + } + + @Test + public void testGetMultiBinaryProperty() { + InputStream stream = mock(InputStream.class); + + Blob blob = mock(Blob.class); + doReturn(stream).when(blob).getNewStream(); + + PropertyState property = mock(PropertyState.class); + doReturn("name").when(property).getName(); + doReturn(Type.BINARIES).when(property).getType(); + doReturn(singletonList(blob)).when(property).getValue(Type.BINARIES); + + Tree tree = mock(Tree.class); + doReturn(singletonList(property)).when(tree).getProperties(); + + ContentRemoteTree remoteTree = createTree(tree, new RemoteTreeFilters() { + + @Override + public long getBinaryThreshold() { + return Long.MAX_VALUE; + } + + }); + + Map properties = remoteTree.getProperties(); + + assertTrue(properties.containsKey("name")); + assertTrue(properties.get("name").isMultiBinary()); + assertEquals(stream, getOnlyElement(properties.get("name").asMultiBinary()).get()); + } + + @Test + public void testGetBinaryIdProperty() { + Blob blob = mock(Blob.class); + doReturn("id").when(blob).getReference(); + + PropertyState property = mock(PropertyState.class); + doReturn("name").when(property).getName(); + doReturn(Type.BINARY).when(property).getType(); + doReturn(blob).when(property).getValue(Type.BINARY); + + Tree tree = mock(Tree.class); + doReturn(singletonList(property)).when(tree).getProperties(); + + Map properties = createTree(tree).getProperties(); + + assertTrue(properties.containsKey("name")); + assertTrue(properties.get("name").isBinaryId()); + assertEquals("id", properties.get("name").asBinaryId()); + } + + @Test + public void testGetMultiBinaryIdProperty() { + Blob blob = mock(Blob.class); + doReturn("id").when(blob).getReference(); + + PropertyState property = mock(PropertyState.class); + doReturn("name").when(property).getName(); + doReturn(Type.BINARIES).when(property).getType(); + doReturn(singletonList(blob)).when(property).getValue(Type.BINARIES); + + Tree tree = mock(Tree.class); + doReturn(singletonList(property)).when(tree).getProperties(); + + Map properties = createTree(tree).getProperties(); + + assertTrue(properties.containsKey("name")); + assertTrue(properties.get("name").isMultiBinaryId()); + assertEquals("id", getOnlyElement(properties.get("name").asMultiBinaryId())); + } + + @Test + public void testGetBooleanProperty() { + PropertyState property = mock(PropertyState.class); + doReturn("name").when(property).getName(); + doReturn(Type.BOOLEAN).when(property).getType(); + doReturn(true).when(property).getValue(Type.BOOLEAN); + + Tree tree = mock(Tree.class); + doReturn(singletonList(property)).when(tree).getProperties(); + + Map properties = createTree(tree).getProperties(); + + assertTrue(properties.containsKey("name")); + assertTrue(properties.get("name").isBoolean()); + assertEquals(true, properties.get("name").asBoolean()); + } + + @Test + public void testGetMultiBooleanProperty() { + PropertyState property = mock(PropertyState.class); + doReturn("name").when(property).getName(); + doReturn(Type.BOOLEANS).when(property).getType(); + doReturn(singletonList(true)).when(property).getValue(Type.BOOLEANS); + + Tree tree = mock(Tree.class); + doReturn(singletonList(property)).when(tree).getProperties(); + + Map properties = createTree(tree).getProperties(); + + assertTrue(properties.containsKey("name")); + assertTrue(properties.get("name").isMultiBoolean()); + assertEquals(true, getOnlyElement(properties.get("name").asMultiBoolean())); + } + + @Test + public void testGetDateProperty() { + Calendar calendar = Calendar.getInstance(); + + PropertyState property = mock(PropertyState.class); + doReturn("name").when(property).getName(); + doReturn(Type.DATE).when(property).getType(); + doReturn(ISO8601.format(calendar)).when(property).getValue(Type.DATE); + + Tree tree = mock(Tree.class); + doReturn(singletonList(property)).when(tree).getProperties(); + + Map properties = createTree(tree).getProperties(); + + assertTrue(properties.containsKey("name")); + assertTrue(properties.get("name").isDate()); + assertEquals(calendar.getTimeInMillis(), properties.get("name").asDate().longValue()); + } + + @Test + public void testGetMultiDateProperty() { + Calendar calendar = Calendar.getInstance(); + + PropertyState property = mock(PropertyState.class); + doReturn("name").when(property).getName(); + doReturn(Type.DATES).when(property).getType(); + doReturn(singletonList(ISO8601.format(calendar))).when(property).getValue(Type.DATES); + + Tree tree = mock(Tree.class); + doReturn(singletonList(property)).when(tree).getProperties(); + + Map properties = createTree(tree).getProperties(); + + assertTrue(properties.containsKey("name")); + assertTrue(properties.get("name").isMultiDate()); + assertEquals(calendar.getTimeInMillis(), getOnlyElement(properties.get("name").asMultiDate()).longValue()); + } + + @Test + public void testDecimalProperty() { + PropertyState property = mock(PropertyState.class); + doReturn("name").when(property).getName(); + doReturn(Type.DECIMAL).when(property).getType(); + doReturn(BigDecimal.ONE).when(property).getValue(Type.DECIMAL); + + Tree tree = mock(Tree.class); + doReturn(singletonList(property)).when(tree).getProperties(); + + Map properties = createTree(tree).getProperties(); + + assertTrue(properties.containsKey("name")); + assertTrue(properties.get("name").isDecimal()); + assertEquals(BigDecimal.ONE, properties.get("name").asDecimal()); + } + + @Test + public void testGetMultiDecimalProperty() { + PropertyState property = mock(PropertyState.class); + doReturn("name").when(property).getName(); + doReturn(Type.DECIMALS).when(property).getType(); + doReturn(singletonList(BigDecimal.ONE)).when(property).getValue(Type.DECIMALS); + + Tree tree = mock(Tree.class); + doReturn(singletonList(property)).when(tree).getProperties(); + + Map properties = createTree(tree).getProperties(); + + assertTrue(properties.containsKey("name")); + assertTrue(properties.get("name").isMultiDecimal()); + assertEquals(BigDecimal.ONE, getOnlyElement(properties.get("name").asMultiDecimal())); + } + + @Test + public void testDoubleProperty() { + PropertyState property = mock(PropertyState.class); + doReturn("name").when(property).getName(); + doReturn(Type.DOUBLE).when(property).getType(); + doReturn(4.2).when(property).getValue(Type.DOUBLE); + + Tree tree = mock(Tree.class); + doReturn(singletonList(property)).when(tree).getProperties(); + + Map properties = createTree(tree).getProperties(); + + assertTrue(properties.containsKey("name")); + assertTrue(properties.get("name").isDouble()); + assertEquals(4.2, properties.get("name").asDouble(), 1e-9); + } + + @Test + public void testGetMultiDoubleProperty() { + PropertyState property = mock(PropertyState.class); + doReturn("name").when(property).getName(); + doReturn(Type.DOUBLES).when(property).getType(); + doReturn(singletonList(4.2)).when(property).getValue(Type.DOUBLES); + + Tree tree = mock(Tree.class); + doReturn(singletonList(property)).when(tree).getProperties(); + + Map properties = createTree(tree).getProperties(); + + assertTrue(properties.containsKey("name")); + assertTrue(properties.get("name").isMultiDouble()); + assertEquals(4.2, getOnlyElement(properties.get("name").asMultiDouble()), 1e-9); + } + + @Test + public void testLongProperty() { + PropertyState property = mock(PropertyState.class); + doReturn("name").when(property).getName(); + doReturn(Type.LONG).when(property).getType(); + doReturn(42L).when(property).getValue(Type.LONG); + + Tree tree = mock(Tree.class); + doReturn(singletonList(property)).when(tree).getProperties(); + + Map properties = createTree(tree).getProperties(); + + assertTrue(properties.containsKey("name")); + assertTrue(properties.get("name").isLong()); + assertEquals(42L, properties.get("name").asLong().longValue()); + } + + @Test + public void testGetMultiLongProperty() { + PropertyState property = mock(PropertyState.class); + doReturn("name").when(property).getName(); + doReturn(Type.LONGS).when(property).getType(); + doReturn(singletonList(42L)).when(property).getValue(Type.LONGS); + + Tree tree = mock(Tree.class); + doReturn(singletonList(property)).when(tree).getProperties(); + + Map properties = createTree(tree).getProperties(); + + assertTrue(properties.containsKey("name")); + assertTrue(properties.get("name").isMultiLong()); + assertEquals(42L, getOnlyElement(properties.get("name").asMultiLong()).longValue()); + } + + @Test + public void testNameProperty() { + PropertyState property = mock(PropertyState.class); + doReturn("name").when(property).getName(); + doReturn(Type.NAME).when(property).getType(); + doReturn("value").when(property).getValue(Type.NAME); + + Tree tree = mock(Tree.class); + doReturn(singletonList(property)).when(tree).getProperties(); + + Map properties = createTree(tree).getProperties(); + + assertTrue(properties.containsKey("name")); + assertTrue(properties.get("name").isName()); + assertEquals("value", properties.get("name").asName()); + } + + @Test + public void testGetMultiNameProperty() { + PropertyState property = mock(PropertyState.class); + doReturn("name").when(property).getName(); + doReturn(Type.NAMES).when(property).getType(); + doReturn(singletonList("value")).when(property).getValue(Type.NAMES); + + Tree tree = mock(Tree.class); + doReturn(singletonList(property)).when(tree).getProperties(); + + Map properties = createTree(tree).getProperties(); + + assertTrue(properties.containsKey("name")); + assertTrue(properties.get("name").isMultiName()); + assertEquals("value", getOnlyElement(properties.get("name").asMultiName())); + } + + @Test + public void testPathProperty() { + PropertyState property = mock(PropertyState.class); + doReturn("name").when(property).getName(); + doReturn(Type.PATH).when(property).getType(); + doReturn("value").when(property).getValue(Type.PATH); + + Tree tree = mock(Tree.class); + doReturn(singletonList(property)).when(tree).getProperties(); + + Map properties = createTree(tree).getProperties(); + + assertTrue(properties.containsKey("name")); + assertTrue(properties.get("name").isPath()); + assertEquals("value", properties.get("name").asPath()); + } + + @Test + public void testGetMultiPathProperty() { + PropertyState property = mock(PropertyState.class); + doReturn("name").when(property).getName(); + doReturn(Type.PATHS).when(property).getType(); + doReturn(singletonList("value")).when(property).getValue(Type.PATHS); + + Tree tree = mock(Tree.class); + doReturn(singletonList(property)).when(tree).getProperties(); + + Map properties = createTree(tree).getProperties(); + + assertTrue(properties.containsKey("name")); + assertTrue(properties.get("name").isMultiPath()); + assertEquals("value", getOnlyElement(properties.get("name").asMultiPath())); + } + + @Test + public void testReferenceProperty() { + PropertyState property = mock(PropertyState.class); + doReturn("name").when(property).getName(); + doReturn(Type.REFERENCE).when(property).getType(); + doReturn("value").when(property).getValue(Type.REFERENCE); + + Tree tree = mock(Tree.class); + doReturn(singletonList(property)).when(tree).getProperties(); + + Map properties = createTree(tree).getProperties(); + + assertTrue(properties.containsKey("name")); + assertTrue(properties.get("name").isReference()); + assertEquals("value", properties.get("name").asReference()); + } + + @Test + public void testGetMultiReferenceProperty() { + PropertyState property = mock(PropertyState.class); + doReturn("name").when(property).getName(); + doReturn(Type.REFERENCES).when(property).getType(); + doReturn(singletonList("value")).when(property).getValue(Type.REFERENCES); + + Tree tree = mock(Tree.class); + doReturn(singletonList(property)).when(tree).getProperties(); + + Map properties = createTree(tree).getProperties(); + + assertTrue(properties.containsKey("name")); + assertTrue(properties.get("name").isMultiReference()); + assertEquals("value", getOnlyElement(properties.get("name").asMultiReference())); + } + + @Test + public void testTextProperty() { + PropertyState property = mock(PropertyState.class); + doReturn("name").when(property).getName(); + doReturn(Type.STRING).when(property).getType(); + doReturn("value").when(property).getValue(Type.STRING); + + Tree tree = mock(Tree.class); + doReturn(singletonList(property)).when(tree).getProperties(); + + Map properties = createTree(tree).getProperties(); + + assertTrue(properties.containsKey("name")); + assertTrue(properties.get("name").isText()); + assertEquals("value", properties.get("name").asText()); + } + + @Test + public void testGetMultiTextProperty() { + PropertyState property = mock(PropertyState.class); + doReturn("name").when(property).getName(); + doReturn(Type.STRINGS).when(property).getType(); + doReturn(singletonList("value")).when(property).getValue(Type.STRINGS); + + Tree tree = mock(Tree.class); + doReturn(singletonList(property)).when(tree).getProperties(); + + Map properties = createTree(tree).getProperties(); + + assertTrue(properties.containsKey("name")); + assertTrue(properties.get("name").isMultiText()); + assertEquals("value", getOnlyElement(properties.get("name").asMultiText())); + } + + @Test + public void testUriProperty() { + PropertyState property = mock(PropertyState.class); + doReturn("name").when(property).getName(); + doReturn(Type.URI).when(property).getType(); + doReturn("value").when(property).getValue(Type.URI); + + Tree tree = mock(Tree.class); + doReturn(singletonList(property)).when(tree).getProperties(); + + Map properties = createTree(tree).getProperties(); + + assertTrue(properties.containsKey("name")); + assertTrue(properties.get("name").isUri()); + assertEquals("value", properties.get("name").asUri()); + } + + @Test + public void testGetMultiUriProperty() { + PropertyState property = mock(PropertyState.class); + doReturn("name").when(property).getName(); + doReturn(Type.URIS).when(property).getType(); + doReturn(singletonList("value")).when(property).getValue(Type.URIS); + + Tree tree = mock(Tree.class); + doReturn(singletonList(property)).when(tree).getProperties(); + + Map properties = createTree(tree).getProperties(); + + assertTrue(properties.containsKey("name")); + assertTrue(properties.get("name").isMultiUri()); + assertEquals("value", getOnlyElement(properties.get("name").asMultiUri())); + } + + @Test + public void testWeakReferenceProperty() { + PropertyState property = mock(PropertyState.class); + doReturn("name").when(property).getName(); + doReturn(Type.WEAKREFERENCE).when(property).getType(); + doReturn("value").when(property).getValue(Type.WEAKREFERENCE); + + Tree tree = mock(Tree.class); + doReturn(singletonList(property)).when(tree).getProperties(); + + Map properties = createTree(tree).getProperties(); + + assertTrue(properties.containsKey("name")); + assertTrue(properties.get("name").isWeakReference()); + assertEquals("value", properties.get("name").asWeakReference()); + } + + @Test + public void testGetMultiWeakReferenceProperty() { + PropertyState property = mock(PropertyState.class); + doReturn("name").when(property).getName(); + doReturn(Type.WEAKREFERENCES).when(property).getType(); + doReturn(singletonList("value")).when(property).getValue(Type.WEAKREFERENCES); + + Tree tree = mock(Tree.class); + doReturn(singletonList(property)).when(tree).getProperties(); + + Map properties = createTree(tree).getProperties(); + + assertTrue(properties.containsKey("name")); + assertTrue(properties.get("name").isMultiWeakReference()); + assertEquals("value", getOnlyElement(properties.get("name").asMultiWeakReference())); + } + + @Test + public void testFilterPropertyIn() { + PropertyState fooProperty = mock(PropertyState.class); + doReturn("foo").when(fooProperty).getName(); + doReturn(Type.BOOLEAN).when(fooProperty).getType(); + doReturn(true).when(fooProperty).getValue(Type.BOOLEAN); + + PropertyState barProperty = mock(PropertyState.class); + doReturn("bar").when(barProperty).getName(); + doReturn(Type.BOOLEAN).when(barProperty).getType(); + doReturn(true).when(barProperty).getValue(Type.BOOLEAN); + + Tree tree = mock(Tree.class); + doReturn(asList(fooProperty, barProperty)).when(tree).getProperties(); + + ContentRemoteTree remoteTree = createTree(tree, new RemoteTreeFilters() { + + @Override + public Set getPropertyFilters() { + return newHashSet("foo"); + } + + }); + + Map properties = remoteTree.getProperties(); + + assertTrue(properties.containsKey("foo")); + assertFalse(properties.containsKey("bar")); + } + + @Test + public void testFilterPropertyOut() { + PropertyState fooProperty = mock(PropertyState.class); + doReturn("foo").when(fooProperty).getName(); + doReturn(Type.BOOLEAN).when(fooProperty).getType(); + doReturn(true).when(fooProperty).getValue(Type.BOOLEAN); + + PropertyState barProperty = mock(PropertyState.class); + doReturn("bar").when(barProperty).getName(); + doReturn(Type.BOOLEAN).when(barProperty).getType(); + doReturn(true).when(barProperty).getValue(Type.BOOLEAN); + + Tree tree = mock(Tree.class); + doReturn(asList(fooProperty, barProperty)).when(tree).getProperties(); + + ContentRemoteTree remoteTree = createTree(tree, new RemoteTreeFilters() { + + @Override + public Set getPropertyFilters() { + return newHashSet("-bar"); + } + + }); + + Map properties = remoteTree.getProperties(); + + assertTrue(properties.containsKey("foo")); + assertFalse(properties.containsKey("bar")); + } + + @Test + public void testGetChildrenMaxDepth() { + Tree child = mock(Tree.class); + doReturn("child").when(child).getName(); + + Tree tree = mock(Tree.class); + doReturn(asList(child)).when(tree).getChildren(); + + Map children = createTree(tree).getChildren(); + + assertTrue(children.containsKey("child")); + assertNull(children.get("child")); + } + + @Test + public void testGetChildren() { + Tree child = mock(Tree.class); + doReturn("child").when(child).getName(); + + Tree tree = mock(Tree.class); + doReturn(asList(child)).when(tree).getChildren(); + + ContentRemoteTree remoteTree = createTree(tree, new RemoteTreeFilters() { + + @Override + public int getDepth() { + return 1; + } + + }); + + Map children = remoteTree.getChildren(); + + assertTrue(children.containsKey("child")); + assertNotNull(children.get("child")); + } + + @Test + public void testGetChildrenWithStart() { + Tree foo = mock(Tree.class); + doReturn("foo").when(foo).getName(); + + Tree bar = mock(Tree.class); + doReturn("bar").when(bar).getName(); + + Tree tree = mock(Tree.class); + doReturn(asList(foo, bar)).when(tree).getChildren(); + + ContentRemoteTree remoteTree = createTree(tree, new RemoteTreeFilters() { + + @Override + public int getChildrenStart() { + return 1; + } + + }); + + Map children = remoteTree.getChildren(); + + assertFalse(children.containsKey("foo")); + assertTrue(children.containsKey("bar")); + } + + @Test + public void testGetChildrenWithNegativeStart() { + Tree foo = mock(Tree.class); + doReturn("foo").when(foo).getName(); + + Tree bar = mock(Tree.class); + doReturn("bar").when(bar).getName(); + + Tree tree = mock(Tree.class); + doReturn(asList(foo, bar)).when(tree).getChildren(); + + ContentRemoteTree remoteTree = createTree(tree, new RemoteTreeFilters() { + + @Override + public int getChildrenStart() { + return -1; + } + + }); + + Map children = remoteTree.getChildren(); + + assertTrue(children.containsKey("foo")); + assertTrue(children.containsKey("bar")); + } + + @Test + public void testGetChildrenWithStartTooBig() { + Tree foo = mock(Tree.class); + doReturn("foo").when(foo).getName(); + + Tree bar = mock(Tree.class); + doReturn("bar").when(bar).getName(); + + Tree tree = mock(Tree.class); + doReturn(asList(foo, bar)).when(tree).getChildren(); + + ContentRemoteTree remoteTree = createTree(tree, new RemoteTreeFilters() { + + @Override + public int getChildrenStart() { + return 2; + } + + }); + + Map children = remoteTree.getChildren(); + + assertFalse(children.containsKey("foo")); + assertFalse(children.containsKey("bar")); + } + + @Test + public void testGetChildrenWithCount() { + Tree foo = mock(Tree.class); + doReturn("foo").when(foo).getName(); + + Tree bar = mock(Tree.class); + doReturn("bar").when(bar).getName(); + + Tree tree = mock(Tree.class); + doReturn(asList(foo, bar)).when(tree).getChildren(); + + ContentRemoteTree remoteTree = createTree(tree, new RemoteTreeFilters() { + + @Override + public int getChildrenCount() { + return 1; + } + }); + + Map children = remoteTree.getChildren(); + + assertTrue(children.containsKey("foo")); + assertFalse(children.containsKey("bar")); + } + + @Test + public void testGetChildrenWithNegativeCount() { + Tree foo = mock(Tree.class); + doReturn("foo").when(foo).getName(); + + Tree bar = mock(Tree.class); + doReturn("bar").when(bar).getName(); + + Tree tree = mock(Tree.class); + doReturn(asList(foo, bar)).when(tree).getChildren(); + + ContentRemoteTree remoteTree = createTree(tree, new RemoteTreeFilters() { + + @Override + public int getChildrenCount() { + return -1; + } + }); + + Map children = remoteTree.getChildren(); + + assertTrue(children.containsKey("foo")); + assertTrue(children.containsKey("bar")); + } + + @Test + public void testGetChildrenWithZeroCount() { + Tree foo = mock(Tree.class); + doReturn("foo").when(foo).getName(); + + Tree bar = mock(Tree.class); + doReturn("bar").when(bar).getName(); + + Tree tree = mock(Tree.class); + doReturn(asList(foo, bar)).when(tree).getChildren(); + + ContentRemoteTree remoteTree = createTree(tree, new RemoteTreeFilters() { + + @Override + public int getChildrenCount() { + return 0; + } + }); + + Map children = remoteTree.getChildren(); + + assertFalse(children.containsKey("foo")); + assertFalse(children.containsKey("bar")); + } + + @Test + public void testGetChildrenWithCountTooBig() { + Tree foo = mock(Tree.class); + doReturn("foo").when(foo).getName(); + + Tree bar = mock(Tree.class); + doReturn("bar").when(bar).getName(); + + Tree tree = mock(Tree.class); + doReturn(asList(foo, bar)).when(tree).getChildren(); + + ContentRemoteTree remoteTree = createTree(tree, new RemoteTreeFilters() { + + @Override + public int getChildrenCount() { + return 3; + } + }); + + Map children = remoteTree.getChildren(); + + assertTrue(children.containsKey("foo")); + assertTrue(children.containsKey("bar")); + } + + @Test + public void testGetChildrenWithSlicing() { + Tree foo = mock(Tree.class); + doReturn("foo").when(foo).getName(); + + Tree bar = mock(Tree.class); + doReturn("bar").when(bar).getName(); + + Tree baz = mock(Tree.class); + doReturn("baz").when(baz).getName(); + + Tree tree = mock(Tree.class); + doReturn(asList(foo, bar, baz)).when(tree).getChildren(); + + ContentRemoteTree remoteTree = createTree(tree, new RemoteTreeFilters() { + + @Override + public int getChildrenStart() { + return 1; + } + + @Override + public int getChildrenCount() { + return 1; + } + }); + + Map children = remoteTree.getChildren(); + + assertFalse(children.containsKey("foo")); + assertTrue(children.containsKey("bar")); + assertFalse(children.containsKey("baz")); + } + + @Test + public void testGetChildrenWithIncludeFilters() { + Tree foo = mock(Tree.class); + doReturn("foo").when(foo).getName(); + + Tree bar = mock(Tree.class); + doReturn("bar").when(bar).getName(); + + Tree tree = mock(Tree.class); + doReturn(asList(foo, bar)).when(tree).getChildren(); + + ContentRemoteTree remoteTree = createTree(tree, new RemoteTreeFilters() { + + @Override + public Set getNodeFilters() { + return newHashSet("foo"); + } + + }); + + Map children = remoteTree.getChildren(); + + assertTrue(children.containsKey("foo")); + assertFalse(children.containsKey("bar")); + } + + @Test + public void testGetChildrenWithExcludeFilters() { + Tree foo = mock(Tree.class); + doReturn("foo").when(foo).getName(); + + Tree bar = mock(Tree.class); + doReturn("bar").when(bar).getName(); + + Tree tree = mock(Tree.class); + doReturn(asList(foo, bar)).when(tree).getChildren(); + + ContentRemoteTree remoteTree = createTree(tree, new RemoteTreeFilters() { + + @Override + public Set getNodeFilters() { + return newHashSet("-bar"); + } + + }); + + Map children = remoteTree.getChildren(); + + assertTrue(children.containsKey("foo")); + assertFalse(children.containsKey("bar")); + } + + @Test + public void testGetChildrenWithSlicingAndFiltering() { + Tree bar = mock(Tree.class); + doReturn("bar").when(bar).getName(); + + Tree foo = mock(Tree.class); + doReturn("foo").when(foo).getName(); + + Tree baz = mock(Tree.class); + doReturn("baz").when(baz).getName(); + + Tree tree = mock(Tree.class); + doReturn(asList(bar, foo, baz)).when(tree).getChildren(); + + ContentRemoteTree remoteTree = createTree(tree, new RemoteTreeFilters() { + + @Override + public Set getNodeFilters() { + return Sets.newHashSet("ba*"); + } + + @Override + public int getChildrenStart() { + return 1; + } + + @Override + public int getChildrenCount() { + return 1; + } + }); + + Map children = remoteTree.getChildren(); + + assertFalse(children.containsKey("bar")); + assertFalse(children.containsKey("foo")); + assertFalse(children.containsKey("baz")); + } + +} diff --git oak-remote/src/test/java/org/apache/jackrabbit/oak/remote/content/CopyContentRemoteOperationTest.java oak-remote/src/test/java/org/apache/jackrabbit/oak/remote/content/CopyContentRemoteOperationTest.java new file mode 100644 index 0000000..597653d --- /dev/null +++ oak-remote/src/test/java/org/apache/jackrabbit/oak/remote/content/CopyContentRemoteOperationTest.java @@ -0,0 +1,104 @@ +/* + * 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.junit.Test; + +import static java.util.Collections.emptyList; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +public class CopyContentRemoteOperationTest { + + CopyContentRemoteOperation createOperation(String source, String target) { + return new CopyContentRemoteOperation(source, target); + } + + @Test + public void testCopy() { + Tree parent = mock(Tree.class); + doReturn(true).when(parent).exists(); + + Tree target = mock(Tree.class); + doReturn(false).when(target).exists(); + doReturn(parent).when(target).getParent(); + doReturn("target").when(target).getName(); + + Tree source = mock(Tree.class); + doReturn(true).when(source).exists(); + doReturn(emptyList()).when(source).getProperties(); + doReturn(emptyList()).when(source).getChildren(); + + Root root = mock(Root.class); + doReturn(source).when(root).getTree("/source"); + doReturn(target).when(root).getTree("/target"); + + createOperation("/source", "/target").apply(root); + + verify(parent).addChild("target"); + } + + @Test(expected = IllegalStateException.class) + public void testCopyWithNonExistingSource() { + Tree source = mock(Tree.class); + doReturn(false).when(source).exists(); + + Root root = mock(Root.class); + doReturn(source).when(root).getTree("/source"); + + createOperation("/source", "/target").apply(root); + } + + @Test(expected = IllegalStateException.class) + public void testCopyWithExistingTarget() { + Tree target = mock(Tree.class); + doReturn(true).when(target).exists(); + + Tree source = mock(Tree.class); + doReturn(true).when(source).exists(); + + Root root = mock(Root.class); + doReturn(source).when(root).getTree("/source"); + doReturn(target).when(root).getTree("/target"); + + createOperation("/source", "/target").apply(root); + } + + @Test(expected = IllegalStateException.class) + public void testCopyWithNonExistingTargetParent() { + Tree parent = mock(Tree.class); + doReturn(false).when(parent).exists(); + + Tree target = mock(Tree.class); + doReturn(false).when(target).exists(); + doReturn(parent).when(target).getParent(); + + Tree source = mock(Tree.class); + doReturn(true).when(source).exists(); + + Root root = mock(Root.class); + doReturn(source).when(root).getTree("/source"); + doReturn(target).when(root).getTree("/target"); + + createOperation("/source", "/target").apply(root); + } + +} diff --git oak-remote/src/test/java/org/apache/jackrabbit/oak/remote/content/MoveContentRemoteOperationTest.java oak-remote/src/test/java/org/apache/jackrabbit/oak/remote/content/MoveContentRemoteOperationTest.java new file mode 100644 index 0000000..630083e --- /dev/null +++ oak-remote/src/test/java/org/apache/jackrabbit/oak/remote/content/MoveContentRemoteOperationTest.java @@ -0,0 +1,48 @@ +/* + * 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.junit.Test; + +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; + +public class MoveContentRemoteOperationTest { + + MoveContentRemoteOperation createOperation(String source, String target) { + return new MoveContentRemoteOperation(source, target); + } + + @Test + public void testMove() { + Root root = mock(Root.class); + doReturn(true).when(root).move("/source", "/target"); + + createOperation("/source", "/target").apply(root); + } + + @Test(expected = IllegalArgumentException.class) + public void testMoveUnsuccessful() { + Root root = mock(Root.class); + doReturn(false).when(root).move("/source", "/target"); + + createOperation("/source", "/target").apply(root); + } + +} diff --git oak-remote/src/test/java/org/apache/jackrabbit/oak/remote/content/RemoveContentRemoteOperationTest.java oak-remote/src/test/java/org/apache/jackrabbit/oak/remote/content/RemoveContentRemoteOperationTest.java new file mode 100644 index 0000000..29b0dbb --- /dev/null +++ oak-remote/src/test/java/org/apache/jackrabbit/oak/remote/content/RemoveContentRemoteOperationTest.java @@ -0,0 +1,68 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jackrabbit.oak.remote.content; + +import org.apache.jackrabbit.oak.api.Root; +import org.apache.jackrabbit.oak.api.Tree; +import org.junit.Test; + +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; + +public class RemoveContentRemoteOperationTest { + + private RemoveContentRemoteOperation createOperation(String path) { + return new RemoveContentRemoteOperation(path); + } + + @Test + public void testRemove() { + Tree tree = mock(Tree.class); + doReturn(true).when(tree).exists(); + doReturn(true).when(tree).remove(); + + Root root = mock(Root.class); + doReturn(tree).when(root).getTree("/test"); + + createOperation("/test").apply(root); + } + + @Test(expected = IllegalStateException.class) + public void testRemoveWithNonExistingTree() { + Tree tree = mock(Tree.class); + doReturn(false).when(tree).exists(); + + Root root = mock(Root.class); + doReturn(tree).when(root).getTree("/test"); + + createOperation("/test").apply(root); + } + + @Test(expected = IllegalStateException.class) + public void testRemoveWithNonRemovableTree() { + Tree tree = mock(Tree.class); + doReturn(true).when(tree).exists(); + doReturn(false).when(tree).remove(); + + Root root = mock(Root.class); + doReturn(tree).when(root).getTree("/test"); + + createOperation("/test").apply(root); + } + +} diff --git oak-remote/src/test/java/org/apache/jackrabbit/oak/remote/content/SetContentRemoteOperationTest.java oak-remote/src/test/java/org/apache/jackrabbit/oak/remote/content/SetContentRemoteOperationTest.java new file mode 100644 index 0000000..86d21b6 --- /dev/null +++ oak-remote/src/test/java/org/apache/jackrabbit/oak/remote/content/SetContentRemoteOperationTest.java @@ -0,0 +1,526 @@ +/* + * 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.Predicate; +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; +import org.apache.jackrabbit.oak.remote.RemoteValue.Supplier; +import org.apache.jackrabbit.util.ISO8601; +import org.hamcrest.BaseMatcher; +import org.hamcrest.Description; +import org.hamcrest.Matcher; +import org.junit.Test; + +import java.io.InputStream; +import java.math.BigDecimal; +import java.util.Calendar; + +import static com.google.common.collect.Iterables.any; +import static java.util.Collections.singletonList; +import static org.apache.jackrabbit.oak.remote.RemoteValue.toBinary; +import static org.apache.jackrabbit.oak.remote.RemoteValue.toBinaryId; +import static org.apache.jackrabbit.oak.remote.RemoteValue.toBoolean; +import static org.apache.jackrabbit.oak.remote.RemoteValue.toDate; +import static org.apache.jackrabbit.oak.remote.RemoteValue.toDecimal; +import static org.apache.jackrabbit.oak.remote.RemoteValue.toDouble; +import static org.apache.jackrabbit.oak.remote.RemoteValue.toLong; +import static org.apache.jackrabbit.oak.remote.RemoteValue.toMultiBinary; +import static org.apache.jackrabbit.oak.remote.RemoteValue.toMultiBinaryId; +import static org.apache.jackrabbit.oak.remote.RemoteValue.toMultiBoolean; +import static org.apache.jackrabbit.oak.remote.RemoteValue.toMultiDate; +import static org.apache.jackrabbit.oak.remote.RemoteValue.toMultiDecimal; +import static org.apache.jackrabbit.oak.remote.RemoteValue.toMultiDouble; +import static org.apache.jackrabbit.oak.remote.RemoteValue.toMultiLong; +import static org.apache.jackrabbit.oak.remote.RemoteValue.toMultiName; +import static org.apache.jackrabbit.oak.remote.RemoteValue.toMultiPath; +import static org.apache.jackrabbit.oak.remote.RemoteValue.toMultiReference; +import static org.apache.jackrabbit.oak.remote.RemoteValue.toMultiText; +import static org.apache.jackrabbit.oak.remote.RemoteValue.toMultiUri; +import static org.apache.jackrabbit.oak.remote.RemoteValue.toMultiWeakReference; +import static org.apache.jackrabbit.oak.remote.RemoteValue.toName; +import static org.apache.jackrabbit.oak.remote.RemoteValue.toPath; +import static org.apache.jackrabbit.oak.remote.RemoteValue.toReference; +import static org.apache.jackrabbit.oak.remote.RemoteValue.toText; +import static org.apache.jackrabbit.oak.remote.RemoteValue.toUri; +import static org.apache.jackrabbit.oak.remote.RemoteValue.toWeakReference; +import static org.mockito.Matchers.argThat; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +public class SetContentRemoteOperationTest { + + private SetContentRemoteOperation createOperation(String path, String name, RemoteValue value) { + return new SetContentRemoteOperation(path, name, value); + } + + private Matcher> isIterableReferencing(final T value) { + return new BaseMatcher>() { + + @Override + public boolean matches(Object item) { + Iterable iterable = null; + + if (item instanceof Iterable) { + iterable = (Iterable) item; + } + + if (iterable == null) { + return false; + } + + return any(iterable, new Predicate() { + + @Override + public boolean apply(Object element) { + return element == value; + } + + }); + } + + @Override + public void describeTo(Description description) { + description.appendText("an iterable referencing ").appendValue(value); + } + + }; + } + + private Matcher> isIterableContaining(final T value) { + return new BaseMatcher>() { + + @Override + public boolean matches(Object item) { + Iterable iterable = null; + + if (item instanceof Iterable) { + iterable = (Iterable) item; + } + + if (iterable == null) { + return false; + } + + return any(iterable, new Predicate() { + + @Override + public boolean apply(Object element) { + if (element == null && value == null) { + return true; + } + + if (element != null && value != null) { + return element.equals(value); + } + + return false; + } + + }); + } + + @Override + public void describeTo(Description description) { + description.appendText("an iterable containing ").appendValue(value); + } + + }; + } + + @Test(expected = IllegalStateException.class) + public void testSetWithNonExistingTree() { + Tree tree = mock(Tree.class); + doReturn(false).when(tree).exists(); + + Root root = mock(Root.class); + doReturn(tree).when(root).getTree("/test"); + + createOperation("/test", "name", mock(RemoteValue.class)).apply(root); + } + + @Test + public void testSetBinaryProperty() throws Exception { + Blob blob = mock(Blob.class); + + InputStream stream = mock(InputStream.class); + + Supplier supplier = mock(Supplier.class); + doReturn(stream).when(supplier).get(); + + Tree tree = mock(Tree.class); + doReturn(true).when(tree).exists(); + + Root root = mock(Root.class); + doReturn(tree).when(root).getTree("/test"); + doReturn(blob).when(root).createBlob(stream); + + createOperation("/test", "name", toBinary(supplier)).apply(root); + + verify(tree).setProperty("name", blob, Type.BINARY); + } + + @Test + public void testSetMultiBinaryProperty() throws Exception { + Blob blob = mock(Blob.class); + + InputStream stream = mock(InputStream.class); + + Supplier supplier = mock(Supplier.class); + doReturn(stream).when(supplier).get(); + + Tree tree = mock(Tree.class); + doReturn(true).when(tree).exists(); + + Root root = mock(Root.class); + doReturn(tree).when(root).getTree("/test"); + doReturn(blob).when(root).createBlob(stream); + + createOperation("/test", "name", toMultiBinary(singletonList(supplier))).apply(root); + + verify(tree).setProperty(eq("name"), argThat(isIterableReferencing(blob)), eq(Type.BINARIES)); + } + + @Test + public void testSetBinaryIdProperty() { + Blob blob = mock(Blob.class); + + Tree tree = mock(Tree.class); + doReturn(true).when(tree).exists(); + + Root root = mock(Root.class); + doReturn(tree).when(root).getTree("/test"); + doReturn(blob).when(root).getBlob("id"); + + createOperation("/test", "name", toBinaryId("id")).apply(root); + + verify(tree).setProperty("name", blob, Type.BINARY); + } + + @Test + public void setMultiBinaryIdProperty() { + Blob blob = mock(Blob.class); + + Tree tree = mock(Tree.class); + doReturn(true).when(tree).exists(); + + Root root = mock(Root.class); + doReturn(tree).when(root).getTree("/test"); + doReturn(blob).when(root).getBlob("id"); + + createOperation("/test", "name", toMultiBinaryId(singletonList("id"))).apply(root); + + verify(tree).setProperty(eq("name"), argThat(isIterableReferencing(blob)), eq(Type.BINARIES)); + } + + @Test + public void testSetBooleanProperty() { + Tree tree = mock(Tree.class); + doReturn(true).when(tree).exists(); + + Root root = mock(Root.class); + doReturn(tree).when(root).getTree("/test"); + + createOperation("/test", "name", toBoolean(true)).apply(root); + + verify(tree).setProperty("name", true, Type.BOOLEAN); + } + + @Test + public void testSetMultiBooleanProperty() { + Tree tree = mock(Tree.class); + doReturn(true).when(tree).exists(); + + Root root = mock(Root.class); + doReturn(tree).when(root).getTree("/test"); + + createOperation("/test", "name", toMultiBoolean(singletonList(true))).apply(root); + + verify(tree).setProperty(eq("name"), argThat(isIterableContaining(true)), eq(Type.BOOLEANS)); + } + + @Test + public void testSetDateProperty() { + Calendar calendar = Calendar.getInstance(); + + Tree tree = mock(Tree.class); + doReturn(true).when(tree).exists(); + + Root root = mock(Root.class); + doReturn(tree).when(root).getTree("/test"); + + createOperation("/test", "name", toDate(calendar.getTimeInMillis())).apply(root); + + verify(tree).setProperty("name", ISO8601.format(calendar), Type.DATE); + } + + @Test + public void testSetMultiDateProperty() { + Calendar calendar = Calendar.getInstance(); + + Tree tree = mock(Tree.class); + doReturn(true).when(tree).exists(); + + Root root = mock(Root.class); + doReturn(tree).when(root).getTree("/test"); + + createOperation("/test", "name", toMultiDate(singletonList(calendar.getTimeInMillis()))).apply(root); + + verify(tree).setProperty(eq("name"), argThat(isIterableContaining(ISO8601.format(calendar))), eq(Type.DATES)); + } + + @Test + public void testSetDecimalProperty() { + Tree tree = mock(Tree.class); + doReturn(true).when(tree).exists(); + + Root root = mock(Root.class); + doReturn(tree).when(root).getTree("/test"); + + createOperation("/test", "name", toDecimal(BigDecimal.ONE)).apply(root); + + verify(tree).setProperty("name", BigDecimal.ONE, Type.DECIMAL); + } + + @Test + public void testSetMultiDecimalProperty() { + Tree tree = mock(Tree.class); + doReturn(true).when(tree).exists(); + + Root root = mock(Root.class); + doReturn(tree).when(root).getTree("/test"); + + createOperation("/test", "name", toMultiDecimal(singletonList(BigDecimal.ONE))).apply(root); + + verify(tree).setProperty(eq("name"), argThat(isIterableContaining(BigDecimal.ONE)), eq(Type.DECIMALS)); + } + + @Test + public void testSetDoubleProperty() { + Tree tree = mock(Tree.class); + doReturn(true).when(tree).exists(); + + Root root = mock(Root.class); + doReturn(tree).when(root).getTree("/test"); + + createOperation("/test", "name", toDouble(4.2)).apply(root); + + verify(tree).setProperty("name", 4.2, Type.DOUBLE); + } + + @Test + public void testSetMultiDoubleProperty() { + Tree tree = mock(Tree.class); + doReturn(true).when(tree).exists(); + + Root root = mock(Root.class); + doReturn(tree).when(root).getTree("/test"); + + createOperation("/test", "name", toMultiDouble(singletonList(4.2))).apply(root); + + verify(tree).setProperty(eq("name"), argThat(isIterableContaining(4.2)), eq(Type.DOUBLES)); + } + + @Test + public void testSetLongProperty() { + Tree tree = mock(Tree.class); + doReturn(true).when(tree).exists(); + + Root root = mock(Root.class); + doReturn(tree).when(root).getTree("/test"); + + createOperation("/test", "name", toLong(42L)).apply(root); + + verify(tree).setProperty("name", 42L, Type.LONG); + } + + @Test + public void testSetMultiLongProperty() { + Tree tree = mock(Tree.class); + doReturn(true).when(tree).exists(); + + Root root = mock(Root.class); + doReturn(tree).when(root).getTree("/test"); + + createOperation("/test", "name", toMultiLong(singletonList(42L))).apply(root); + + verify(tree).setProperty(eq("name"), argThat(isIterableContaining(42L)), eq(Type.LONGS)); + } + + @Test + public void testSetNameProperty() { + Tree tree = mock(Tree.class); + doReturn(true).when(tree).exists(); + + Root root = mock(Root.class); + doReturn(tree).when(root).getTree("/test"); + + createOperation("/test", "name", toName("value")).apply(root); + + verify(tree).setProperty("name", "value", Type.NAME); + } + + @Test + public void testSetMultiNameProperty() { + Tree tree = mock(Tree.class); + doReturn(true).when(tree).exists(); + + Root root = mock(Root.class); + doReturn(tree).when(root).getTree("/test"); + + createOperation("/test", "name", toMultiName(singletonList("value"))).apply(root); + + verify(tree).setProperty(eq("name"), argThat(isIterableContaining("value")), eq(Type.NAMES)); + } + + @Test + public void testSetPathProperty() { + Tree tree = mock(Tree.class); + doReturn(true).when(tree).exists(); + + Root root = mock(Root.class); + doReturn(tree).when(root).getTree("/test"); + + createOperation("/test", "name", toPath("value")).apply(root); + + verify(tree).setProperty("name", "value", Type.PATH); + } + + @Test + public void testSetMultiPathProperty() { + Tree tree = mock(Tree.class); + doReturn(true).when(tree).exists(); + + Root root = mock(Root.class); + doReturn(tree).when(root).getTree("/test"); + + createOperation("/test", "name", toMultiPath(singletonList("value"))).apply(root); + + verify(tree).setProperty(eq("name"), argThat(isIterableContaining("value")), eq(Type.PATHS)); + } + + @Test + public void testSetReferenceProperty() { + Tree tree = mock(Tree.class); + doReturn(true).when(tree).exists(); + + Root root = mock(Root.class); + doReturn(tree).when(root).getTree("/test"); + + createOperation("/test", "name", toReference("value")).apply(root); + + verify(tree).setProperty("name", "value", Type.REFERENCE); + } + + @Test + public void testSetMultiReferenceProperty() { + Tree tree = mock(Tree.class); + doReturn(true).when(tree).exists(); + + Root root = mock(Root.class); + doReturn(tree).when(root).getTree("/test"); + + createOperation("/test", "name", toMultiReference(singletonList("value"))).apply(root); + + verify(tree).setProperty(eq("name"), argThat(isIterableContaining("value")), eq(Type.REFERENCES)); + } + + @Test + public void testSetStringProperty() { + Tree tree = mock(Tree.class); + doReturn(true).when(tree).exists(); + + Root root = mock(Root.class); + doReturn(tree).when(root).getTree("/test"); + + createOperation("/test", "name", toText("value")).apply(root); + + verify(tree).setProperty("name", "value", Type.STRING); + } + + @Test + public void testSetMultiStringProperty() { + Tree tree = mock(Tree.class); + doReturn(true).when(tree).exists(); + + Root root = mock(Root.class); + doReturn(tree).when(root).getTree("/test"); + + createOperation("/test", "name", toMultiText(singletonList("value"))).apply(root); + + verify(tree).setProperty(eq("name"), argThat(isIterableContaining("value")), eq(Type.STRINGS)); + } + + @Test + public void testSetUriProperty() { + Tree tree = mock(Tree.class); + doReturn(true).when(tree).exists(); + + Root root = mock(Root.class); + doReturn(tree).when(root).getTree("/test"); + + createOperation("/test", "name", toUri("value")).apply(root); + + verify(tree).setProperty("name", "value", Type.URI); + } + + @Test + public void testSetMultiUriProperty() { + Tree tree = mock(Tree.class); + doReturn(true).when(tree).exists(); + + Root root = mock(Root.class); + doReturn(tree).when(root).getTree("/test"); + + createOperation("/test", "name", toMultiUri(singletonList("value"))).apply(root); + + verify(tree).setProperty(eq("name"), argThat(isIterableContaining("value")), eq(Type.URIS)); + } + + @Test + public void testSetWeakReferenceProperty() { + Tree tree = mock(Tree.class); + doReturn(true).when(tree).exists(); + + Root root = mock(Root.class); + doReturn(tree).when(root).getTree("/test"); + + createOperation("/test", "name", toWeakReference("value")).apply(root); + + verify(tree).setProperty("name", "value", Type.WEAKREFERENCE); + } + + @Test + public void testSetMultiWeakReferenceProperty() { + Tree tree = mock(Tree.class); + doReturn(true).when(tree).exists(); + + Root root = mock(Root.class); + doReturn(tree).when(root).getTree("/test"); + + createOperation("/test", "name", toMultiWeakReference(singletonList("value"))).apply(root); + + verify(tree).setProperty(eq("name"), argThat(isIterableContaining("value")), eq(Type.WEAKREFERENCES)); + } + +} diff --git oak-remote/src/test/java/org/apache/jackrabbit/oak/remote/content/UnsetContentRemoteOperationTest.java oak-remote/src/test/java/org/apache/jackrabbit/oak/remote/content/UnsetContentRemoteOperationTest.java new file mode 100644 index 0000000..694a058 --- /dev/null +++ oak-remote/src/test/java/org/apache/jackrabbit/oak/remote/content/UnsetContentRemoteOperationTest.java @@ -0,0 +1,71 @@ +/* + * 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.junit.Test; + +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +public class UnsetContentRemoteOperationTest { + + private UnsetContentRemoteOperation createOperation(String path, String name) { + return new UnsetContentRemoteOperation(path, name); + } + + @Test + public void testUnset() { + Tree tree = mock(Tree.class); + doReturn(true).when(tree).exists(); + doReturn(true).when(tree).hasProperty("name"); + + Root root = mock(Root.class); + doReturn(tree).when(root).getTree("/test"); + + createOperation("/test", "name").apply(root); + + verify(tree).removeProperty("name"); + } + + @Test(expected = IllegalStateException.class) + public void testUnsetWithNonExistingTree() { + Tree tree = mock(Tree.class); + doReturn(false).when(tree).exists(); + + Root root = mock(Root.class); + doReturn(tree).when(root).getTree("/test"); + + createOperation("/test", "name").apply(root); + } + + @Test(expected = IllegalStateException.class) + public void testUnsetWithNonExistingProperty() { + Tree tree = mock(Tree.class); + doReturn(true).when(tree).exists(); + doReturn(false).when(tree).hasProperty("name"); + + Root root = mock(Root.class); + doReturn(tree).when(root).getTree("/test"); + + createOperation("/test", "name").apply(root); + } + +} diff --git oak-remote/src/test/java/org/apache/jackrabbit/oak/remote/filter/FilterTest.java oak-remote/src/test/java/org/apache/jackrabbit/oak/remote/filter/FilterTest.java new file mode 100644 index 0000000..34fbd74 --- /dev/null +++ oak-remote/src/test/java/org/apache/jackrabbit/oak/remote/filter/FilterTest.java @@ -0,0 +1,57 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jackrabbit.oak.remote.filter; + +import org.junit.Test; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +public class FilterTest { + + @Test + public void testExact() { + Filter filter = new Filter("name"); + + assertTrue(filter.matches("name")); + assertFalse(filter.matches("nam")); + assertFalse(filter.matches("named")); + } + + @Test + public void testWildcard() { + Filter filter = new Filter("na*e"); + + assertTrue(filter.matches("nae")); + assertTrue(filter.matches("name")); + assertTrue(filter.matches("namme")); + assertFalse(filter.matches("nam")); + assertFalse(filter.matches("named")); + } + + @Test + public void testEscapedWildcard() { + Filter filter = new Filter("na\\*e"); + + assertTrue(filter.matches("na*e")); + assertFalse(filter.matches("nae")); + assertFalse(filter.matches("name")); + assertFalse(filter.matches("namme")); + } + +} diff --git oak-remote/src/test/java/org/apache/jackrabbit/oak/remote/filter/FiltersTest.java oak-remote/src/test/java/org/apache/jackrabbit/oak/remote/filter/FiltersTest.java new file mode 100644 index 0000000..1a75d1d --- /dev/null +++ oak-remote/src/test/java/org/apache/jackrabbit/oak/remote/filter/FiltersTest.java @@ -0,0 +1,86 @@ +/* + * 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 com.google.common.collect.Sets; +import org.junit.Test; + +import java.util.HashSet; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +public class FiltersTest { + + @Test(expected = IllegalArgumentException.class) + public void testNullFilters() { + new Filters(null); + } + + @Test(expected = IllegalArgumentException.class) + public void testNullFilter() { + new Filters(Sets.newHashSet((String) null)); + } + + @Test(expected = IllegalArgumentException.class) + public void testEmptyIncludeFilter() { + new Filters(Sets.newHashSet("")); + } + + @Test(expected = IllegalArgumentException.class) + public void testEmptyExcludeFilter() { + new Filters(Sets.newHashSet("-")); + } + + @Test + public void testEmptyFilters() { + Filters filters = new Filters(new HashSet()); + + assertTrue(filters.matches("foo")); + assertTrue(filters.matches("bar")); + assertTrue(filters.matches("baz")); + } + + @Test + public void testIncludeFilter() { + Filters filters = new Filters(Sets.newHashSet("ba*")); + + assertFalse(filters.matches("foo")); + assertTrue(filters.matches("bar")); + assertTrue(filters.matches("baz")); + } + + @Test + public void testExcludeFilter() { + Filters filters = new Filters(Sets.newHashSet("-foo")); + + assertFalse(filters.matches("foo")); + assertTrue(filters.matches("bar")); + assertTrue(filters.matches("baz")); + } + + @Test + public void testIncludeExcludeFilters() { + Filters filters = new Filters(Sets.newHashSet("ba*", "-baz")); + + assertFalse(filters.matches("foo")); + assertTrue(filters.matches("bar")); + assertFalse(filters.matches("baz")); + } + +} diff --git oak-remote/src/test/java/org/apache/jackrabbit/oak/remote/http/handler/AuthenticationWrapperHandlerTest.java oak-remote/src/test/java/org/apache/jackrabbit/oak/remote/http/handler/AuthenticationWrapperHandlerTest.java new file mode 100644 index 0000000..408c5df --- /dev/null +++ oak-remote/src/test/java/org/apache/jackrabbit/oak/remote/http/handler/AuthenticationWrapperHandlerTest.java @@ -0,0 +1,160 @@ +/* + * 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.RemoteRepository; +import org.apache.jackrabbit.oak.remote.RemoteSession; +import org.junit.Test; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; + +public class AuthenticationWrapperHandlerTest { + + private Handler createHandler(Handler authenticated, Handler notAuthenticated) { + return new AuthenticationWrapperHandler(authenticated, notAuthenticated); + } + + @Test + public void testExistingSession() throws Exception { + RemoteSession session = mock(RemoteSession.class); + + HttpServletRequest request = mock(HttpServletRequest.class); + doReturn(session).when(request).getAttribute("session"); + + HttpServletResponse response = mock(HttpServletResponse.class); + + Handler authenticated = mock(Handler.class); + + Handler notAuthenticated = mock(Handler.class); + + createHandler(authenticated, notAuthenticated).handle(request, response); + + verify(authenticated).handle(request, response); + verify(notAuthenticated, never()).handle(request, response); + } + + @Test + public void testRepositoryNotProvided() throws Exception { + Handler authenticated = mock(Handler.class); + + Handler notAuthenticated = mock(Handler.class); + + HttpServletRequest request = mock(HttpServletRequest.class); + + HttpServletResponse response = mock(HttpServletResponse.class); + + createHandler(authenticated, notAuthenticated).handle(request, response); + + verify(authenticated, never()).handle(request, response); + verify(notAuthenticated).handle(request, response); + } + + @Test + public void testAuthorizationHeaderNotProvided() throws Exception { + Handler authenticated = mock(Handler.class); + + Handler notAuthenticated = mock(Handler.class); + + RemoteRepository repository = mock(RemoteRepository.class); + + HttpServletRequest request = mock(HttpServletRequest.class); + doReturn(repository).when(request).getAttribute("repository"); + + HttpServletResponse response = mock(HttpServletResponse.class); + + createHandler(authenticated, notAuthenticated).handle(request, response); + + verify(authenticated, never()).handle(request, response); + verify(notAuthenticated).handle(request, response); + } + + @Test + public void testAuthorizationWithNonBasicScheme() throws Exception { + Handler authenticated = mock(Handler.class); + + Handler notAuthenticated = mock(Handler.class); + + RemoteRepository repository = mock(RemoteRepository.class); + + HttpServletRequest request = mock(HttpServletRequest.class); + doReturn(repository).when(request).getAttribute("repository"); + doReturn("Whatever").when(request).getHeader("Authorization"); + + HttpServletResponse response = mock(HttpServletResponse.class); + + createHandler(authenticated, notAuthenticated).handle(request, response); + + verify(authenticated, never()).handle(request, response); + verify(notAuthenticated).handle(request, response); + } + + @Test + public void testBasicAuthorizationWithInvalidToken() throws Exception { + Handler authenticated = mock(Handler.class); + + Handler notAuthenticated = mock(Handler.class); + + RemoteRepository repository = mock(RemoteRepository.class); + + HttpServletRequest request = mock(HttpServletRequest.class); + doReturn(repository).when(request).getAttribute("repository"); + doReturn("Basic RG9uJ3QgeW91IGhhdmUgYW55dGhpbmcgYmV0dGVyIHRvIGRvPw==").when(request).getHeader("Authorization"); + + HttpServletResponse response = mock(HttpServletResponse.class); + + createHandler(authenticated, notAuthenticated).handle(request, response); + + verify(authenticated, never()).handle(request, response); + verify(notAuthenticated).handle(request, response); + } + + @Test + public void testBasicAuthorizationWithValidToken() throws Exception { + Handler authenticated = mock(Handler.class); + + Handler notAuthenticated = mock(Handler.class); + + RemoteSession session = mock(RemoteSession.class); + + RemoteCredentials credentials = mock(RemoteCredentials.class); + + RemoteRepository repository = mock(RemoteRepository.class); + doReturn(credentials).when(repository).createBasicCredentials("admin", "admin".toCharArray()); + doReturn(session).when(repository).login(credentials); + + HttpServletRequest request = mock(HttpServletRequest.class); + doReturn(repository).when(request).getAttribute("repository"); + doReturn("Basic YWRtaW46YWRtaW4=").when(request).getHeader("Authorization"); + + HttpServletResponse response = mock(HttpServletResponse.class); + + createHandler(authenticated, notAuthenticated).handle(request, response); + + verify(request).setAttribute("session", session); + verify(authenticated).handle(request, response); + verify(notAuthenticated, never()).handle(request, response); + } + +} diff --git oak-remote/src/test/java/org/apache/jackrabbit/oak/remote/http/matcher/AllMatcherTest.java oak-remote/src/test/java/org/apache/jackrabbit/oak/remote/http/matcher/AllMatcherTest.java new file mode 100644 index 0000000..3a3e535 --- /dev/null +++ oak-remote/src/test/java/org/apache/jackrabbit/oak/remote/http/matcher/AllMatcherTest.java @@ -0,0 +1,68 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jackrabbit.oak.remote.http.matcher; + +import org.junit.Test; + +import javax.servlet.http.HttpServletRequest; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; + +public class AllMatcherTest { + + boolean matches(boolean... results) { + Matcher[] matchers = new Matcher[results.length]; + + for (int i = 0; i < results.length; i++) { + matchers[i] = mock(Matcher.class); + } + + HttpServletRequest request = mock(HttpServletRequest.class); + + for (int i = 0; i < results.length; i++) { + doReturn(results[i]).when(matchers[i]).match(request); + } + + return new AllMatcher(matchers).match(request); + + } + + @Test + public void testNoMatchers() { + assertTrue(matches()); + } + + @Test + public void testSingleMatcher() { + assertFalse(matches(false)); + assertTrue(matches(true)); + } + + @Test + public void testMatchers() { + assertFalse(matches(false, false)); + assertFalse(matches(false, true)); + assertFalse(matches(true, false)); + assertTrue(matches(true, true)); + } + + +} diff --git oak-remote/src/test/java/org/apache/jackrabbit/oak/remote/http/matcher/MatchersTest.java oak-remote/src/test/java/org/apache/jackrabbit/oak/remote/http/matcher/MatchersTest.java new file mode 100644 index 0000000..16037a5 --- /dev/null +++ oak-remote/src/test/java/org/apache/jackrabbit/oak/remote/http/matcher/MatchersTest.java @@ -0,0 +1,90 @@ +/* + * 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.matcher; + +import org.junit.Test; + +import static org.apache.jackrabbit.oak.remote.http.matcher.Matchers.matchesAll; +import static org.apache.jackrabbit.oak.remote.http.matcher.Matchers.matchesMethod; +import static org.apache.jackrabbit.oak.remote.http.matcher.Matchers.matchesPath; +import static org.apache.jackrabbit.oak.remote.http.matcher.Matchers.matchesRequest; +import static org.junit.Assert.assertNotNull; + +public class MatchersTest { + + @Test + public void testCreateMethodMatcher() { + assertNotNull(matchesMethod("GET")); + } + + @Test(expected = IllegalArgumentException.class) + public void testCreateMethodMatcherWithNullMethod() { + matchesMethod(null); + } + + @Test + public void testCreatePathMatcher() { + assertNotNull(matchesPath("/test")); + } + + @Test(expected = IllegalArgumentException.class) + public void testCreatePathMatcherWithNullPattern() { + matchesPath(null); + } + + @Test(expected = IllegalArgumentException.class) + public void testCreatePathMatcherWithInvalidPattern() { + matchesPath("/test("); + } + + @Test + public void testCreateAllMatcher() { + assertNotNull(matchesAll()); + } + + @Test(expected = IllegalArgumentException.class) + public void testCreateAllMatcherWithNullMatchers() { + matchesAll(null); + } + + @Test(expected = IllegalArgumentException.class) + public void testCreateAllMatchersWithNullMatcher() { + matchesAll(null, null); + } + + @Test + public void testCreateRequestMatcher() { + assertNotNull(matchesRequest("GET", "/test")); + } + + @Test(expected = IllegalArgumentException.class) + public void testCreateRequestMatcherWithNullMethod() { + matchesRequest(null, "/test"); + } + + @Test(expected = IllegalArgumentException.class) + public void testCreateRequestMatcherWithNullPattern() { + matchesRequest("GET", null); + } + + @Test(expected = IllegalArgumentException.class) + public void testCreateRequestMatcherWithInvalidPattern() { + matchesRequest("GET", "/test("); + } + +} diff --git oak-remote/src/test/java/org/apache/jackrabbit/oak/remote/http/matcher/MethodMatcherTest.java oak-remote/src/test/java/org/apache/jackrabbit/oak/remote/http/matcher/MethodMatcherTest.java new file mode 100644 index 0000000..8ae376d --- /dev/null +++ oak-remote/src/test/java/org/apache/jackrabbit/oak/remote/http/matcher/MethodMatcherTest.java @@ -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.http.matcher; + +import org.junit.Test; + +import javax.servlet.http.HttpServletRequest; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; + +public class MethodMatcherTest { + + private boolean matches(String requestMethod, String matcherMethod) { + HttpServletRequest request = mock(HttpServletRequest.class); + doReturn(requestMethod).when(request).getMethod(); + return new MethodMatcher(matcherMethod).match(request); + } + + @Test + public void testMatch() { + assertTrue(matches("GET", "GET")); + } + + @Test + public void testNoMatch() { + assertFalse(matches("GET", "PUT")); + } + + @Test + public void testCaseInsensitiveMatch() { + assertTrue(matches("GET", "get")); + } + +} diff --git oak-remote/src/test/java/org/apache/jackrabbit/oak/remote/http/matcher/PathMatcherTest.java oak-remote/src/test/java/org/apache/jackrabbit/oak/remote/http/matcher/PathMatcherTest.java new file mode 100644 index 0000000..b569009 --- /dev/null +++ oak-remote/src/test/java/org/apache/jackrabbit/oak/remote/http/matcher/PathMatcherTest.java @@ -0,0 +1,58 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jackrabbit.oak.remote.http.matcher; + +import org.junit.Test; + +import javax.servlet.http.HttpServletRequest; +import java.util.regex.Pattern; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; + +public class PathMatcherTest { + + private boolean matches(String requestPath, String matcherPattern) { + HttpServletRequest request = mock(HttpServletRequest.class); + doReturn(requestPath).when(request).getPathInfo(); + return new PathMatcher(Pattern.compile(matcherPattern)).match(request); + } + + @Test + public void testMatch() { + assertTrue(matches("/test", "/test")); + } + + @Test + public void testCaseSensitiveMatch() { + assertFalse(matches("/test", "/Test")); + } + + @Test + public void testPatternMatch() { + assertTrue(matches("/test/something", "/test/.*")); + } + + @Test + public void testNoPathInfo() { + assertFalse(matches(null, "/test")); + } + +} diff --git oak-run/pom.xml oak-run/pom.xml index 8469e77..b7eb83c 100644 --- oak-run/pom.xml +++ oak-run/pom.xml @@ -245,6 +245,11 @@ oak-http ${project.version} + + org.apache.jackrabbit + oak-remote + ${project.version} + org.apache.jackrabbit oak-upgrade diff --git oak-run/src/main/java/org/apache/jackrabbit/oak/run/Main.java oak-run/src/main/java/org/apache/jackrabbit/oak/run/Main.java index 8aae0dc..8d88fe3 100644 --- oak-run/src/main/java/org/apache/jackrabbit/oak/run/Main.java +++ oak-run/src/main/java/org/apache/jackrabbit/oak/run/Main.java @@ -101,6 +101,8 @@ import org.apache.jackrabbit.oak.plugins.segment.compaction.CompactionStrategy.C import org.apache.jackrabbit.oak.plugins.segment.file.FileStore; import org.apache.jackrabbit.oak.plugins.segment.standby.client.StandbyClient; import org.apache.jackrabbit.oak.plugins.segment.standby.server.StandbyServer; +import org.apache.jackrabbit.oak.remote.content.ContentRemoteRepository; +import org.apache.jackrabbit.oak.remote.http.RemoteServlet; import org.apache.jackrabbit.oak.scalability.ScalabilityRunner; import org.apache.jackrabbit.oak.spi.state.NodeState; import org.apache.jackrabbit.oak.spi.state.NodeStore; @@ -1153,6 +1155,9 @@ public class Main { ServletHolder holder = new ServletHolder(new OakServlet(repository)); context.addServlet(holder, path + "/*"); + ServletHolder remoteServlet = new ServletHolder(new RemoteServlet(new ContentRemoteRepository(repository))); + context.addServlet(remoteServlet, path + "/api/*"); + // 2 - Webdav Server on JCR repository final Repository jcrRepository = jcr.createRepository(); @SuppressWarnings("serial") diff --git pom.xml pom.xml index 9ec001f..dfdd973 100644 --- pom.xml +++ pom.xml @@ -56,6 +56,7 @@ oak-it oak-pojosr oak-authorization-cug + oak-remote