├── settings.gradle ├── jackalope.png ├── jackalopereverse.png ├── gradle ├── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── bintray.gradle └── ide.gradle ├── .gitignore ├── gradle.properties ├── .travis.yml ├── src ├── main │ └── java │ │ └── com │ │ └── twcable │ │ └── jackalope │ │ ├── Builder.java │ │ ├── NodeBuilder.java │ │ ├── ItemBuilder.java │ │ ├── RepositoryBuilder.java │ │ ├── ResourceBuilder.java │ │ ├── PropertyBuilder.java │ │ ├── impl │ │ ├── jcr │ │ │ ├── NodeIteratorImpl.java │ │ │ ├── PropertyIteratorImpl.java │ │ │ ├── QueryResultImpl.java │ │ │ ├── JcrUtils.java │ │ │ ├── RangeIteratorImpl.java │ │ │ ├── ObservationManagerImpl.java │ │ │ ├── QueryManagerImpl.java │ │ │ ├── BinaryImpl.java │ │ │ ├── NodeDefinitionImpl.java │ │ │ ├── QueryImpl.java │ │ │ ├── RepositoryImpl.java │ │ │ ├── ValueFactoryImpl.java │ │ │ ├── NodeTypeImpl.java │ │ │ ├── ItemImpl.java │ │ │ ├── ValueImpl.java │ │ │ └── WorkspaceImpl.java │ │ ├── sling │ │ │ ├── SlingRepositoryImpl.java │ │ │ ├── SlingRepositoryException.java │ │ │ ├── SimpleResourceResolverFactory.java │ │ │ ├── PropertyResourceImpl.java │ │ │ ├── ItemResourceImpl.java │ │ │ ├── NodeResourceImpl.java │ │ │ └── ResourceResolverImpl.java │ │ ├── common │ │ │ ├── Values.java │ │ │ └── Paths.java │ │ └── cq │ │ │ ├── RenditionImpl.java │ │ │ ├── PageIteratorImpl.java │ │ │ ├── AssetImpl.java │ │ │ └── PageImpl.java │ │ ├── JcrConstants.java │ │ ├── JCRQueryBuilder.java │ │ └── JCRBuilder.java └── test │ └── groovy │ └── com │ └── twcable │ └── jackalope │ ├── impl │ ├── jcr │ │ ├── NodeTypeImplSpec.groovy │ │ ├── RepositoryImplSpec.groovy │ │ ├── PropertyIteratorImplSpec.groovy │ │ ├── NodeIteratorImpSpec.groovy │ │ ├── ValueFactoryImplSpec.groovy │ │ ├── RangeIteratorImplSpec.groovy │ │ ├── ValueImplSpec.groovy │ │ ├── SessionImplSpec.groovy │ │ ├── PropertyImplSpec.groovy │ │ └── NodeImplSpec.groovy │ ├── common │ │ ├── ValuesSpec.groovy │ │ └── PathsSpec.groovy │ ├── JCRQueryBuilderSpec.groovy │ ├── cq │ │ ├── AssetSpec.groovy │ │ └── PageImplSpec.groovy │ └── sling │ │ ├── PropertyResourceImplSpec.groovy │ │ ├── ResourceResolverImplSpec.groovy │ │ └── NodeResourceImplSpec.groovy │ └── JCRBuilderSpec.groovy ├── RELEASE_NOTES.md ├── CONTRIBUTING.md ├── docs └── RELEASING.adoc ├── gradlew.bat ├── gradlew └── README.md /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'jackalope' 2 | -------------------------------------------------------------------------------- /jackalope.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TWCable/jackalope/HEAD/jackalope.png -------------------------------------------------------------------------------- /jackalopereverse.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TWCable/jackalope/HEAD/jackalopereverse.png -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TWCable/jackalope/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | .gradle/ 3 | .idea/ 4 | *.ipr 5 | *.iml 6 | *.iws 7 | out/ 8 | atlassian-ide-plugin.xml 9 | *.jar 10 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Tue Feb 24 15:51:41 MST 2015 2 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.3-bin.zip 3 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # suppress inspection "UnusedProperty" for whole file 2 | description = Provides a convenient way of stubbing out Sling and the JCR 3 | group = com.twcable.jackalope 4 | version = 3.0.2 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | 3 | jdk: 4 | - oraclejdk7 5 | - oraclejdk8 6 | - openjdk7 7 | 8 | # Allow running on a container instead of a traditional VM 9 | sudo: false 10 | 11 | 12 | # show the logging of tests, and there's no need to run the daemon on a single-use container 13 | script: ./gradlew check -iS --no-daemon 14 | -------------------------------------------------------------------------------- /src/main/java/com/twcable/jackalope/Builder.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Time Warner Cable, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.twcable.jackalope; 18 | 19 | /** 20 | * Simple "marker interface" for the various builders. 21 | */ 22 | public interface Builder { 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/com/twcable/jackalope/NodeBuilder.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Time Warner Cable, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.twcable.jackalope; 18 | 19 | import javax.jcr.Node; 20 | 21 | public interface NodeBuilder extends ItemBuilder { 22 | Node build(); 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/com/twcable/jackalope/ItemBuilder.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Time Warner Cable, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.twcable.jackalope; 18 | 19 | import javax.jcr.Node; 20 | 21 | public interface ItemBuilder extends Builder { 22 | public T build(Node parent); 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/com/twcable/jackalope/RepositoryBuilder.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Time Warner Cable, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.twcable.jackalope; 18 | 19 | import org.apache.sling.jcr.api.SlingRepository; 20 | 21 | public interface RepositoryBuilder { 22 | public SlingRepository build(); 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/com/twcable/jackalope/ResourceBuilder.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Time Warner Cable, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.twcable.jackalope; 18 | 19 | import org.apache.sling.api.resource.Resource; 20 | 21 | public interface ResourceBuilder extends Builder { 22 | public Resource build(); 23 | } 24 | -------------------------------------------------------------------------------- /RELEASE_NOTES.md: -------------------------------------------------------------------------------- 1 | # RELEASE NOTES 2 | 3 | ## 3.0.2 4 | 5 | Bug fixes: 6 | 7 | * Preserve the order of nodes in the repository to match the order they were created, so that tests can count on node 8 | order when using functions such as page.listChildren() or node.getNodes() 9 | * Fix bug where "deep" parameter to page.listChildren() is being ignored 10 | * Fix bug page.listChildren() hits NPE due to having a null filter 11 | * Fix bug where PageImpl.isHideInNav() throws NPE if hideInNav property is not set 12 | * Fix bug where PageImpl.isValid() returns false for valid pages, incorrectly returning true for pages that have a 13 | future onTime instead of a past onTime and future offTime 14 | 15 | ## 3.0.1 16 | 17 | Implemented ResourceResolverImpl delete(..) and create(..) methods 18 | 19 | ## 3.0.0 20 | 21 | Updated to support AEM 6.0, from AEM 5.6.1 22 | 23 | ## 2.0.0 24 | 25 | Initial public release 26 | -------------------------------------------------------------------------------- /src/main/java/com/twcable/jackalope/PropertyBuilder.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Time Warner Cable, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.twcable.jackalope; 18 | 19 | import javax.jcr.Node; 20 | import javax.jcr.Property; 21 | 22 | public interface PropertyBuilder extends ItemBuilder { 23 | public Property build(Node parent); 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/com/twcable/jackalope/impl/jcr/NodeIteratorImpl.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Time Warner Cable, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.twcable.jackalope.impl.jcr; 18 | 19 | import javax.jcr.Node; 20 | import javax.jcr.NodeIterator; 21 | import java.util.Collection; 22 | 23 | class NodeIteratorImpl extends RangeIteratorImpl implements NodeIterator { 24 | NodeIteratorImpl(Collection nodes) { 25 | super(nodes); 26 | } 27 | 28 | 29 | @Override 30 | public Node nextNode() { 31 | return (Node)next(); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/com/twcable/jackalope/impl/jcr/PropertyIteratorImpl.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Time Warner Cable, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.twcable.jackalope.impl.jcr; 18 | 19 | import javax.jcr.Property; 20 | import javax.jcr.PropertyIterator; 21 | import java.util.Collection; 22 | 23 | class PropertyIteratorImpl extends RangeIteratorImpl implements PropertyIterator { 24 | PropertyIteratorImpl(Collection properties) { 25 | super(properties); 26 | } 27 | 28 | 29 | @Override 30 | public Property nextProperty() { 31 | return (Property)next(); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to CQ Gradle Plugins 2 | 3 | If you would like to contribute code you can do so through GitHub by forking the repository and sending a pull request. 4 | 5 | When submitting code, please make every effort to follow existing conventions and style in order to keep the code as readable as possible. 6 | 7 | ## License 8 | 9 | By contributing your code, you agree to license your contribution under the terms of the APLv2: [LICENSE](LICENSE.txt) 10 | 11 | All files are released with the Apache 2.0 license. 12 | 13 | If you are adding a new file it should have a header like this: 14 | 15 | ``` 16 | /** 17 | * Copyright 2015 Time Warner Cable, Inc. 18 | * 19 | * Licensed under the Apache License, Version 2.0 (the "License"); 20 | * you may not use this file except in compliance with the License. 21 | * You may obtain a copy of the License at 22 | * 23 | * http://www.apache.org/licenses/LICENSE-2.0 24 | * 25 | * Unless required by applicable law or agreed to in writing, software 26 | * distributed under the License is distributed on an "AS IS" BASIS, 27 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 28 | * See the License for the specific language governing permissions and 29 | * limitations under the License. 30 | */ 31 | ``` 32 | -------------------------------------------------------------------------------- /src/test/groovy/com/twcable/jackalope/impl/jcr/NodeTypeImplSpec.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Time Warner Cable, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.twcable.jackalope.impl.jcr 18 | 19 | import spock.lang.Specification 20 | import spock.lang.Subject 21 | 22 | @Subject(NodeTypeImpl) 23 | class NodeTypeImplSpec extends Specification { 24 | 25 | def "NodeType has a name"() { 26 | expect: 27 | new NodeTypeImpl("name").getName() == "name" 28 | } 29 | 30 | 31 | def "NodeType is tested by name"() { 32 | expect: 33 | new NodeTypeImpl("nodetype").isNodeType("nodetype") 34 | !new NodeTypeImpl("nodetype").isNodeType("nodetypex") 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/com/twcable/jackalope/JcrConstants.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Time Warner Cable, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.twcable.jackalope; 18 | 19 | /** 20 | * Constants defined in the JCR spec ala org.apache.jackrabbit.JcrConstants 21 | */ 22 | @SuppressWarnings("UnusedDeclaration") 23 | public final class JcrConstants { 24 | private JcrConstants() { 25 | } 26 | 27 | 28 | public static final String NT_FILE = "nt:file"; 29 | public static final String NT_UNSTRUCTURED = "nt:unstructured"; 30 | public static final String CQ_PAGE = "cq:Page"; 31 | public static final String CQ_PAGE_CONTENT = "cq:PageContent"; 32 | public static final String CQ_TEMPLATE = "cq:template"; 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/com/twcable/jackalope/impl/sling/SlingRepositoryImpl.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Time Warner Cable, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.twcable.jackalope.impl.sling; 18 | 19 | import com.twcable.jackalope.impl.jcr.RepositoryImpl; 20 | import org.apache.sling.jcr.api.SlingRepository; 21 | 22 | import javax.jcr.RepositoryException; 23 | import javax.jcr.Session; 24 | 25 | public class SlingRepositoryImpl extends RepositoryImpl implements SlingRepository { 26 | 27 | @Override 28 | public String getDefaultWorkspace() { 29 | return null; 30 | } 31 | 32 | 33 | @Override 34 | public Session loginAdministrative(String workspace) throws RepositoryException { 35 | return login(); 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /src/test/groovy/com/twcable/jackalope/impl/jcr/RepositoryImplSpec.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Time Warner Cable, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.twcable.jackalope.impl.jcr 18 | 19 | import spock.lang.Specification 20 | import spock.lang.Subject 21 | 22 | import javax.jcr.Credentials 23 | 24 | @Subject(RepositoryImpl) 25 | class RepositoryImplSpec extends Specification { 26 | 27 | def "Our repository has a single shared session for testing"() { 28 | def repository = new RepositoryImpl(); 29 | 30 | expect: 31 | repository.login() == repository.login("test") 32 | repository.login() == repository.login(new Credentials() {}) 33 | repository.login() == repository.login(new Credentials() {}, "test") 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/com/twcable/jackalope/impl/sling/SlingRepositoryException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Time Warner Cable, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.twcable.jackalope.impl.sling; 18 | 19 | /** 20 | * A RuntimeException that wraps checked exceptions 21 | */ 22 | @SuppressWarnings("UnusedDeclaration") 23 | public class SlingRepositoryException extends RuntimeException { 24 | public SlingRepositoryException() { 25 | super(); 26 | } 27 | 28 | 29 | public SlingRepositoryException(String message) { 30 | super(message); 31 | } 32 | 33 | 34 | public SlingRepositoryException(String message, Throwable cause) { 35 | super(message, cause); 36 | } 37 | 38 | 39 | public SlingRepositoryException(Throwable cause) { 40 | super(cause); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/test/groovy/com/twcable/jackalope/impl/common/ValuesSpec.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Time Warner Cable, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.twcable.jackalope.impl.common 18 | 19 | import com.twcable.jackalope.impl.jcr.ValueImpl 20 | import spock.lang.Specification 21 | import spock.lang.Subject 22 | 23 | import javax.jcr.Value 24 | 25 | @Subject(Values) 26 | class ValuesSpec extends Specification { 27 | def "Convert values to strings"() { 28 | expect: 29 | Values.convertValuesToStrings([new ValueImpl("a"), new ValueImpl("b")] as Value[]) == ["a", "b"] as String[] 30 | } 31 | 32 | 33 | def "Convert strings to values"() { 34 | expect: 35 | Values.convertStringsToValues(["a", "b"] as String[]).collect { it.getString() } == ["a", "b"] as String[] 36 | } 37 | 38 | 39 | def "Convert objects to values"() { 40 | expect: 41 | Values.convertObjectsToValues(["a", "b"] as Object[]).collect { it.getString() } == ["a", "b"] as String[] 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/test/groovy/com/twcable/jackalope/impl/jcr/PropertyIteratorImplSpec.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Time Warner Cable, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.twcable.jackalope.impl.jcr 18 | 19 | import com.google.common.collect.Lists 20 | import spock.lang.Specification 21 | import spock.lang.Subject 22 | 23 | @Subject(PropertyIteratorImpl) 24 | class PropertyIteratorImplSpec extends Specification { 25 | 26 | def "A PropertyIterator iterates over a list of Properties"() { 27 | def session = new SessionImpl() 28 | 29 | when: 30 | List actual = Lists.newArrayList(new PropertyIteratorImpl([ 31 | new PropertyImpl(session, "first", new ValueImpl("a")), 32 | new PropertyImpl(session, "second", new ValueImpl("b")), 33 | new PropertyImpl(session, "third", new ValueImpl("c"))])) 34 | 35 | then: 36 | actual.find { it.name == "first" }.string == "a" 37 | actual.find { it.name == "second" }.string == "b" 38 | actual.find { it.name == "third" }.string == "c" 39 | actual.size() == 3 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /src/test/groovy/com/twcable/jackalope/impl/jcr/NodeIteratorImpSpec.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Time Warner Cable, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.twcable.jackalope.impl.jcr 18 | 19 | import com.google.common.collect.Lists 20 | import spock.lang.Specification 21 | import spock.lang.Subject 22 | 23 | @Subject(NodeImpl) 24 | class NodeIteratorImpSpec extends Specification { 25 | 26 | def "A Node iterates over a list of Properties"() { 27 | def session = new SessionImpl() 28 | def node1 = new NodeImpl(session, "first") 29 | node1.setProperty("prop", "a") 30 | def node2 = new NodeImpl(session, "second") 31 | node2.setProperty("prop", "b") 32 | def node3 = new NodeImpl(session, "third") 33 | node3.setProperty("prop", "c") 34 | 35 | when: 36 | List actual = Lists.newArrayList(new NodeIteratorImpl([node1, node2, node3])) 37 | 38 | then: 39 | actual.find { it.name == "first" }.getProperty("prop").string == "a" 40 | actual.find { it.name == "second" }.getProperty("prop").string == "b" 41 | actual.find { it.name == "third" }.getProperty("prop").string == "c" 42 | actual.size() == 3 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/com/twcable/jackalope/impl/jcr/QueryResultImpl.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Time Warner Cable, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.twcable.jackalope.impl.jcr; 18 | 19 | import javax.jcr.Node; 20 | import javax.jcr.NodeIterator; 21 | import javax.jcr.RepositoryException; 22 | import javax.jcr.query.QueryResult; 23 | import javax.jcr.query.RowIterator; 24 | import java.util.Arrays; 25 | 26 | /** 27 | * Simple implementation of an {@link QueryResult} 28 | */ 29 | public class QueryResultImpl implements QueryResult { 30 | private Node[] nodes = new Node[0]; 31 | 32 | 33 | public QueryResultImpl(Node... nodes) { 34 | this.nodes = nodes; 35 | } 36 | 37 | 38 | @Override 39 | public String[] getColumnNames() throws RepositoryException { 40 | return new String[0]; 41 | } 42 | 43 | 44 | @Override 45 | public RowIterator getRows() throws RepositoryException { 46 | return null; 47 | } 48 | 49 | 50 | @Override 51 | public NodeIterator getNodes() throws RepositoryException { 52 | return new NodeIteratorImpl(Arrays.asList(nodes)); 53 | } 54 | 55 | 56 | @Override 57 | public String[] getSelectorNames() throws RepositoryException { 58 | return new String[0]; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /gradle/bintray.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'maven' 2 | apply plugin: 'com.jfrog.bintray' 3 | 4 | // 5 | // Make sure the needed properties exist if the user is trying to upload to Bintray 6 | // 7 | gradle.taskGraph.whenReady { taskGraph -> 8 | if (taskGraph.hasTask(bintrayUpload)) { 9 | if (!project.hasProperty('bintray.user') || !project.hasProperty('bintray.key')) { 10 | throw new IllegalArgumentException((String)"Please define 'bintray.user' and " + 11 | "'bintray.key' properties. (Such as in ~/.gradle/gradle.properties)") 12 | } 13 | } 14 | } 15 | 16 | //noinspection GroovyAssignabilityCheck 17 | bintray { 18 | user = project.properties['bintray.user'] 19 | key = project.properties['bintray.key'] 20 | configurations = ['archives'] 21 | 22 | publish = version.status == 'release' 23 | 24 | pkg { 25 | userOrg = 'twcable' 26 | repo = 'aem' 27 | name = project.name 28 | 29 | desc = project.description 30 | 31 | websiteUrl = 'https://github.com/TWCable/jackalope' 32 | issueTrackerUrl = 'https://github.com/TWCable/jackalope/issues' 33 | vcsUrl = 'https://github.com/TWCable/jackalope.git' 34 | licenses = ['Apache-2.0'] 35 | labels = ['groovy', 'cq', 'aem', 'jcr', 'sling'] 36 | attributes = ['plat': ['aem', 'cq', 'jcr', 'sling']] 37 | 38 | publicDownloadNumbers = true 39 | 40 | //noinspection GroovyAssignabilityCheck 41 | version { 42 | desc = 'First public release' 43 | //noinspection GrReassignedInClosureLocalVar 44 | name = project.version.bintrayVersion 45 | } 46 | } 47 | } 48 | 49 | task sourcesJar(type: Jar, dependsOn: classes) { 50 | classifier = 'sources' 51 | from sourceSets.main.allSource 52 | } 53 | 54 | artifacts { 55 | archives sourcesJar 56 | } 57 | -------------------------------------------------------------------------------- /docs/RELEASING.adoc: -------------------------------------------------------------------------------- 1 | = Releasing A New Version 2 | 3 | == Update Files 4 | 5 | . Update the `version` property in link:../gradle.properties[`gradle.properties`]. We follow http://semver.org/spec/v2.0.0.html[Semantic Versioning 2.0.0], so increment accordingly 6 | 7 | . Update link:../README.md[README.md] with the new version in the "`Including In Your Build`" section 8 | 9 | . Update link:../RELEASE_NOTES.md[RELEASE_NOTES.md] with what has changed. Assuming the commit messages have been written well, it will generally be the subject lines of the commits since the last release, but edit (including adding) as appropriate for a human to easily understand what has changed 10 | 11 | == Create A Clean Build 12 | 13 | [source,bash] 14 | -- 15 | $ ./gradlew clean build 16 | -- 17 | 18 | == Commit and Push 19 | 20 | [source,bash] 21 | -- 22 | $ git add gradle.properties README.md RELEASE_NOTES.md 23 | $ git commit -m v9.9.9 # "v" followed by the version number 24 | $ git tag v9.9.9 25 | $ git push origin HEAD:master 26 | $ git push origin v9.9.9 27 | -- 28 | 29 | Create a https://github.com/TWCable/jackalope/releases/new[new GitHub release], selecting the just-uploaded tag from the list. There's no need for a "`Release title`", though copy & paste the latest version information from RELEASE_NOTES.md into the "`Describe this release`" box. If you want you can attach the built artifact to the GitHub release, but the "`official`" location is https://bintray.com/twcable/aem/jackalope/_latestVersion[BinTray]. 30 | 31 | *NOTE*: If you are back-porting a release or the like, GitHub's "`release`" feature is extremely simplistic and always labels most recently create "release" record as being the "`Latest release`". 32 | 33 | 34 | == Upload to BinTray 35 | 36 | [source,bash] 37 | -- 38 | $ ./gradlew bintrayUpload 39 | -- 40 | 41 | If you have not yet set up your credentials, the task will tell you what to do. 42 | -------------------------------------------------------------------------------- /src/main/java/com/twcable/jackalope/impl/jcr/JcrUtils.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Time Warner Cable, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.twcable.jackalope.impl.jcr; 18 | 19 | import javax.jcr.Node; 20 | import javax.jcr.RepositoryException; 21 | import java.util.Iterator; 22 | 23 | /** 24 | * Utility class for JCR functions. 25 | */ 26 | public final class JcrUtils { 27 | 28 | private JcrUtils() { 29 | } 30 | 31 | 32 | public static Iterable getChildNodes(final Node node) { 33 | return new Iterable() { 34 | @SuppressWarnings("unchecked") 35 | @Override 36 | public Iterator iterator() { 37 | try { 38 | return node.getNodes(); 39 | } 40 | catch (RepositoryException re) { 41 | return new Iterator() { 42 | @Override 43 | public boolean hasNext() { 44 | return false; 45 | } 46 | 47 | 48 | @Override 49 | public Node next() { 50 | return null; 51 | } 52 | 53 | 54 | @Override 55 | public void remove() { 56 | } 57 | }; 58 | } 59 | } 60 | }; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/main/java/com/twcable/jackalope/impl/common/Values.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Time Warner Cable, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.twcable.jackalope.impl.common; 18 | 19 | import com.twcable.jackalope.impl.jcr.ValueImpl; 20 | 21 | import javax.jcr.RepositoryException; 22 | import javax.jcr.Value; 23 | import java.util.ArrayList; 24 | import java.util.List; 25 | 26 | /** 27 | * Utilities for manipulating {@link Value}s. 28 | */ 29 | public final class Values { 30 | 31 | private Values() { 32 | } 33 | 34 | 35 | public static String[] convertValuesToStrings(Value... values) throws RepositoryException { 36 | List strings = new ArrayList<>(); 37 | for (Value value : values) 38 | strings.add(value.getString()); 39 | return strings.toArray(new String[strings.size()]); 40 | } 41 | 42 | 43 | public static Value[] convertStringsToValues(String... strings) { 44 | List values = new ArrayList<>(); 45 | for (String string : strings) 46 | values.add(new ValueImpl(string)); 47 | return values.toArray(new Value[values.size()]); 48 | } 49 | 50 | 51 | public static Value[] convertObjectsToValues(Object... strings) { 52 | List values = new ArrayList<>(); 53 | for (Object object : strings) 54 | values.add(new ValueImpl(object)); 55 | return values.toArray(new Value[values.size()]); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/main/java/com/twcable/jackalope/impl/cq/RenditionImpl.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Time Warner Cable, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.twcable.jackalope.impl.cq; 18 | 19 | import com.day.cq.dam.api.Asset; 20 | import com.day.cq.dam.api.Rendition; 21 | import com.twcable.jackalope.impl.sling.NodeResourceImpl; 22 | import org.apache.sling.api.resource.Resource; 23 | import org.apache.sling.api.resource.ResourceUtil; 24 | import org.apache.sling.api.resource.ValueMap; 25 | 26 | import javax.jcr.Node; 27 | import java.io.InputStream; 28 | 29 | /** 30 | * Simple implementation of a {@link Rendition} and an asset resource 31 | */ 32 | class RenditionImpl extends NodeResourceImpl implements Rendition { 33 | private final Asset asset; 34 | 35 | 36 | public RenditionImpl(Asset asset, Resource resource) { 37 | super(resource.getResourceResolver(), resource.adaptTo(Node.class)); 38 | this.asset = asset; 39 | } 40 | 41 | 42 | @Override 43 | public String getMimeType() { 44 | return null; 45 | } 46 | 47 | 48 | @Override 49 | public ValueMap getProperties() { 50 | return ResourceUtil.getValueMap(this.getChild("jcr:content")); 51 | } 52 | 53 | 54 | @Override 55 | public long getSize() { 56 | return 0; 57 | } 58 | 59 | 60 | @Override 61 | public InputStream getStream() { 62 | return null; 63 | } 64 | 65 | 66 | @Override 67 | public Asset getAsset() { 68 | return asset; 69 | } 70 | 71 | } 72 | -------------------------------------------------------------------------------- /src/test/groovy/com/twcable/jackalope/impl/JCRQueryBuilderSpec.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Time Warner Cable, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.twcable.jackalope.impl 18 | 19 | import com.google.common.collect.Lists 20 | import com.twcable.jackalope.JCRBuilder 21 | import com.twcable.jackalope.JCRQueryBuilder 22 | import spock.lang.Specification 23 | import spock.lang.Subject 24 | 25 | import static com.twcable.jackalope.JCRQueryBuilder.query 26 | import static com.twcable.jackalope.JCRQueryBuilder.queryManager 27 | import static com.twcable.jackalope.JCRQueryBuilder.result 28 | 29 | @Subject(JCRQueryBuilder) 30 | class JCRQueryBuilderSpec extends Specification { 31 | 32 | def "Constructing a QueryManager attaches it to its associated session"() { 33 | def node = JCRBuilder.node("content").build() 34 | 35 | when: 36 | def queryManager = queryManager(node.session).build() 37 | 38 | then: 39 | queryManager != null 40 | node.session.workspace.queryManager == queryManager 41 | } 42 | 43 | 44 | def "Construct a QueryManager with a query and results"() { 45 | def node = JCRBuilder.node("content").build() 46 | 47 | when: 48 | queryManager(node.session, query("query", "language", result(node))).build() 49 | def results = Lists.newArrayList(node.session.workspace.queryManager.createQuery("query", "language").execute().nodes) 50 | 51 | then: 52 | results.size() == 1 53 | results[0] == node 54 | } 55 | 56 | } 57 | -------------------------------------------------------------------------------- /src/test/groovy/com/twcable/jackalope/impl/cq/AssetSpec.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Time Warner Cable, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.twcable.jackalope.impl.cq 18 | 19 | import spock.lang.Specification 20 | import spock.lang.Subject 21 | 22 | import static com.twcable.jackalope.JCRBuilder.node 23 | import static com.twcable.jackalope.JCRBuilder.property 24 | import static com.twcable.jackalope.JCRBuilder.resource 25 | import static org.apache.jackrabbit.JcrConstants.JCR_PRIMARYTYPE 26 | 27 | @Subject(AssetImpl) 28 | class AssetSpec extends Specification { 29 | def "getOriginal returns the original rendition"() { 30 | def resource = resource( 31 | node("/test.csv", 32 | property(JCR_PRIMARYTYPE, "dam:Asset"), 33 | node("jcr:content", 34 | property(JCR_PRIMARYTYPE, "dam:AssetContent"), 35 | node("renditions", 36 | property(JCR_PRIMARYTYPE, "nt:folder"), 37 | node("original", 38 | property(JCR_PRIMARYTYPE, "nt:file"), 39 | node("jcr:content", 40 | property("jcr:mimeType", "text/csv"))))))).build() 41 | def asset = new AssetImpl(resource) 42 | 43 | when: "getOriginal is called" 44 | def original = asset.getOriginal() 45 | 46 | then: 47 | original.getProperties().get("jcr:mimeType") == "text/csv" 48 | original.getChild("jcr:content") != null 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/com/twcable/jackalope/impl/jcr/RangeIteratorImpl.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Time Warner Cable, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.twcable.jackalope.impl.jcr; 18 | 19 | import javax.jcr.RangeIterator; 20 | import java.util.Collection; 21 | import java.util.Iterator; 22 | 23 | /** 24 | * A simple implementation of RangeIterator that delegates to a single simple Iterator. 25 | * 26 | * @param Type of elements in the Collection 27 | */ 28 | class RangeIteratorImpl implements RangeIterator { 29 | final int size; 30 | final Iterator iterator; 31 | int position = 0; 32 | 33 | 34 | /** 35 | * Construct a RangeIteratorImpl. 36 | * 37 | * @param source The collection to iterate over. 38 | */ 39 | public RangeIteratorImpl(Collection source) { 40 | this.size = source.size(); 41 | this.iterator = source.iterator(); 42 | } 43 | 44 | 45 | @Override 46 | public void skip(long skipNum) { 47 | while (skipNum-- > 0) next(); 48 | } 49 | 50 | 51 | @Override 52 | public long getSize() { 53 | return size; 54 | } 55 | 56 | 57 | @Override 58 | public long getPosition() { 59 | return position; 60 | } 61 | 62 | 63 | @Override 64 | public boolean hasNext() { 65 | return iterator.hasNext(); 66 | } 67 | 68 | 69 | @Override 70 | public Object next() { 71 | position++; 72 | return iterator.next(); 73 | } 74 | 75 | 76 | @Override 77 | public void remove() { 78 | iterator.remove(); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/main/java/com/twcable/jackalope/impl/jcr/ObservationManagerImpl.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Time Warner Cable, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.twcable.jackalope.impl.jcr; 18 | 19 | import javax.jcr.RepositoryException; 20 | import javax.jcr.observation.EventJournal; 21 | import javax.jcr.observation.EventListener; 22 | import javax.jcr.observation.EventListenerIterator; 23 | import javax.jcr.observation.ObservationManager; 24 | 25 | /** 26 | * Simple implementation of an {@link ObservationManager} 27 | */ 28 | public class ObservationManagerImpl implements ObservationManager { 29 | 30 | @Override 31 | public void addEventListener(EventListener listener, int eventTypes, String absPath, boolean isDeep, String[] uuid, String[] nodeTypeName, boolean noLocal) throws RepositoryException { 32 | } 33 | 34 | 35 | @Override 36 | public void removeEventListener(EventListener listener) throws RepositoryException { 37 | } 38 | 39 | 40 | @Override 41 | public EventListenerIterator getRegisteredEventListeners() throws RepositoryException { 42 | return null; 43 | } 44 | 45 | 46 | @Override 47 | public void setUserData(String userData) throws RepositoryException { 48 | } 49 | 50 | 51 | @Override 52 | public EventJournal getEventJournal() throws RepositoryException { 53 | return null; 54 | } 55 | 56 | 57 | @Override 58 | public EventJournal getEventJournal(int eventTypes, String absPath, boolean isDeep, String[] uuid, String[] nodeTypeName) throws RepositoryException { 59 | return null; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/main/java/com/twcable/jackalope/impl/jcr/QueryManagerImpl.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Time Warner Cable, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.twcable.jackalope.impl.jcr; 18 | 19 | import javax.jcr.Node; 20 | import javax.jcr.RepositoryException; 21 | import javax.jcr.query.InvalidQueryException; 22 | import javax.jcr.query.Query; 23 | import javax.jcr.query.QueryManager; 24 | import javax.jcr.query.qom.QueryObjectModelFactory; 25 | 26 | /** 27 | * Simple implementation of an {@link QueryManager} 28 | */ 29 | @SuppressWarnings("DuplicateThrows") 30 | public class QueryManagerImpl implements QueryManager { 31 | private Query[] queries = new QueryImpl[0]; 32 | 33 | 34 | public QueryManagerImpl(Query... queries) { 35 | this.queries = queries; 36 | } 37 | 38 | 39 | @Override 40 | public Query createQuery(String statement, String language) throws InvalidQueryException, RepositoryException { 41 | for (Query query : queries) 42 | if (query.getLanguage().equals(language) && query.getStatement().equals(statement)) 43 | return query; 44 | return new QueryImpl(statement, language, new QueryResultImpl()); 45 | } 46 | 47 | 48 | @Override 49 | public QueryObjectModelFactory getQOMFactory() { 50 | return null; 51 | } 52 | 53 | 54 | @Override 55 | public Query getQuery(Node node) throws InvalidQueryException, RepositoryException { 56 | return null; 57 | } 58 | 59 | 60 | @Override 61 | public String[] getSupportedQueryLanguages() throws RepositoryException { 62 | return new String[0]; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/main/java/com/twcable/jackalope/impl/sling/SimpleResourceResolverFactory.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Time Warner Cable, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.twcable.jackalope.impl.sling; 18 | 19 | import org.apache.sling.api.resource.LoginException; 20 | import org.apache.sling.api.resource.ResourceResolver; 21 | import org.apache.sling.api.resource.ResourceResolverFactory; 22 | import org.apache.sling.jcr.api.SlingRepository; 23 | 24 | import javax.annotation.Nonnull; 25 | import java.util.Map; 26 | 27 | /** 28 | * A trivial {@link ResourceResolverFactory} that creates a {@link ResourceResolverImpl} based on a 29 | * {@link SlingRepository} and always returns the same instance. 30 | */ 31 | public class SimpleResourceResolverFactory implements ResourceResolverFactory { 32 | private ResourceResolver resourceResolver = null; 33 | 34 | 35 | public SimpleResourceResolverFactory(@Nonnull SlingRepository repository) { 36 | this.resourceResolver = new ResourceResolverImpl(repository); 37 | } 38 | 39 | 40 | @Override 41 | public ResourceResolver getResourceResolver(Map authenticationInfo) throws LoginException { 42 | return this.resourceResolver; 43 | } 44 | 45 | 46 | @Override 47 | public ResourceResolver getAdministrativeResourceResolver(Map authenticationInfo) throws LoginException { 48 | return this.resourceResolver; 49 | } 50 | 51 | 52 | @Override 53 | public ResourceResolver getServiceResourceResolver(Map authenticationInfo) throws LoginException { 54 | return this.resourceResolver; 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /src/test/groovy/com/twcable/jackalope/impl/cq/PageImplSpec.groovy: -------------------------------------------------------------------------------- 1 | package com.twcable.jackalope.impl.cq 2 | 3 | import com.twcable.jackalope.impl.jcr.NodeImpl 4 | import com.twcable.jackalope.impl.jcr.SessionImpl 5 | import com.twcable.jackalope.impl.sling.ResourceResolverImpl 6 | import com.twcable.jackalope.impl.sling.SlingRepositoryImpl 7 | import spock.lang.Specification 8 | import spock.lang.Subject 9 | 10 | @Subject(PageImpl) 11 | class PageImplSpec extends Specification { 12 | SlingRepositoryImpl repository 13 | SessionImpl session 14 | 15 | def setup() { 16 | repository = new SlingRepositoryImpl() 17 | session = repository.login() as SessionImpl 18 | } 19 | 20 | def "isHideInNav() returns correct value"() { 21 | def pageNode = new NodeImpl(session, "/page") 22 | def resourceResolver = new ResourceResolverImpl(repository) 23 | def page = new PageImpl(resourceResolver.getResource("/page")) 24 | 25 | when: 26 | def content = pageNode.addNode("jcr:content", "cq:PageContent") 27 | 28 | then: 29 | !page.isHideInNav() 30 | 31 | when: 32 | content.setProperty("hideInNav", true) 33 | 34 | then: 35 | page.isHideInNav() 36 | } 37 | 38 | def "isValid() returns correct value"() { 39 | def pageNode = new NodeImpl(session, "/page") 40 | def resourceResolver = new ResourceResolverImpl(repository) 41 | def page = new PageImpl(resourceResolver.getResource("/page")) 42 | 43 | when: 44 | def content = pageNode.addNode("jcr:content", "cq:PageContent") 45 | 46 | then: 47 | page.isValid() 48 | 49 | when: 50 | content.setProperty("onTime", new GregorianCalendar(2099, 5, 15)) 51 | 52 | then: 53 | !page.isValid() 54 | 55 | when: 56 | content.setProperty("onTime", new GregorianCalendar(1900, 5, 15)) 57 | 58 | then: 59 | page.isValid() 60 | 61 | when: 62 | content.setProperty("offTime", new GregorianCalendar(1900, 6, 15)) 63 | 64 | then: 65 | !page.isValid() 66 | 67 | when: 68 | content.setProperty("offTime", new GregorianCalendar(2099, 6, 15)) 69 | 70 | then: 71 | page.isValid() 72 | 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/main/java/com/twcable/jackalope/impl/jcr/BinaryImpl.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Time Warner Cable, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.twcable.jackalope.impl.jcr; 18 | 19 | import org.apache.commons.io.IOUtils; 20 | 21 | import javax.jcr.Binary; 22 | import javax.jcr.RepositoryException; 23 | import java.io.ByteArrayInputStream; 24 | import java.io.IOException; 25 | import java.io.InputStream; 26 | 27 | /** 28 | * Simple implementation of an {@link Binary} 29 | */ 30 | public class BinaryImpl implements Binary { 31 | final byte[] bytes; 32 | 33 | 34 | public BinaryImpl(byte[] bytes) { 35 | this.bytes = bytes; 36 | } 37 | 38 | 39 | public BinaryImpl(InputStream stream) { 40 | byte[] b = new byte[0]; 41 | try { 42 | b = IOUtils.toByteArray(stream); 43 | } 44 | catch (IOException ioe) { /* ignore */ } 45 | bytes = b; 46 | } 47 | 48 | 49 | @Override 50 | public InputStream getStream() throws RepositoryException { 51 | return new ByteArrayInputStream(bytes); 52 | } 53 | 54 | 55 | @Override 56 | public int read(byte[] b, long position) throws IOException, RepositoryException { 57 | int p_int = (int)position; // Why is position a long and the return an int? 58 | int length = (b.length < bytes.length - p_int) ? b.length : bytes.length - p_int; 59 | if (length < 0) return -1; 60 | System.arraycopy(bytes, p_int, b, 0, length); 61 | return length; 62 | } 63 | 64 | 65 | @Override 66 | public long getSize() throws RepositoryException { 67 | return bytes.length; 68 | } 69 | 70 | 71 | @Override 72 | public void dispose() { 73 | 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/main/java/com/twcable/jackalope/impl/jcr/NodeDefinitionImpl.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Time Warner Cable, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.twcable.jackalope.impl.jcr; 18 | 19 | import javax.jcr.nodetype.NodeDefinition; 20 | import javax.jcr.nodetype.NodeType; 21 | import javax.jcr.version.OnParentVersionAction; 22 | 23 | /** 24 | * Simple implementation of an {@link NodeDefinition} hardcoded to "nt:base" 25 | */ 26 | public class NodeDefinitionImpl implements NodeDefinition { 27 | 28 | @Override 29 | public NodeType[] getRequiredPrimaryTypes() { 30 | return new NodeType[]{new NodeTypeImpl("nt:base")}; 31 | } 32 | 33 | 34 | @Override 35 | public String[] getRequiredPrimaryTypeNames() { 36 | return new String[]{"nt:base"}; 37 | } 38 | 39 | 40 | @Override 41 | public NodeType getDefaultPrimaryType() { 42 | return new NodeTypeImpl("nt:base"); 43 | } 44 | 45 | 46 | @Override 47 | public String getDefaultPrimaryTypeName() { 48 | return "nt:base"; 49 | } 50 | 51 | 52 | @Override 53 | public boolean allowsSameNameSiblings() { 54 | return false; 55 | } 56 | 57 | 58 | @Override 59 | public NodeType getDeclaringNodeType() { 60 | return null; 61 | } 62 | 63 | 64 | @Override 65 | public String getName() { 66 | return "nt:base"; 67 | } 68 | 69 | 70 | @Override 71 | public boolean isAutoCreated() { 72 | return false; 73 | } 74 | 75 | 76 | @Override 77 | public boolean isMandatory() { 78 | return false; 79 | } 80 | 81 | 82 | @Override 83 | public int getOnParentVersion() { 84 | return OnParentVersionAction.IGNORE; 85 | } 86 | 87 | 88 | @Override 89 | public boolean isProtected() { 90 | return false; 91 | } 92 | 93 | } 94 | -------------------------------------------------------------------------------- /src/main/java/com/twcable/jackalope/impl/cq/PageIteratorImpl.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Time Warner Cable, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.twcable.jackalope.impl.cq; 18 | 19 | import com.day.cq.commons.Filter; 20 | import com.day.cq.wcm.api.Page; 21 | import com.google.common.collect.Lists; 22 | import org.apache.sling.api.resource.Resource; 23 | 24 | import java.util.Iterator; 25 | import java.util.LinkedList; 26 | import java.util.List; 27 | 28 | /** 29 | * Simple iterator over filtered Pages under a Resource 30 | */ 31 | public class PageIteratorImpl implements Iterator { 32 | 33 | private final Iterator iterator; 34 | 35 | 36 | public PageIteratorImpl(Resource resource, Filter filter, boolean deep) { 37 | this.iterator = getChildren(resource, filter, deep).iterator(); 38 | } 39 | 40 | public PageIteratorImpl(Resource resource, Filter filter) { 41 | this(resource, filter, false); 42 | } 43 | 44 | @Override 45 | public boolean hasNext() { 46 | return iterator.hasNext(); 47 | } 48 | 49 | 50 | @Override 51 | public Page next() { 52 | return iterator.next(); 53 | } 54 | 55 | 56 | @Override 57 | public void remove() { 58 | iterator.remove(); 59 | } 60 | 61 | private List getChildren(Resource resource, Filter filter, boolean deep) { 62 | List pages = new LinkedList<>(); 63 | for (Resource next : Lists.newArrayList(resource.listChildren())) { 64 | Page page = next.adaptTo(Page.class); 65 | if (page == null || !filter.includes(page)) continue; 66 | pages.add(page); 67 | if (deep) { 68 | pages.addAll(getChildren(page.adaptTo(Resource.class), filter, true)); 69 | } 70 | } 71 | return pages; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/test/groovy/com/twcable/jackalope/impl/jcr/ValueFactoryImplSpec.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Time Warner Cable, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.twcable.jackalope.impl.jcr 18 | 19 | import org.apache.commons.io.IOUtils 20 | import spock.lang.Specification 21 | import spock.lang.Subject 22 | 23 | import javax.jcr.PropertyType 24 | 25 | @Subject(ValueFactoryImpl) 26 | @SuppressWarnings("GroovyPointlessBoolean") 27 | class ValueFactoryImplSpec extends Specification { 28 | 29 | def "creates Values of different Types"() { 30 | def now = Calendar.getInstance() 31 | 32 | expect: 33 | new ValueFactoryImpl().createValue("hello").string == "hello" 34 | new ValueFactoryImpl().createValue(20l).long == 20l; 35 | new ValueFactoryImpl().createValue(2.1d).double == 2.1d 36 | new ValueFactoryImpl().createValue(now).date == now 37 | new ValueFactoryImpl().createValue(false).boolean == false 38 | new ValueFactoryImpl().createValue(new BigDecimal(2.1)).decimal == new BigDecimal(2.1) 39 | 40 | def binary = new ValueFactoryImpl().createBinary(IOUtils.toInputStream("hello, world", "UTF-8")) 41 | IOUtils.toString(new ValueFactoryImpl().createValue(binary).binary.stream) == "hello, world" 42 | } 43 | 44 | 45 | def "converts strings to types"() { 46 | expect: 47 | new ValueFactoryImpl().createValue("hello", PropertyType.STRING).string == "hello" 48 | new ValueFactoryImpl().createValue("20", PropertyType.LONG).long == 20l 49 | new ValueFactoryImpl().createValue("1.5", PropertyType.DOUBLE).double == 1.5d 50 | new ValueFactoryImpl().createValue("false", PropertyType.BOOLEAN).boolean == false 51 | new ValueFactoryImpl().createValue("2.5", PropertyType.DECIMAL).decimal == new BigDecimal(2.5) 52 | IOUtils.toString(new ValueFactoryImpl().createValue("hello, world", PropertyType.BINARY).binary.stream) == "hello, world" 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /src/test/groovy/com/twcable/jackalope/impl/jcr/RangeIteratorImplSpec.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Time Warner Cable, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.twcable.jackalope.impl.jcr 18 | 19 | import spock.lang.Specification 20 | import spock.lang.Subject 21 | 22 | @Subject(RangeIteratorImpl) 23 | class RangeIteratorImplSpec extends Specification { 24 | 25 | def "skip skips elements in the iterator"() { 26 | def iterator = new RangeIteratorImpl<>(["a", "b", "c", "d", "e", "f", "g"]) 27 | 28 | when: 29 | iterator.skip(1) 30 | 31 | then: 32 | iterator.next() == "b" 33 | 34 | when: 35 | iterator.skip(3) 36 | 37 | then: 38 | iterator.next() == "f" 39 | } 40 | 41 | 42 | def "skip throws NoSuchElementException if skipped past the last element in the iterator."() { 43 | when: 44 | def iterator = new RangeIteratorImpl(["a", "b", "c"]); 45 | iterator.next() // "a" 46 | iterator.skip(3) 47 | 48 | then: 49 | thrown(NoSuchElementException) 50 | 51 | when: 52 | def emptyIterator = new RangeIteratorImpl([]); 53 | emptyIterator.skip(1) 54 | 55 | then: 56 | thrown(NoSuchElementException) 57 | } 58 | 59 | 60 | def "the iterator keeps track of its position"() { 61 | when: 62 | def iterator = new RangeIteratorImpl<>(["a", "b", "c", "d", "e", "f", "g"]) 63 | 64 | then: 65 | iterator.position == 0 66 | 67 | when: 68 | iterator.skip(1) 69 | 70 | then: 71 | iterator.position == 1 72 | 73 | when: 74 | iterator.next() // "b" 75 | 76 | then: 77 | iterator.position == 2 78 | 79 | when: 80 | iterator.skip(3) 81 | 82 | then: 83 | iterator.position == 5 84 | 85 | when: 86 | iterator.next() // "f" 87 | 88 | then: 89 | iterator.position == 6 90 | 91 | when: 92 | iterator.next() 93 | 94 | then: 95 | iterator.position == 7 96 | } 97 | 98 | } 99 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /src/test/groovy/com/twcable/jackalope/impl/jcr/ValueImplSpec.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Time Warner Cable, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.twcable.jackalope.impl.jcr 18 | 19 | import org.apache.commons.io.IOUtils 20 | import spock.lang.Specification 21 | import spock.lang.Subject 22 | 23 | import javax.jcr.PropertyType 24 | 25 | @Subject(ValueImpl) 26 | @SuppressWarnings("GrDeprecatedAPIUsage") 27 | class ValueImplSpec extends Specification { 28 | 29 | def "ValueImpl holds a jcr value"() { 30 | def factory = new ValueFactoryImpl() 31 | 32 | expect: 33 | new ValueImpl(new String("hello, world")).string == "hello, world" 34 | new ValueImpl(Double.MAX_VALUE).double == Double.MAX_VALUE 35 | new ValueImpl(Long.MAX_VALUE).long == Long.MAX_VALUE 36 | new ValueImpl(BigDecimal.TEN).decimal == BigDecimal.TEN 37 | new ValueImpl(Boolean.TRUE).boolean == Boolean.TRUE 38 | new ValueImpl(Calendar.instance).date.time.date == Calendar.instance.time.date 39 | new ValueImpl(PropertyType.STRING, new String("hello, world")).string == "hello, world" 40 | new ValueImpl(PropertyType.DOUBLE, Double.MAX_VALUE).double == Double.MAX_VALUE 41 | new ValueImpl(PropertyType.LONG, Long.MAX_VALUE).long == Long.MAX_VALUE 42 | new ValueImpl(PropertyType.DECIMAL, BigDecimal.TEN).decimal == BigDecimal.TEN 43 | new ValueImpl(PropertyType.BOOLEAN, Boolean.TRUE).boolean == Boolean.TRUE 44 | new ValueImpl(PropertyType.DATE, Calendar.instance).date.time.date == Calendar.instance.time.date 45 | new ValueImpl("hello, world").string == "hello, world" 46 | new ValueImpl(Double.valueOf(2.5d)).string == "2.5" 47 | new ValueImpl(Long.valueOf(10L)).string == "10" 48 | new ValueImpl(BigDecimal.TEN).string == "10" 49 | new ValueImpl(Boolean.TRUE).string == "true" 50 | // TODO: Implement correct calendar to string conversion 51 | // new ValueImpl(Calendar.getInstance()).getString() == "" 52 | IOUtils.toString(new ValueImpl(PropertyType.BINARY, factory.createBinary("hello, world")).binary.stream) == "hello, world" 53 | 54 | def bytes = new byte[3]; 55 | new ValueImpl(PropertyType.BINARY, factory.createBinary("hello, world")).binary.read(bytes, 1) 56 | new String(bytes) == "ell" 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /src/main/java/com/twcable/jackalope/impl/jcr/QueryImpl.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Time Warner Cable, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.twcable.jackalope.impl.jcr; 18 | 19 | import javax.jcr.ItemExistsException; 20 | import javax.jcr.ItemNotFoundException; 21 | import javax.jcr.Node; 22 | import javax.jcr.PathNotFoundException; 23 | import javax.jcr.RepositoryException; 24 | import javax.jcr.UnsupportedRepositoryOperationException; 25 | import javax.jcr.Value; 26 | import javax.jcr.lock.LockException; 27 | import javax.jcr.nodetype.ConstraintViolationException; 28 | import javax.jcr.query.InvalidQueryException; 29 | import javax.jcr.query.Query; 30 | import javax.jcr.query.QueryResult; 31 | import javax.jcr.version.VersionException; 32 | 33 | /** 34 | * Simple implementation of an {@link Query} 35 | */ 36 | @SuppressWarnings("DuplicateThrows") 37 | public class QueryImpl implements Query { 38 | String statement = null; 39 | String language = null; 40 | QueryResult result = null; 41 | 42 | 43 | public QueryImpl(String statement, String language, QueryResult result) { 44 | this.statement = statement; 45 | this.language = language; 46 | this.result = result; 47 | } 48 | 49 | 50 | @Override 51 | public QueryResult execute() throws InvalidQueryException, RepositoryException { 52 | return result; 53 | } 54 | 55 | 56 | @Override 57 | public void setLimit(long limit) { 58 | } 59 | 60 | 61 | @Override 62 | public void setOffset(long offset) { 63 | } 64 | 65 | 66 | @Override 67 | public String getStatement() { 68 | return statement; 69 | } 70 | 71 | 72 | @Override 73 | public String getLanguage() { 74 | return language; 75 | } 76 | 77 | 78 | @Override 79 | public String getStoredQueryPath() throws ItemNotFoundException, RepositoryException { 80 | return null; 81 | } 82 | 83 | 84 | @Override 85 | public Node storeAsNode(String absPath) throws ItemExistsException, PathNotFoundException, VersionException, ConstraintViolationException, LockException, UnsupportedRepositoryOperationException, RepositoryException { 86 | return null; 87 | } 88 | 89 | 90 | @Override 91 | public void bindValue(String varName, Value value) throws IllegalArgumentException, RepositoryException { 92 | } 93 | 94 | 95 | @Override 96 | public String[] getBindVariableNames() throws RepositoryException { 97 | return new String[0]; 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/main/java/com/twcable/jackalope/impl/jcr/RepositoryImpl.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Time Warner Cable, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.twcable.jackalope.impl.jcr; 18 | 19 | import org.apache.sling.jcr.api.SlingRepository; 20 | 21 | import javax.jcr.Credentials; 22 | import javax.jcr.LoginException; 23 | import javax.jcr.NoSuchWorkspaceException; 24 | import javax.jcr.RepositoryException; 25 | import javax.jcr.Session; 26 | import javax.jcr.Value; 27 | 28 | /** 29 | * Simple implementation of an {@link SlingRepository} 30 | */ 31 | @SuppressWarnings("DuplicateThrows") 32 | public class RepositoryImpl implements SlingRepository { 33 | private final SessionImpl session; 34 | 35 | 36 | public RepositoryImpl() { 37 | this.session = new SessionImpl(this); 38 | } 39 | 40 | 41 | @Override 42 | public String[] getDescriptorKeys() { 43 | return new String[0]; 44 | } 45 | 46 | 47 | @Override 48 | public boolean isStandardDescriptor(String key) { 49 | return false; 50 | } 51 | 52 | 53 | @Override 54 | public boolean isSingleValueDescriptor(String key) { 55 | return false; 56 | } 57 | 58 | 59 | @Override 60 | public Value getDescriptorValue(String key) { 61 | return null; 62 | } 63 | 64 | 65 | @Override 66 | public Value[] getDescriptorValues(String key) { 67 | return new Value[0]; 68 | } 69 | 70 | 71 | @Override 72 | public String getDescriptor(String key) { 73 | return null; 74 | } 75 | 76 | 77 | @Override 78 | public Session login(Credentials credentials, String workspaceName) throws LoginException, NoSuchWorkspaceException, RepositoryException { 79 | return session; 80 | } 81 | 82 | 83 | @Override 84 | public Session login(Credentials credentials) throws LoginException, RepositoryException { 85 | return session; 86 | } 87 | 88 | 89 | @Override 90 | public Session login(String workspaceName) throws LoginException, NoSuchWorkspaceException, RepositoryException { 91 | return session; 92 | } 93 | 94 | 95 | @Override 96 | public Session login() throws LoginException, RepositoryException { 97 | return session; 98 | } 99 | 100 | 101 | @Override 102 | public String getDefaultWorkspace() { 103 | return null; // Unimplemented 104 | } 105 | 106 | 107 | @Override 108 | public Session loginAdministrative(String workspace) throws RepositoryException { 109 | return session; 110 | } 111 | 112 | 113 | @Override 114 | public Session loginService(String s, String s1) throws LoginException, RepositoryException { 115 | return session; 116 | } 117 | 118 | } 119 | -------------------------------------------------------------------------------- /src/main/java/com/twcable/jackalope/impl/sling/PropertyResourceImpl.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Time Warner Cable, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.twcable.jackalope.impl.sling; 18 | 19 | import com.twcable.jackalope.impl.common.Values; 20 | import org.apache.sling.api.resource.Resource; 21 | import org.apache.sling.api.resource.ResourceResolver; 22 | import org.apache.sling.api.resource.ValueMap; 23 | 24 | import javax.jcr.Property; 25 | import javax.jcr.RepositoryException; 26 | import javax.jcr.Value; 27 | import java.math.BigDecimal; 28 | import java.util.Calendar; 29 | 30 | public class PropertyResourceImpl extends ItemResourceImpl { 31 | private final Property property; 32 | 33 | 34 | public PropertyResourceImpl(ResourceResolver resourceResolver, Property property) { 35 | super(resourceResolver, property); 36 | this.property = property; 37 | } 38 | 39 | 40 | @Override 41 | public Iterable getChildren() { 42 | return null; 43 | } 44 | 45 | 46 | @Override 47 | public String getResourceType() { 48 | // Taken from the sling implementation 49 | try { 50 | return getResourceTypeForNode(property.getParent()) + "/" + property.getName(); 51 | } 52 | catch (RepositoryException re) { 53 | throw new SlingRepositoryException(re); 54 | } 55 | } 56 | 57 | 58 | @Override 59 | public boolean hasChildren() { 60 | return false; 61 | } 62 | 63 | 64 | @Override 65 | public ValueMap getValueMap() { 66 | return null; 67 | } 68 | 69 | 70 | @Override 71 | @SuppressWarnings("unchecked") 72 | public AdapterType adaptTo(Class type) { 73 | try { 74 | return (type == String.class) ? (AdapterType)property.getString() : 75 | (type == Boolean.class) ? (AdapterType)Boolean.valueOf(property.getBoolean()) : 76 | (type == Long.class) ? (AdapterType)Long.valueOf(property.getLong()) : 77 | (type == Double.class) ? (AdapterType)new Double(property.getDouble()) : 78 | (type == BigDecimal.class) ? (AdapterType)property.getDecimal() : 79 | (type == Calendar.class) ? (AdapterType)property.getDate() : 80 | (type == Value.class) ? (AdapterType)property.getValue() : 81 | (type == String[].class) ? (AdapterType)Values.convertValuesToStrings(property.getValues()) : 82 | (type == Value[].class) ? (AdapterType)property.getValues() : 83 | super.adaptTo(type); 84 | } 85 | catch (RepositoryException re) { 86 | return super.adaptTo(type); 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/test/groovy/com/twcable/jackalope/impl/sling/PropertyResourceImplSpec.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Time Warner Cable, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.twcable.jackalope.impl.sling 18 | 19 | import com.twcable.jackalope.impl.jcr.NodeImpl 20 | import com.twcable.jackalope.impl.jcr.PropertyImpl 21 | import com.twcable.jackalope.impl.jcr.SessionImpl 22 | import com.twcable.jackalope.impl.jcr.ValueImpl 23 | import spock.lang.Specification 24 | import spock.lang.Subject 25 | 26 | import static org.apache.sling.jcr.resource.JcrResourceConstants.SLING_RESOURCE_TYPE_PROPERTY 27 | 28 | @Subject(PropertyResourceImpl) 29 | class PropertyResourceImplSpec extends Specification { 30 | 31 | def "A PropertyResource can be adapted to its various value types"() { 32 | def now = Calendar.getInstance() 33 | def session = new SessionImpl() 34 | def resolver = new ResourceResolverImpl(new SlingRepositoryImpl()) 35 | def stringp = new PropertyResourceImpl(resolver, new PropertyImpl(session, "string", new ValueImpl("hello"))) 36 | def longp = new PropertyResourceImpl(resolver, new PropertyImpl(session, "long", new ValueImpl(Long.valueOf(1000l)))) 37 | def doublep = new PropertyResourceImpl(resolver, new PropertyImpl(session, "double", new ValueImpl(Double.valueOf(5000d)))) 38 | def decimalp = new PropertyResourceImpl(resolver, new PropertyImpl(session, "decimal", new ValueImpl(BigDecimal.valueOf(2000d)))) 39 | def booleanp = new PropertyResourceImpl(resolver, new PropertyImpl(session, "boolean", new ValueImpl(Boolean.TRUE))) 40 | def datep = new PropertyResourceImpl(resolver, new PropertyImpl(session, "date", new ValueImpl(now))) 41 | 42 | expect: 43 | stringp.adaptTo(String) instanceof String 44 | stringp.adaptTo(String) == "hello" 45 | longp.adaptTo(Long) instanceof Long 46 | longp.adaptTo(Long) == Long.valueOf(1000l) 47 | doublep.adaptTo(Double) instanceof Double 48 | doublep.adaptTo(Double) == Double.valueOf(5000d) 49 | decimalp.adaptTo(BigDecimal) instanceof BigDecimal 50 | decimalp.adaptTo(BigDecimal) == BigDecimal.valueOf(2000d) 51 | booleanp.adaptTo(Boolean) instanceof Boolean 52 | booleanp.adaptTo(Boolean) == Boolean.TRUE 53 | datep.adaptTo(Calendar) instanceof Calendar 54 | datep.adaptTo(Calendar) == now 55 | } 56 | 57 | 58 | def "getResourceType returns the resource type of the containing node plus the property name"() { 59 | def repository = new SlingRepositoryImpl() 60 | def session = repository.login() as SessionImpl 61 | def node = new NodeImpl(session, "/test") 62 | node.setProperty(SLING_RESOURCE_TYPE_PROPERTY, "app/test/components/test") 63 | node.setProperty("string", "hello") 64 | 65 | when: 66 | def resource = new PropertyResourceImpl(new ResourceResolverImpl(repository), node.getProperty("string")) 67 | 68 | then: 69 | resource.getResourceType() == "app/test/components/test/string" 70 | } 71 | 72 | } 73 | -------------------------------------------------------------------------------- /src/test/groovy/com/twcable/jackalope/impl/jcr/SessionImplSpec.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Time Warner Cable, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.twcable.jackalope.impl.jcr 18 | 19 | import spock.lang.Specification 20 | import spock.lang.Subject 21 | 22 | class SessionImplSpec extends Specification { 23 | 24 | @Subject 25 | SessionImpl session = new SessionImpl() 26 | 27 | 28 | def "Nodes can be added and retrieved"() { 29 | when: 30 | def node = new NodeImpl(session, "test") 31 | 32 | then: 33 | session.nodeExists("test") 34 | session.itemExists("test") 35 | session.getNode("test") == node 36 | } 37 | 38 | 39 | def "Properties can be added and retrieved"() { 40 | when: 41 | def node = new NodeImpl(session, "test") 42 | node.setProperty("prop1", "a") 43 | node.setProperty("prop2", "a") 44 | 45 | then: 46 | session.propertyExists("test/prop1") 47 | session.propertyExists("test/prop2") 48 | session.getProperty("test/prop1").getString() == "a" 49 | session.getProperty("test/prop2").getString() == "a" 50 | } 51 | 52 | 53 | def "Session tracks added and changing nodes"() { 54 | when: 55 | def node = new NodeImpl(session, "test") 56 | 57 | then: 58 | session.isNew(node) 59 | session.hasPendingChanges() 60 | 61 | when: 62 | node.save() 63 | 64 | then: 65 | !session.isNew(node) 66 | !session.hasPendingChanges() 67 | 68 | when: 69 | node.setProperty("prop", "a") 70 | 71 | then: 72 | session.isModified(node) 73 | session.hasPendingChanges() 74 | 75 | when: 76 | session.save() 77 | 78 | then: 79 | !session.isModified(node) 80 | !session.hasPendingChanges() 81 | } 82 | 83 | 84 | def "Removing an item removes its descendents"() { 85 | def parent = new NodeImpl(session, "parent") 86 | def child = parent.addNode("child") 87 | def grandchild = child.addNode("grandchild") 88 | grandchild.addNode("greatgrandchild") 89 | 90 | expect: 91 | session.nodeExists("parent/child") 92 | session.nodeExists("parent/child/grandchild") 93 | session.nodeExists("parent/child/grandchild/greatgrandchild") 94 | 95 | when: 96 | session.removeItem(child.getPath()) 97 | 98 | then: 99 | !session.nodeExists("parent/child") 100 | !session.nodeExists("parent/child/grandchild") 101 | !session.nodeExists("parent/child/grandchild/greatgrandchild") 102 | } 103 | 104 | 105 | @SuppressWarnings("GroovyUnusedAssignment") 106 | def "A node can be moved"() { 107 | def dest = new NodeImpl(session, "/dest") 108 | def src = new NodeImpl(session, "/src") 109 | src.addNode("child") 110 | 111 | when: 112 | session.move("/src", "/dest/target") 113 | 114 | then: 115 | session.nodeExists("/dest/target") 116 | session.nodeExists("/dest/target/child") 117 | } 118 | 119 | } 120 | -------------------------------------------------------------------------------- /src/main/java/com/twcable/jackalope/JCRQueryBuilder.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Time Warner Cable, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.twcable.jackalope; 18 | 19 | import com.twcable.jackalope.impl.jcr.QueryImpl; 20 | import com.twcable.jackalope.impl.jcr.QueryManagerImpl; 21 | import com.twcable.jackalope.impl.jcr.QueryResultImpl; 22 | import com.twcable.jackalope.impl.jcr.WorkspaceImpl; 23 | 24 | import javax.annotation.Nonnull; 25 | import javax.jcr.Node; 26 | import javax.jcr.Session; 27 | import javax.jcr.query.Query; 28 | import javax.jcr.query.QueryManager; 29 | import javax.jcr.query.QueryResult; 30 | import java.util.ArrayList; 31 | import java.util.List; 32 | 33 | public class JCRQueryBuilder { 34 | 35 | @Nonnull 36 | public static QueryManagerBuilder queryManager(Session session, QueryBuilder... queryBuilders) { 37 | return new QueryManagerBuilder(session, queryBuilders); 38 | } 39 | 40 | 41 | @Nonnull 42 | public static QueryBuilder query(String statement, String language, QueryResultBuilder queryResultBuilder) { 43 | return new QueryBuilder(statement, language, queryResultBuilder); 44 | } 45 | 46 | 47 | @Nonnull 48 | public static QueryResultBuilder result(Node... nodes) { 49 | return new QueryResultBuilder(nodes); 50 | } 51 | 52 | // ********************************************************************** 53 | // 54 | // INNER CLASSES 55 | // 56 | // ********************************************************************** 57 | 58 | 59 | static class QueryManagerBuilder { 60 | private Session session; 61 | private QueryBuilder[] queryBuilders; 62 | 63 | 64 | public QueryManagerBuilder(Session session, QueryBuilder... queryBuilders) { 65 | this.session = session; 66 | this.queryBuilders = queryBuilders; 67 | } 68 | 69 | 70 | public QueryManager build() { 71 | List queries = new ArrayList<>(); 72 | for (QueryBuilder queryBuilder : queryBuilders) 73 | queries.add(queryBuilder.build()); 74 | QueryManager queryManager = new QueryManagerImpl(queries.toArray(new Query[queries.size()])); 75 | ((WorkspaceImpl)session.getWorkspace()).setQueryManager(queryManager); 76 | return queryManager; 77 | } 78 | } 79 | 80 | static class QueryBuilder { 81 | String statement; 82 | String language; 83 | QueryResultBuilder queryResultBuilder; 84 | 85 | 86 | public QueryBuilder(String statement, String language, QueryResultBuilder queryResultBuilder) { 87 | this.statement = statement; 88 | this.language = language; 89 | this.queryResultBuilder = queryResultBuilder; 90 | } 91 | 92 | 93 | public Query build() { 94 | return new QueryImpl(statement, language, queryResultBuilder.build()); 95 | } 96 | } 97 | 98 | static class QueryResultBuilder { 99 | Node[] nodes = new Node[0]; 100 | 101 | 102 | public QueryResultBuilder(Node... nodes) { 103 | this.nodes = nodes; 104 | } 105 | 106 | 107 | public QueryResult build() { 108 | return new QueryResultImpl(nodes); 109 | } 110 | } 111 | 112 | } 113 | -------------------------------------------------------------------------------- /src/main/java/com/twcable/jackalope/impl/sling/ItemResourceImpl.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Time Warner Cable, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.twcable.jackalope.impl.sling; 18 | 19 | import org.apache.sling.api.resource.Resource; 20 | import org.apache.sling.api.resource.ResourceMetadata; 21 | import org.apache.sling.api.resource.ResourceResolver; 22 | 23 | import javax.jcr.Item; 24 | import javax.jcr.Node; 25 | import javax.jcr.RepositoryException; 26 | import java.util.Iterator; 27 | 28 | import static org.apache.sling.jcr.resource.JcrResourceConstants.SLING_RESOURCE_TYPE_PROPERTY; 29 | 30 | /** 31 | * Basic abstract class to implement an {@link Item} as a {@link Resource} 32 | */ 33 | abstract public class ItemResourceImpl implements Resource { 34 | final ResourceResolver resourceResolver; 35 | final Item item; 36 | 37 | 38 | public ItemResourceImpl(ResourceResolver resourceResolver, Item item) { 39 | this.resourceResolver = resourceResolver; 40 | this.item = item; 41 | } 42 | 43 | 44 | @Override 45 | public String getPath() { 46 | try { 47 | return item.getPath(); 48 | } 49 | catch (RepositoryException re) { 50 | throw new SlingRepositoryException(re); 51 | } 52 | } 53 | 54 | 55 | @Override 56 | public String getName() { 57 | try { 58 | return item.getName(); 59 | } 60 | catch (RepositoryException re) { 61 | throw new SlingRepositoryException(re); 62 | } 63 | } 64 | 65 | 66 | @Override 67 | public Iterator listChildren() { 68 | return null; 69 | } 70 | 71 | 72 | @Override 73 | public Resource getParent() { 74 | try { 75 | return resourceResolver.getResource(item.getParent().getPath()); 76 | } 77 | catch (RepositoryException re) { 78 | throw new SlingRepositoryException(re); 79 | } 80 | } 81 | 82 | 83 | @Override 84 | public Resource getChild(String relPath) { 85 | return null; 86 | } 87 | 88 | 89 | @Override 90 | public String getResourceType() { 91 | return null; 92 | } 93 | 94 | 95 | @Override 96 | public String getResourceSuperType() { 97 | return null; 98 | } 99 | 100 | 101 | @Override 102 | public boolean isResourceType(String resourceType) { 103 | return resourceType.equals(getResourceType()); 104 | } 105 | 106 | 107 | @Override 108 | public ResourceMetadata getResourceMetadata() { 109 | ResourceMetadata metadata = new ResourceMetadata(); 110 | metadata.setResolutionPath(getPath()); 111 | return metadata; 112 | } 113 | 114 | 115 | @Override 116 | public ResourceResolver getResourceResolver() { 117 | return resourceResolver; 118 | } 119 | 120 | 121 | @Override 122 | public AdapterType adaptTo(Class type) { 123 | return null; 124 | } 125 | 126 | 127 | protected String getResourceTypeForNode(Node node) throws RepositoryException { 128 | return node.hasProperty(SLING_RESOURCE_TYPE_PROPERTY) 129 | ? node.getProperty(SLING_RESOURCE_TYPE_PROPERTY).getString() 130 | : node.getPrimaryNodeType().getName(); 131 | } 132 | 133 | } 134 | -------------------------------------------------------------------------------- /src/test/groovy/com/twcable/jackalope/impl/sling/ResourceResolverImplSpec.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Time Warner Cable, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.twcable.jackalope.impl.sling 18 | 19 | import com.twcable.jackalope.impl.jcr.NodeImpl 20 | import com.twcable.jackalope.impl.jcr.SessionImpl 21 | import org.apache.sling.api.resource.ValueMap 22 | import spock.lang.Specification 23 | import spock.lang.Subject 24 | 25 | class ResourceResolverImplSpec extends Specification { 26 | 27 | SlingRepositoryImpl repository 28 | SessionImpl session 29 | 30 | @Subject 31 | ResourceResolverImpl resourceResolver 32 | 33 | 34 | def setup() { 35 | repository = new SlingRepositoryImpl() 36 | session = repository.login() as SessionImpl 37 | resourceResolver = new ResourceResolverImpl(repository) 38 | } 39 | 40 | 41 | def "Resolves node resources"() { 42 | //noinspection GroovyUnusedAssignment 43 | def node = new NodeImpl(session, "/test") 44 | 45 | when: 46 | def resolved = resourceResolver.resolve("/test") 47 | 48 | then: 49 | resolved.name == "test" 50 | resolved.path == "/test" 51 | resolved instanceof NodeResourceImpl 52 | 53 | when: 54 | def resource = resourceResolver.getResource("/test") 55 | 56 | then: 57 | resource.name == "test" 58 | resource.path == "/test" 59 | 60 | when: 61 | def resourceChild = resourceResolver.getResource(resourceResolver.getResource("/"), "test") 62 | 63 | then: 64 | resourceChild.name == "test" 65 | resourceChild.path == "/test" 66 | } 67 | 68 | 69 | def "Resolves property resources"() { 70 | def node = new NodeImpl(session, "/test") 71 | node.setProperty("prop", "hello, world") 72 | 73 | when: 74 | def resolved = resourceResolver.resolve("/test/prop") 75 | 76 | then: 77 | resolved.name == "prop" 78 | resolved.path == "/test/prop" 79 | resolved instanceof PropertyResourceImpl 80 | 81 | when: 82 | def resource = resourceResolver.getResource("/test/prop") 83 | 84 | then: 85 | resource.name == "prop" 86 | resource.path == "/test/prop" 87 | 88 | when: 89 | def resourceProp = resourceResolver.getResource(resourceResolver.getResource("/test"), "prop") 90 | 91 | then: 92 | resourceProp.name == "prop" 93 | resourceProp.path == "/test/prop" 94 | } 95 | 96 | 97 | def "Creates node resources"() { 98 | //noinspection GroovyUnusedAssignment 99 | def parentNode = new NodeImpl(session, "/test") 100 | def parent = resourceResolver.getResource("/test") 101 | 102 | when: 103 | def created = resourceResolver.create(parent, "child", [prop1: "val1", prop2: "val2"]) 104 | 105 | then: 106 | created.name == "child" 107 | created.path == "/test/child" 108 | created.adaptTo(ValueMap).keySet() == ["prop1", "prop2"] as Set 109 | } 110 | 111 | 112 | def "Deletes node resources"() { 113 | def path = "/test" 114 | 115 | //noinspection GroovyUnusedAssignment 116 | def parentNode = new NodeImpl(session, path) 117 | def resource = resourceResolver.getResource(path) 118 | 119 | expect: 120 | resourceResolver.getResource(path) != null 121 | 122 | when: 123 | resourceResolver.delete(resource) 124 | 125 | then: 126 | resourceResolver.getResource(path) == null 127 | } 128 | 129 | } 130 | -------------------------------------------------------------------------------- /src/main/java/com/twcable/jackalope/impl/jcr/ValueFactoryImpl.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Time Warner Cable, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.twcable.jackalope.impl.jcr; 18 | 19 | import javax.jcr.Binary; 20 | import javax.jcr.Node; 21 | import javax.jcr.PropertyType; 22 | import javax.jcr.RepositoryException; 23 | import javax.jcr.Value; 24 | import javax.jcr.ValueFactory; 25 | import javax.jcr.ValueFormatException; 26 | import java.io.IOException; 27 | import java.io.InputStream; 28 | import java.math.BigDecimal; 29 | import java.util.Calendar; 30 | 31 | /** 32 | * Simple implementation of an {@link ValueFactory} 33 | */ 34 | public class ValueFactoryImpl implements ValueFactory { 35 | 36 | @Override 37 | public Value createValue(String value) { 38 | return new ValueImpl(PropertyType.STRING, value); 39 | } 40 | 41 | 42 | @Override 43 | public Value createValue(String value, int type) throws ValueFormatException { 44 | switch (type) { 45 | case PropertyType.STRING: 46 | return createValue(value); 47 | case PropertyType.LONG: 48 | return createValue(Long.valueOf(value)); 49 | case PropertyType.DOUBLE: 50 | return createValue(Double.valueOf(value)); 51 | case PropertyType.BOOLEAN: 52 | return createValue(Boolean.valueOf(value)); 53 | case PropertyType.DECIMAL: 54 | return createValue(new BigDecimal(value)); 55 | case PropertyType.DATE: // TODO: parse dates 56 | case PropertyType.BINARY: 57 | return createValue(createBinary(value)); 58 | default: 59 | return null; 60 | } 61 | } 62 | 63 | 64 | @Override 65 | public Value createValue(long value) { 66 | return new ValueImpl(PropertyType.LONG, value); 67 | } 68 | 69 | 70 | @Override 71 | public Value createValue(double value) { 72 | return new ValueImpl(PropertyType.DOUBLE, value); 73 | } 74 | 75 | 76 | @Override 77 | public Value createValue(BigDecimal value) { 78 | return new ValueImpl(PropertyType.DECIMAL, value); 79 | } 80 | 81 | 82 | @Override 83 | public Value createValue(boolean value) { 84 | return new ValueImpl(PropertyType.BOOLEAN, value); 85 | } 86 | 87 | 88 | @Override 89 | public Value createValue(Calendar value) { 90 | return new ValueImpl(PropertyType.DATE, value); 91 | } 92 | 93 | 94 | @Override 95 | public Value createValue(InputStream value) { 96 | try { 97 | return createValue(createBinary(value)); 98 | } 99 | catch (RepositoryException re) { 100 | return createValue(new BinaryImpl(new byte[0])); 101 | } 102 | } 103 | 104 | 105 | @Override 106 | public Value createValue(Binary value) { 107 | return new ValueImpl(PropertyType.BINARY, value); 108 | } 109 | 110 | 111 | @Override 112 | public Value createValue(Node value) throws RepositoryException { 113 | return null; 114 | } 115 | 116 | 117 | @Override 118 | public Value createValue(Node value, boolean weak) throws RepositoryException { 119 | return null; 120 | } 121 | 122 | 123 | @Override 124 | public Binary createBinary(InputStream stream) throws RepositoryException { 125 | Binary b = new BinaryImpl(stream); 126 | try { 127 | stream.close(); 128 | } 129 | catch (IOException ioe) { /* ignore */ } 130 | return b; 131 | } 132 | 133 | 134 | public Binary createBinary(String s) { 135 | return new BinaryImpl(s.getBytes()); 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /gradle/ide.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'idea' 2 | apply plugin: 'eclipse' 3 | 4 | //************************************************************************* 5 | // 6 | // IntelliJ IDEA configuration 7 | // 8 | //************************************************************************* 9 | 10 | idea.module { 11 | downloadJavadoc = true 12 | downloadSources = true 13 | } 14 | 15 | idea.project { 16 | vcs = 'Git' 17 | 18 | ipr { 19 | withXml { provider -> 20 | def codeStyleNode = provider.node.component.find { it.@name == 'ProjectCodeStyleSettingsManager' } 21 | if (codeStyleNode == null) { 22 | codeStyleNode = provider.node.appendNode('component', [name: 'ProjectCodeStyleSettingsManager']) 23 | } 24 | codeStyleNode.replaceNode { node -> 25 | component(name: 'ProjectCodeStyleSettingsManager') { 26 | option(name: "PER_PROJECT_SETTINGS") { 27 | value { 28 | option(name: "OTHER_INDENT_OPTIONS") { 29 | value { 30 | option(name: "INDENT_SIZE", value: "4") 31 | option(name: "CONTINUATION_INDENT_SIZE", value: "4") 32 | option(name: "TAB_SIZE", value: "4") 33 | option(name: "USE_TAB_CHARACTER", value: "false") 34 | option(name: "SMART_TABS", value: "false") 35 | option(name: "LABEL_INDENT_SIZE", value: "0") 36 | option(name: "LABEL_INDENT_ABSOLUTE", value: "false") 37 | option(name: "USE_RELATIVE_INDENTS", value: "false") 38 | } 39 | } 40 | option(name: "CLASS_COUNT_TO_USE_IMPORT_ON_DEMAND", value: "9999") 41 | option(name: "NAMES_COUNT_TO_USE_IMPORT_ON_DEMAND", value: "9999") 42 | XML { 43 | option(name: "XML_LEGACY_SETTINGS_IMPORTED", value: "true") 44 | } 45 | 46 | // this is needed in addition to the one below, for import settings 47 | GroovyCodeStyleSettings { 48 | option(name: "CLASS_COUNT_TO_USE_IMPORT_ON_DEMAND", value: "9999") 49 | option(name: "NAMES_COUNT_TO_USE_IMPORT_ON_DEMAND", value: "9999") 50 | } 51 | 52 | // oddly, both "JAVA" and "Java" are used... 53 | ['Groovy', 'JAVA', 'Java', 'Scala'].each { 54 | codeStyleSettings(language: it) { 55 | option(name: "CLASS_COUNT_TO_USE_IMPORT_ON_DEMAND", value: "9999") 56 | option(name: "NAMES_COUNT_TO_USE_IMPORT_ON_DEMAND", value: "9999") 57 | option(name: "BLANK_LINES_AROUND_METHOD", value: "2") 58 | //option(name: "BLANK_LINES_BEFORE_METHOD_BODY", value: "1") 59 | option(name: "ELSE_ON_NEW_LINE", value: "true") 60 | option(name: "CATCH_ON_NEW_LINE", value: "true") 61 | option(name: "FINALLY_ON_NEW_LINE", value: "true") 62 | option(name: "SPACE_AFTER_TYPE_CAST", value: "false") 63 | option(name: "INDENT_SIZE", value: "2") 64 | option(name: "TAB_SIZE", value: "4") 65 | 66 | // both this level and 'indentOptions' are used 67 | option(name: "CONTINUATION_INDENT_SIZE", value: "4") 68 | indentOptions { 69 | option(name: "CONTINUATION_INDENT_SIZE", value: "4") 70 | } 71 | } 72 | } 73 | } 74 | } 75 | option(name: "USE_PER_PROJECT_SETTINGS", value: "true") 76 | } 77 | } 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/main/java/com/twcable/jackalope/impl/jcr/NodeTypeImpl.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Time Warner Cable, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.twcable.jackalope.impl.jcr; 18 | 19 | import javax.jcr.Value; 20 | import javax.jcr.nodetype.NodeDefinition; 21 | import javax.jcr.nodetype.NodeType; 22 | import javax.jcr.nodetype.NodeTypeIterator; 23 | import javax.jcr.nodetype.PropertyDefinition; 24 | 25 | /** 26 | * Simple implementation of an {@link NodeType} 27 | */ 28 | public class NodeTypeImpl implements NodeType { 29 | private final String nodeTypeName; 30 | 31 | 32 | public NodeTypeImpl(String nodeTypeName) { 33 | this.nodeTypeName = nodeTypeName; 34 | } 35 | 36 | 37 | @Override 38 | public NodeType[] getSupertypes() { 39 | return new NodeType[0]; 40 | } 41 | 42 | 43 | @Override 44 | public NodeType[] getDeclaredSupertypes() { 45 | return new NodeType[0]; 46 | } 47 | 48 | 49 | @Override 50 | public NodeTypeIterator getSubtypes() { 51 | return null; 52 | } 53 | 54 | 55 | @Override 56 | public NodeTypeIterator getDeclaredSubtypes() { 57 | return null; 58 | } 59 | 60 | 61 | @Override 62 | public boolean isNodeType(String nodeTypeName) { 63 | return this.nodeTypeName.equals(nodeTypeName); //To change body of implemented methods use File | Settings | File Templates. 64 | } 65 | 66 | 67 | @Override 68 | public PropertyDefinition[] getPropertyDefinitions() { 69 | return new PropertyDefinition[0]; 70 | } 71 | 72 | 73 | @Override 74 | public NodeDefinition[] getChildNodeDefinitions() { 75 | return new NodeDefinition[0]; 76 | } 77 | 78 | 79 | @Override 80 | public boolean canSetProperty(String propertyName, Value value) { 81 | return true; 82 | } 83 | 84 | 85 | @Override 86 | public boolean canSetProperty(String propertyName, Value[] values) { 87 | return true; 88 | } 89 | 90 | 91 | @Override 92 | public boolean canAddChildNode(String childNodeName) { 93 | return true; 94 | } 95 | 96 | 97 | @Override 98 | public boolean canAddChildNode(String childNodeName, String nodeTypeName) { 99 | return true; 100 | } 101 | 102 | 103 | @Override 104 | public boolean canRemoveItem(String itemName) { 105 | return true; 106 | } 107 | 108 | 109 | @Override 110 | public boolean canRemoveNode(String nodeName) { 111 | return true; 112 | } 113 | 114 | 115 | @Override 116 | public boolean canRemoveProperty(String propertyName) { 117 | return true; 118 | } 119 | 120 | 121 | @Override 122 | public String getName() { 123 | return nodeTypeName; 124 | } 125 | 126 | 127 | @Override 128 | public String[] getDeclaredSupertypeNames() { 129 | return new String[0]; 130 | } 131 | 132 | 133 | @Override 134 | public boolean isAbstract() { 135 | return false; 136 | } 137 | 138 | 139 | @Override 140 | public boolean isMixin() { 141 | return false; 142 | } 143 | 144 | 145 | @Override 146 | public boolean hasOrderableChildNodes() { 147 | return false; 148 | } 149 | 150 | 151 | @Override 152 | public boolean isQueryable() { 153 | return false; 154 | } 155 | 156 | 157 | @Override 158 | public String getPrimaryItemName() { 159 | return null; 160 | } 161 | 162 | 163 | @Override 164 | public PropertyDefinition[] getDeclaredPropertyDefinitions() { 165 | return new PropertyDefinition[0]; 166 | } 167 | 168 | 169 | @Override 170 | public NodeDefinition[] getDeclaredChildNodeDefinitions() { 171 | return new NodeDefinition[0]; 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /src/main/java/com/twcable/jackalope/impl/jcr/ItemImpl.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Time Warner Cable, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.twcable.jackalope.impl.jcr; 18 | 19 | import com.google.common.base.Strings; 20 | import com.twcable.jackalope.impl.common.Paths; 21 | 22 | import javax.annotation.Nonnull; 23 | import javax.annotation.Nullable; 24 | import javax.jcr.AccessDeniedException; 25 | import javax.jcr.InvalidItemStateException; 26 | import javax.jcr.Item; 27 | import javax.jcr.ItemExistsException; 28 | import javax.jcr.ItemNotFoundException; 29 | import javax.jcr.ItemVisitor; 30 | import javax.jcr.Node; 31 | import javax.jcr.RepositoryException; 32 | import javax.jcr.Session; 33 | import javax.jcr.lock.LockException; 34 | import javax.jcr.nodetype.ConstraintViolationException; 35 | import javax.jcr.version.VersionException; 36 | import java.util.Objects; 37 | 38 | /** 39 | * In memory JCR Item 40 | */ 41 | @SuppressWarnings("DuplicateThrows") 42 | public abstract class ItemImpl implements Item { 43 | protected final SessionImpl session; 44 | private String path; 45 | 46 | 47 | /** 48 | * Construct an implementation of the jcr Item interface. 49 | * 50 | * @param session The session for which this Item is being constructed 51 | * @param path The jcr path of this item 52 | * @throws ItemNotFoundException 53 | * @throws ItemExistsException 54 | */ 55 | ItemImpl(@Nonnull SessionImpl session, @Nonnull String path) throws ItemNotFoundException, ItemExistsException { 56 | this.session = session; 57 | this.path = path; 58 | session.addItem(this); 59 | } 60 | 61 | 62 | @Override 63 | @Nonnull 64 | public String getPath() { 65 | return path; 66 | } 67 | 68 | 69 | void setPath(String path) { 70 | this.path = path; 71 | } 72 | 73 | 74 | @Override 75 | @Nonnull 76 | public String getName() { 77 | return Paths.basename(path); 78 | } 79 | 80 | 81 | @Override 82 | @Nullable 83 | public Item getAncestor(int depth) throws ItemNotFoundException, RepositoryException { 84 | int myDepth = getDepth(); 85 | if (depth > myDepth) throw new ItemNotFoundException(); 86 | return (depth < myDepth) ? ((NodeImpl)getParent()).getAncestor(depth) : null; 87 | } 88 | 89 | 90 | @Override 91 | @Nonnull 92 | public Node getParent() throws ItemNotFoundException, RepositoryException { 93 | if (session.getRootNode() == this) throw new ItemNotFoundException(); 94 | if (Strings.isNullOrEmpty(Paths.parent(path))) return session.getRootNode(); 95 | return session.getNode(Paths.parent(path)); 96 | } 97 | 98 | 99 | ItemImpl getParentImpl() { 100 | try { 101 | return (ItemImpl)getParent(); 102 | } 103 | catch (RepositoryException re) { 104 | return null; 105 | } 106 | } 107 | 108 | 109 | @Override 110 | public int getDepth() { 111 | return Paths.depth(path); 112 | } 113 | 114 | 115 | @Override 116 | public Session getSession() { 117 | return session; 118 | } 119 | 120 | 121 | @Override 122 | abstract public boolean isNode(); 123 | 124 | 125 | @Override 126 | public boolean isNew() { 127 | return session.isNew(this); 128 | } 129 | 130 | 131 | @Override 132 | public boolean isModified() { 133 | return session.isModified(this); 134 | } 135 | 136 | 137 | @Override 138 | public boolean isSame(Item otherItem) throws RepositoryException { 139 | return Objects.equals(path, otherItem.getPath()) 140 | && (isNode() == otherItem.isNode()) 141 | && (isNode() || getParent().isSame(otherItem.getParent())); 142 | } 143 | 144 | 145 | @Override 146 | public abstract void accept(ItemVisitor visitor) throws RepositoryException; 147 | 148 | 149 | @Override 150 | public void save() { 151 | session.save(this); 152 | } 153 | 154 | 155 | @Override 156 | public void refresh(boolean keepChanges) throws InvalidItemStateException, RepositoryException { 157 | //Not implemented 158 | } 159 | 160 | 161 | @Override 162 | public void remove() throws VersionException, LockException, ConstraintViolationException, AccessDeniedException, RepositoryException { 163 | session.removeItem(this); 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /src/main/java/com/twcable/jackalope/impl/cq/AssetImpl.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Time Warner Cable, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.twcable.jackalope.impl.cq; 18 | 19 | import com.day.cq.dam.api.Asset; 20 | import com.day.cq.dam.api.Rendition; 21 | import com.day.cq.dam.api.RenditionPicker; 22 | import com.day.cq.dam.api.Revision; 23 | import org.apache.sling.api.resource.Resource; 24 | 25 | import java.io.InputStream; 26 | import java.util.Calendar; 27 | import java.util.Collection; 28 | import java.util.Iterator; 29 | import java.util.List; 30 | import java.util.Map; 31 | 32 | 33 | /** 34 | * Simple implementation of an {@link Asset} 35 | */ 36 | public class AssetImpl implements Asset { 37 | private final Resource resource; 38 | 39 | 40 | public AssetImpl(Resource resource) { 41 | this.resource = resource; 42 | } 43 | 44 | 45 | @Override 46 | public String getPath() { 47 | return resource.getPath(); 48 | } 49 | 50 | 51 | @Override 52 | public String getName() { 53 | return resource.getName(); 54 | } 55 | 56 | 57 | @Override 58 | public long getLastModified() { 59 | return System.currentTimeMillis(); 60 | } 61 | 62 | 63 | @Override 64 | public Rendition getRendition(String s) { 65 | return null; 66 | } 67 | 68 | 69 | @Override 70 | public Rendition getOriginal() { 71 | return new RenditionImpl(this, resource.getChild("jcr:content/renditions/original")); 72 | } 73 | 74 | 75 | @Override 76 | public Rendition getCurrentOriginal() { 77 | return getOriginal(); 78 | } 79 | 80 | 81 | @Override 82 | public boolean isSubAsset() { 83 | return false; 84 | } 85 | 86 | 87 | @Override 88 | public Map getMetadata() { 89 | return null; 90 | } 91 | 92 | 93 | @Override 94 | public Resource setRendition(String s, InputStream inputStream, String s2) { 95 | return null; 96 | } 97 | 98 | 99 | @Override 100 | public void setCurrentOriginal(String s) { 101 | 102 | } 103 | 104 | 105 | @Override 106 | public Revision createRevision(String s, String s2) throws Exception { 107 | return null; 108 | } 109 | 110 | 111 | @Override 112 | public List getRenditions() { 113 | return null; 114 | } 115 | 116 | 117 | @Override 118 | public Iterator listRenditions() { 119 | return null; 120 | } 121 | 122 | 123 | @Override 124 | public Rendition getRendition(RenditionPicker renditionPicker) { 125 | return null; 126 | } 127 | 128 | 129 | @Override 130 | public String getModifier() { 131 | return null; 132 | } 133 | 134 | 135 | @Override 136 | public Asset restore(String s) throws Exception { 137 | return null; 138 | } 139 | 140 | 141 | @Override 142 | public Collection getRevisions(Calendar calendar) throws Exception { 143 | return null; 144 | } 145 | 146 | 147 | @Override 148 | public String getMimeType() { 149 | return null; 150 | } 151 | 152 | 153 | @Override 154 | public Rendition addRendition(String s, InputStream inputStream, String s2) { 155 | return null; 156 | } 157 | 158 | 159 | @Override 160 | public Rendition addRendition(String s, InputStream inputStream, Map stringObjectMap) { 161 | return null; 162 | } 163 | 164 | 165 | @Override 166 | public Asset addSubAsset(String s, String s2, InputStream inputStream) { 167 | return null; 168 | } 169 | 170 | 171 | @Override 172 | public Collection getSubAssets() { 173 | return null; 174 | } 175 | 176 | 177 | @Override 178 | public void removeRendition(String s) { 179 | 180 | } 181 | 182 | 183 | @Override 184 | public void setBatchMode(boolean b) { 185 | 186 | } 187 | 188 | 189 | @Override 190 | public boolean isBatchMode() { 191 | return false; 192 | } 193 | 194 | 195 | @Override 196 | public String getMetadataValue(String s) { 197 | return null; 198 | } 199 | 200 | 201 | @Override 202 | public Object getMetadata(String s) { 203 | return null; 204 | } 205 | 206 | 207 | @Override 208 | public AdapterType adaptTo(Class adapterTypeClass) { 209 | return null; 210 | } 211 | } 212 | -------------------------------------------------------------------------------- /src/test/groovy/com/twcable/jackalope/impl/jcr/PropertyImplSpec.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Time Warner Cable, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.twcable.jackalope.impl.jcr 18 | 19 | import spock.lang.Specification 20 | import spock.lang.Subject 21 | 22 | import javax.jcr.PropertyType 23 | import javax.jcr.Value 24 | import javax.jcr.ValueFormatException 25 | 26 | @Subject(PropertyImpl) 27 | @SuppressWarnings("GrDeprecatedAPIUsage") 28 | class PropertyImplSpec extends Specification { 29 | 30 | def "A property is not a node"() { 31 | expect: 32 | !new PropertyImpl(new SessionImpl(), "test").isNode() 33 | } 34 | 35 | 36 | def "A property has a path"() { 37 | expect: 38 | new PropertyImpl(new SessionImpl(), "test").path == "test" 39 | } 40 | 41 | 42 | def "Property can have a single value"() { 43 | def property = new PropertyImpl(new SessionImpl(), "test", (Value)new ValueImpl("hello, world")) 44 | 45 | expect: 46 | !property.isMultiple() 47 | property.value.string == "hello, world" 48 | property.type == PropertyType.STRING 49 | 50 | } 51 | 52 | 53 | def "Property can have multiple values"() { 54 | def property = new PropertyImpl(new SessionImpl(), "test", [new ValueImpl("hello"), new ValueImpl("world")] as Value[]) 55 | 56 | expect: 57 | property.isMultiple() 58 | property.values[0].string == "hello" 59 | property.values[1].string == "world" 60 | property.type == PropertyType.STRING 61 | } 62 | 63 | 64 | def "Property can have multiple String values"() { 65 | def property = new PropertyImpl(new SessionImpl(), "test", ["hello", "world"] as String[]) 66 | 67 | expect: 68 | property.isMultiple() 69 | property.values[0].string == "hello" 70 | property.values[1].string == "world" 71 | } 72 | 73 | 74 | def "The value can be set to any of the primitive values"() { 75 | def property = new PropertyImpl(new SessionImpl(), "test") 76 | 77 | when: 78 | property.setValue("hello, world") 79 | then: 80 | property.getValue().getString() == "hello, world" 81 | when: 82 | property.setValue(new ValueImpl(Double.MAX_VALUE)) 83 | then: 84 | property.getValue().getDouble() == Double.MAX_VALUE 85 | when: 86 | property.setValue(new ValueImpl(Long.MAX_VALUE)) 87 | then: 88 | property.getValue().getLong() == Long.MAX_VALUE 89 | when: 90 | property.setValue(new ValueImpl(BigDecimal.TEN)) 91 | then: 92 | property.getValue().getDecimal() == BigDecimal.TEN 93 | when: 94 | property.setValue(new ValueImpl(Boolean.FALSE)) 95 | then: 96 | property.getValue().getBoolean() == Boolean.FALSE 97 | when: 98 | property.setValue(new ValueImpl(Calendar.getInstance())) 99 | then: 100 | property.getValue().getDate().getTime().date == Calendar.getInstance().getTime().date 101 | } 102 | 103 | 104 | def "The length of a single value property is the length of the string representation of that property"() { 105 | expect: 106 | new PropertyImpl(new SessionImpl(), "test", value).getLength() == length 107 | 108 | where: 109 | value | length 110 | new ValueImpl("hello") | 5 111 | new ValueImpl(Double.valueOf(2.5d)) | 3 112 | new ValueImpl(Long.valueOf(10l)) | 2 113 | new ValueImpl(BigDecimal.valueOf(5000l)) | 4 114 | new ValueImpl(Boolean.TRUE) | 4 115 | // Todo: fix calendar strings 116 | } 117 | 118 | 119 | def "getLength throws an exception when the property is multi-valued"() { 120 | when: 121 | new PropertyImpl(new SessionImpl(), "test", ["a", "b"] as String[]).length 122 | 123 | then: 124 | thrown(ValueFormatException) 125 | } 126 | 127 | 128 | def "The length of a multi-value property is the length of the string representation of that property"() { 129 | expect: 130 | new PropertyImpl(new SessionImpl(), "test", "a", "bbb").lengths == [1, 3] as long[] 131 | } 132 | 133 | 134 | def "getLengths throws an exception when the property is single value"() { 135 | when: 136 | new PropertyImpl(new SessionImpl(), "test", new ValueImpl("hello")).lengths 137 | 138 | then: 139 | thrown(ValueFormatException) 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /src/main/java/com/twcable/jackalope/impl/jcr/ValueImpl.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Time Warner Cable, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.twcable.jackalope.impl.jcr; 18 | 19 | import javax.jcr.Binary; 20 | import javax.jcr.PropertyType; 21 | import javax.jcr.RepositoryException; 22 | import javax.jcr.Value; 23 | import javax.jcr.ValueFormatException; 24 | import java.io.InputStream; 25 | import java.math.BigDecimal; 26 | import java.util.Calendar; 27 | import java.util.Objects; 28 | 29 | /** 30 | * In memory dummy value for testing 31 | */ 32 | // TODO: Implement the various conversions 33 | // TODO: Implement proper calendar getString 34 | @SuppressWarnings("DuplicateThrows") 35 | public class ValueImpl implements Value { 36 | private final int type; 37 | private final Object valueObject; 38 | 39 | 40 | /** 41 | * Construct an implementation of the jcr Value interface 42 | * 43 | * @param value The value to be stored 44 | * @throws IllegalArgumentException If the type of Value can't be converted to a valid jcr value type. 45 | */ 46 | public ValueImpl(Object value) throws IllegalArgumentException { 47 | this(selectPropertyType(value), value); 48 | } 49 | 50 | 51 | private static int selectPropertyType(Object value) { 52 | return (value instanceof String) ? PropertyType.STRING : 53 | (value instanceof Long) ? PropertyType.LONG : 54 | (value instanceof Double) ? PropertyType.DOUBLE : 55 | (value instanceof BigDecimal) ? PropertyType.DECIMAL : 56 | (value instanceof Calendar) ? PropertyType.DATE : 57 | (value instanceof Boolean) ? PropertyType.BOOLEAN : 58 | (value instanceof Binary) ? PropertyType.BINARY : 59 | PropertyType.UNDEFINED; 60 | } 61 | 62 | 63 | /** 64 | * Construct an implementation of the jcr Value interface 65 | * 66 | * @param type The type of value to be stored 67 | * @param value The value to be stored 68 | * @throws IllegalArgumentException If the type of Value can't be converted to a valid jcr value type. 69 | */ 70 | public ValueImpl(int type, Object value) throws IllegalArgumentException { 71 | this.type = type; 72 | this.valueObject = value; 73 | } 74 | 75 | 76 | @Override 77 | public String getString() throws ValueFormatException, IllegalStateException, RepositoryException { 78 | return valueObject.toString(); 79 | } 80 | 81 | 82 | @Override 83 | public InputStream getStream() throws RepositoryException { 84 | return null; // Deprecated 85 | } 86 | 87 | 88 | @Override 89 | public Binary getBinary() throws RepositoryException { 90 | if (type != PropertyType.BINARY) throw new ValueFormatException(); 91 | return (Binary)valueObject; 92 | } 93 | 94 | 95 | @Override 96 | public long getLong() throws ValueFormatException, RepositoryException { 97 | if (type != PropertyType.LONG) throw new ValueFormatException(); 98 | return (long)valueObject; 99 | } 100 | 101 | 102 | @Override 103 | public double getDouble() throws ValueFormatException, RepositoryException { 104 | if (type != PropertyType.DOUBLE) throw new ValueFormatException(); 105 | return (double)valueObject; 106 | } 107 | 108 | 109 | @Override 110 | public BigDecimal getDecimal() throws ValueFormatException, RepositoryException { 111 | if (type != PropertyType.DECIMAL) throw new ValueFormatException(); 112 | return (BigDecimal)valueObject; 113 | } 114 | 115 | 116 | @Override 117 | public Calendar getDate() throws ValueFormatException, RepositoryException { 118 | if (type != PropertyType.DATE) throw new ValueFormatException(); 119 | return (Calendar)valueObject; 120 | } 121 | 122 | 123 | @Override 124 | public boolean getBoolean() throws ValueFormatException, RepositoryException { 125 | if (type != PropertyType.BOOLEAN) throw new ValueFormatException(); 126 | return (boolean)valueObject; 127 | } 128 | 129 | 130 | @Override 131 | public int getType() { 132 | return type; 133 | } 134 | 135 | 136 | @Override 137 | @SuppressWarnings("SimplifiableIfStatement") 138 | public boolean equals(Object o) { 139 | if (this == o) return true; 140 | if (o == null || getClass() != o.getClass()) return false; 141 | 142 | ValueImpl valueImpl = (ValueImpl)o; 143 | 144 | if (type != valueImpl.type) return false; 145 | 146 | return Objects.equals(valueObject, valueImpl.valueObject); 147 | } 148 | 149 | 150 | @Override 151 | public int hashCode() { 152 | int result = type; 153 | result = 31 * result + (valueObject != null ? valueObject.hashCode() : 0); 154 | return result; 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /src/test/groovy/com/twcable/jackalope/impl/common/PathsSpec.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Time Warner Cable, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.twcable.jackalope.impl.common 18 | 19 | import spock.lang.Specification 20 | import spock.lang.Subject 21 | 22 | @Subject(Paths) 23 | class PathsSpec extends Specification { 24 | 25 | def "head returns the first segment of the path"() { 26 | expect: 27 | Paths.head(input) == expected 28 | 29 | where: 30 | input | expected 31 | "" | "" 32 | "/" | "" 33 | "segment" | "segment" 34 | "segment/next" | "segment" 35 | "/segment" | "segment" 36 | } 37 | 38 | 39 | def "tail returns the segments of the path after the first"() { 40 | expect: 41 | Paths.tail(input) == expected 42 | 43 | where: 44 | input | expected 45 | "" | "" 46 | "/" | "" 47 | "/segment" | "" 48 | "segment" | "" 49 | "/segment/next" | "next" 50 | "segment/next" | "next" 51 | "segment/next/onemore" | "next/onemore" 52 | } 53 | 54 | 55 | def "parent returns the segments of the path before the last segment"() { 56 | expect: 57 | Paths.parent(input) == expected 58 | 59 | where: 60 | input | expected 61 | "" | "" 62 | "/" | "" 63 | "/segment" | "/" 64 | "segment" | "" 65 | "/segment/next" | "/segment" 66 | "segment/next" | "segment" 67 | "segment/next/onemore" | "segment/next" 68 | } 69 | 70 | 71 | def "basename returns the last segment of the path"() { 72 | expect: 73 | Paths.basename(input) == expected 74 | 75 | where: 76 | input | expected 77 | "" | "" 78 | "/" | "" 79 | "/segment" | "segment" 80 | "segment" | "segment" 81 | "/segment/next" | "next" 82 | "segment/next" | "next" 83 | "segment/next/onemore" | "onemore" 84 | } 85 | 86 | 87 | def "ancestorOf returns true if the first path is an ancestor of the second path"() { 88 | expect: 89 | Paths.ancestorOf(first, second) == expected 90 | 91 | where: 92 | first | second | expected 93 | "" | "" | false 94 | "" | "/" | false 95 | "/" | "/segment" | true 96 | "/segment" | "/segment" | false 97 | "segment" | "segment/next" | true 98 | "a/b/c" | "a/b/c/d/e/f" | true 99 | } 100 | 101 | 102 | def "selfOrAncestorOf returns true if the first path is or is an ancestor of the second path"() { 103 | expect: 104 | Paths.selfOrAncestorOf(first, second) == expected 105 | 106 | where: 107 | first | second | expected 108 | "" | "" | false 109 | "" | "/" | false 110 | "/" | "/segment" | true 111 | "/segment" | "/segment" | true 112 | "segment" | "segment" | true 113 | "segment" | "segment/next" | true 114 | "a/b/c" | "a/b/c/d/e/f" | true 115 | } 116 | 117 | 118 | def "depth returns the number of segments in the path"() { 119 | expect: 120 | Paths.depth(input) == expected 121 | 122 | where: 123 | input | expected 124 | "" | 0 125 | "/" | 0 126 | "/segment" | 1 127 | "segment" | 1 128 | "/segment/next" | 2 129 | "segment/next" | 2 130 | } 131 | 132 | 133 | def "isAbsolute returns true if the path begins with a /"() { 134 | expect: 135 | Paths.isAbsolute(input) == expected 136 | 137 | where: 138 | input | expected 139 | "" | false 140 | "/" | true 141 | "/segment" | true 142 | "segment" | false 143 | "/segment/next" | true 144 | "segment/next" | false 145 | } 146 | 147 | 148 | def "isRoot returns true if the path is /"() { 149 | expect: 150 | Paths.isRoot(input) == expected 151 | 152 | where: 153 | input | expected 154 | "" | false 155 | "/" | true 156 | "/segment" | false 157 | "/segment/next" | false 158 | } 159 | 160 | 161 | def "resolve joins paths"() { 162 | expect: 163 | Paths.resolve(first, second) == expected 164 | 165 | where: 166 | first | second | expected 167 | "" | "segment" | "segment" 168 | "/" | "segment" | "/segment" 169 | "/segment" | "next" | "/segment/next" 170 | "/segment" | "/next" | "/next" 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /src/main/java/com/twcable/jackalope/impl/common/Paths.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Time Warner Cable, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.twcable.jackalope.impl.common; 18 | 19 | import com.google.common.base.Strings; 20 | 21 | /** 22 | * Utilities for manipulating path strings. 23 | */ 24 | public final class Paths { 25 | public final static String SEPARATOR = "/"; 26 | 27 | 28 | private Paths() { 29 | } 30 | 31 | 32 | /** 33 | * Returns the first non-root segment of the path. 34 | * 35 | * @param path The path 36 | * @return The first segment of the path 37 | */ 38 | public static String head(String path) { 39 | path = stripRoot(path); 40 | return path.contains(SEPARATOR) ? path.substring(0, path.indexOf(SEPARATOR)) : path; 41 | } 42 | 43 | 44 | /** 45 | * Returns the segments of the path after the first non-root segment. 46 | * 47 | * @param path The path 48 | * @return The segments of the path after the first 49 | */ 50 | public static String tail(String path) { 51 | path = stripRoot(path); 52 | return path.contains(SEPARATOR) ? path.substring(path.indexOf(SEPARATOR) + 1) : ""; 53 | } 54 | 55 | 56 | /** 57 | * Returns the segments of the path before the last segment 58 | * 59 | * @param path The path 60 | * @return The segments of the path before the last segment 61 | */ 62 | public static String parent(String path) { 63 | String prefix = isAbsolute(path) && !isRoot(path) ? "/" : ""; 64 | path = stripRoot(path); 65 | return prefix + (path.contains(SEPARATOR) ? path.substring(0, path.lastIndexOf(SEPARATOR)) : ""); 66 | } 67 | 68 | 69 | /** 70 | * Returns the last non-root segment of the path 71 | * 72 | * @param path The path 73 | * @return The last segment of the path 74 | */ 75 | public static String basename(String path) { 76 | return path.contains(SEPARATOR) ? path.substring(path.lastIndexOf(SEPARATOR) + 1) : path; 77 | } 78 | 79 | 80 | /** 81 | * Returns true if the first path is an ancestor of the second path 82 | * 83 | * @param first The first path 84 | * @param second The second path 85 | * @return True if the first path is an ancestor of the second path 86 | */ 87 | public static boolean ancestorOf(String first, String second) { 88 | return !Strings.isNullOrEmpty(first) && !first.equals(second) && second.startsWith(first); 89 | } 90 | 91 | 92 | /** 93 | * Returns true if the first path is an ancestor of the second path 94 | * 95 | * @param first The first path 96 | * @param second The second path 97 | * @return True if the first path is an ancestor of the second path 98 | */ 99 | public static boolean selfOrAncestorOf(String first, String second) { 100 | return (!Strings.isNullOrEmpty(first) && first.equals(second)) || ancestorOf(first, second); 101 | } 102 | 103 | 104 | /** 105 | * Returns the number of segments in the path 106 | * 107 | * @param path The path 108 | * @return The number of segments in the path. Returns 0 if the path is empty or the root path. 109 | */ 110 | public static int depth(String path) { 111 | path = stripRoot(path); 112 | return !Strings.isNullOrEmpty(path) ? path.split(SEPARATOR).length : 0; 113 | } 114 | 115 | 116 | /** 117 | * Returns true it the path is an absolute path (i.e. starts with '/') 118 | * 119 | * @param path The path 120 | * @return True it the path is an absolute path (i.e. starts with '/') 121 | */ 122 | public static boolean isAbsolute(String path) { 123 | return path.startsWith("/"); 124 | } 125 | 126 | 127 | /** 128 | * Returns true it the path is the root path (i.e. '/') 129 | * 130 | * @param path The path 131 | * @return True it the path is the root path (i.e. '/') 132 | */ 133 | public static boolean isRoot(String path) { 134 | return path.equals("/"); 135 | } 136 | 137 | 138 | /** 139 | * Resolves the second path using the first path as context. 140 | * 141 | * @param first The path to be used as the context 142 | * @param second The target path 143 | * @return If the second path is absolute, it is returned. Otherwise, the return path is constructed by appending 144 | * the second (child) path to the first (parent) path. 145 | */ 146 | //TODO: Normalize the paths 147 | public static String resolve(String first, String second) { 148 | return isAbsolute(second) ? second : 149 | Strings.isNullOrEmpty(first) || isRoot(first) ? first + second : 150 | first + SEPARATOR + second; 151 | } 152 | 153 | 154 | /** 155 | * Returns the path with the root element removed 156 | * 157 | * @param path The path to be modified 158 | * @return The path with the root element removed. 159 | */ 160 | private static String stripRoot(String path) { 161 | return isAbsolute(path) ? path.substring(1) : path; 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # For Cygwin, ensure paths are in UNIX format before anything is touched. 46 | if $cygwin ; then 47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 48 | fi 49 | 50 | # Attempt to set APP_HOME 51 | # Resolve links: $0 may be a link 52 | PRG="$0" 53 | # Need this for relative symlinks. 54 | while [ -h "$PRG" ] ; do 55 | ls=`ls -ld "$PRG"` 56 | link=`expr "$ls" : '.*-> \(.*\)$'` 57 | if expr "$link" : '/.*' > /dev/null; then 58 | PRG="$link" 59 | else 60 | PRG=`dirname "$PRG"`"/$link" 61 | fi 62 | done 63 | SAVED="`pwd`" 64 | cd "`dirname \"$PRG\"`/" >&- 65 | APP_HOME="`pwd -P`" 66 | cd "$SAVED" >&- 67 | 68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 69 | 70 | # Determine the Java command to use to start the JVM. 71 | if [ -n "$JAVA_HOME" ] ; then 72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 73 | # IBM's JDK on AIX uses strange locations for the executables 74 | JAVACMD="$JAVA_HOME/jre/sh/java" 75 | else 76 | JAVACMD="$JAVA_HOME/bin/java" 77 | fi 78 | if [ ! -x "$JAVACMD" ] ; then 79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 80 | 81 | Please set the JAVA_HOME variable in your environment to match the 82 | location of your Java installation." 83 | fi 84 | else 85 | JAVACMD="java" 86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 87 | 88 | Please set the JAVA_HOME variable in your environment to match the 89 | location of your Java installation." 90 | fi 91 | 92 | # Increase the maximum file descriptors if we can. 93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 94 | MAX_FD_LIMIT=`ulimit -H -n` 95 | if [ $? -eq 0 ] ; then 96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 97 | MAX_FD="$MAX_FD_LIMIT" 98 | fi 99 | ulimit -n $MAX_FD 100 | if [ $? -ne 0 ] ; then 101 | warn "Could not set maximum file descriptor limit: $MAX_FD" 102 | fi 103 | else 104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 105 | fi 106 | fi 107 | 108 | # For Darwin, add options to specify how the application appears in the dock 109 | if $darwin; then 110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 111 | fi 112 | 113 | # For Cygwin, switch paths to Windows format before running java 114 | if $cygwin ; then 115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 158 | function splitJvmOpts() { 159 | JVM_OPTS=("$@") 160 | } 161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 163 | 164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 165 | -------------------------------------------------------------------------------- /src/main/java/com/twcable/jackalope/impl/jcr/WorkspaceImpl.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Time Warner Cable, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.twcable.jackalope.impl.jcr; 18 | 19 | import org.xml.sax.ContentHandler; 20 | 21 | import javax.jcr.AccessDeniedException; 22 | import javax.jcr.InvalidItemStateException; 23 | import javax.jcr.InvalidSerializedDataException; 24 | import javax.jcr.ItemExistsException; 25 | import javax.jcr.NamespaceRegistry; 26 | import javax.jcr.NoSuchWorkspaceException; 27 | import javax.jcr.PathNotFoundException; 28 | import javax.jcr.RepositoryException; 29 | import javax.jcr.Session; 30 | import javax.jcr.UnsupportedRepositoryOperationException; 31 | import javax.jcr.Workspace; 32 | import javax.jcr.lock.LockException; 33 | import javax.jcr.lock.LockManager; 34 | import javax.jcr.nodetype.ConstraintViolationException; 35 | import javax.jcr.nodetype.NodeTypeManager; 36 | import javax.jcr.observation.ObservationManager; 37 | import javax.jcr.query.QueryManager; 38 | import javax.jcr.version.Version; 39 | import javax.jcr.version.VersionException; 40 | import javax.jcr.version.VersionManager; 41 | import java.io.IOException; 42 | import java.io.InputStream; 43 | 44 | /** 45 | * Simple implementation of {@link Workspace} 46 | */ 47 | @SuppressWarnings("DuplicateThrows") 48 | public class WorkspaceImpl implements Workspace { 49 | private Session session = null; 50 | private ObservationManager observationManager = null; 51 | 52 | protected QueryManager queryManager = null; 53 | 54 | 55 | @Override 56 | public Session getSession() { 57 | return session; 58 | } 59 | 60 | 61 | @Override 62 | public String getName() { 63 | return null; 64 | } 65 | 66 | 67 | @Override 68 | public void copy(String srcAbsPath, String destAbsPath) throws ConstraintViolationException, VersionException, AccessDeniedException, PathNotFoundException, ItemExistsException, LockException, RepositoryException { 69 | } 70 | 71 | 72 | @Override 73 | public void copy(String srcWorkspace, String srcAbsPath, String destAbsPath) throws NoSuchWorkspaceException, ConstraintViolationException, VersionException, AccessDeniedException, PathNotFoundException, ItemExistsException, LockException, RepositoryException { 74 | } 75 | 76 | 77 | @Override 78 | public void clone(String srcWorkspace, String srcAbsPath, String destAbsPath, boolean removeExisting) throws NoSuchWorkspaceException, ConstraintViolationException, VersionException, AccessDeniedException, PathNotFoundException, ItemExistsException, LockException, RepositoryException { 79 | } 80 | 81 | 82 | @Override 83 | public void move(String srcAbsPath, String destAbsPath) throws ConstraintViolationException, VersionException, AccessDeniedException, PathNotFoundException, ItemExistsException, LockException, RepositoryException { 84 | } 85 | 86 | 87 | @Override 88 | public void restore(Version[] versions, boolean removeExisting) throws ItemExistsException, UnsupportedRepositoryOperationException, VersionException, LockException, InvalidItemStateException, RepositoryException { 89 | } 90 | 91 | 92 | @Override 93 | public LockManager getLockManager() throws UnsupportedRepositoryOperationException, RepositoryException { 94 | return null; 95 | } 96 | 97 | 98 | @Override 99 | public QueryManager getQueryManager() throws RepositoryException { 100 | return queryManager; 101 | } 102 | 103 | 104 | @Override 105 | public NamespaceRegistry getNamespaceRegistry() throws RepositoryException { 106 | return null; 107 | } 108 | 109 | 110 | @Override 111 | public NodeTypeManager getNodeTypeManager() throws RepositoryException { 112 | return null; 113 | } 114 | 115 | 116 | @Override 117 | public ObservationManager getObservationManager() throws UnsupportedRepositoryOperationException, RepositoryException { 118 | if (observationManager == null) { 119 | observationManager = new ObservationManagerImpl(); 120 | } 121 | return observationManager; 122 | } 123 | 124 | 125 | @Override 126 | public VersionManager getVersionManager() throws UnsupportedRepositoryOperationException, RepositoryException { 127 | return null; 128 | } 129 | 130 | 131 | @Override 132 | public String[] getAccessibleWorkspaceNames() throws RepositoryException { 133 | return new String[0]; 134 | } 135 | 136 | 137 | @Override 138 | public ContentHandler getImportContentHandler(String parentAbsPath, int uuidBehavior) throws PathNotFoundException, ConstraintViolationException, VersionException, LockException, AccessDeniedException, RepositoryException { 139 | return null; 140 | } 141 | 142 | 143 | @Override 144 | public void importXML(String parentAbsPath, InputStream in, int uuidBehavior) throws IOException, VersionException, PathNotFoundException, ItemExistsException, ConstraintViolationException, InvalidSerializedDataException, LockException, AccessDeniedException, RepositoryException { 145 | } 146 | 147 | 148 | @Override 149 | public void createWorkspace(String name) throws AccessDeniedException, UnsupportedRepositoryOperationException, RepositoryException { 150 | } 151 | 152 | 153 | @Override 154 | public void createWorkspace(String name, String srcWorkspace) throws AccessDeniedException, UnsupportedRepositoryOperationException, NoSuchWorkspaceException, RepositoryException { 155 | } 156 | 157 | 158 | @Override 159 | public void deleteWorkspace(String name) throws AccessDeniedException, UnsupportedRepositoryOperationException, NoSuchWorkspaceException, RepositoryException { 160 | } 161 | 162 | 163 | public void setSession(Session session) { 164 | this.session = session; 165 | } 166 | 167 | 168 | public void setQueryManager(QueryManager queryManager) { 169 | this.queryManager = queryManager; 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /src/test/groovy/com/twcable/jackalope/impl/sling/NodeResourceImplSpec.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Time Warner Cable, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.twcable.jackalope.impl.sling 18 | 19 | import com.day.cq.dam.api.Asset 20 | import com.google.common.collect.Lists 21 | import com.twcable.jackalope.impl.jcr.NodeImpl 22 | import com.twcable.jackalope.impl.jcr.SessionImpl 23 | import com.twcable.jackalope.impl.jcr.ValueFactoryImpl 24 | import org.apache.commons.io.IOUtils 25 | import org.apache.sling.api.resource.ValueMap 26 | import spock.lang.Specification 27 | import spock.lang.Subject 28 | 29 | import javax.jcr.Node 30 | 31 | import static com.twcable.jackalope.JCRBuilder.node 32 | import static com.twcable.jackalope.JCRBuilder.property 33 | import static com.twcable.jackalope.JCRBuilder.resource 34 | import static org.apache.jackrabbit.JcrConstants.JCR_PRIMARYTYPE 35 | import static org.apache.sling.jcr.resource.JcrResourceConstants.SLING_RESOURCE_TYPE_PROPERTY 36 | 37 | @Subject(NodeResourceImpl) 38 | class NodeResourceImplSpec extends Specification { 39 | SlingRepositoryImpl repository 40 | SessionImpl session 41 | 42 | 43 | def setup() { 44 | repository = new SlingRepositoryImpl() 45 | session = repository.login() as SessionImpl 46 | } 47 | 48 | 49 | def "Create a Node resource from a simple node"() { 50 | def node = new NodeImpl(session, "/test") 51 | node.setProperty(SLING_RESOURCE_TYPE_PROPERTY, "app/test/components/test") 52 | def resourceResolver = new ResourceResolverImpl(repository) 53 | 54 | when: 55 | def resource = new NodeResourceImpl(resourceResolver, node) 56 | 57 | then: 58 | resource.name == "test" 59 | resource.path == "/test" 60 | resource.parent.path == "/" 61 | resource.resourceResolver == resourceResolver 62 | resource.resourceType == "app/test/components/test" 63 | resource.isResourceType("app/test/components/test") 64 | } 65 | 66 | 67 | def "Create child resources"() { 68 | def node = new NodeImpl(session, "/test") 69 | node.addNode("child1") 70 | node.addNode("child2") 71 | def resourceResolver = new ResourceResolverImpl(repository) 72 | 73 | when: 74 | def resource = new NodeResourceImpl(resourceResolver, node) 75 | 76 | then: 77 | resource.getChild("child1").name == "child1" 78 | resource.getChild("child1").path == "/test/child1" 79 | resource.getChild("child1").parent.name == "test" 80 | resource.getChild("child2").name == "child2" 81 | resource.getChild("child2").path == "/test/child2" 82 | resource.getChild("child2").parent.name == "test" 83 | 84 | when: 85 | def actual = Lists.newArrayList(resource.listChildren()) 86 | 87 | then: 88 | actual.find { it.name == "child1" } 89 | actual.find { it.name == "child2" } 90 | } 91 | 92 | 93 | def "NodeResource can be adapted to Node"() { 94 | def resource = new NodeResourceImpl(new ResourceResolverImpl(repository), new NodeImpl(session, "/test")) 95 | def node = resource.adaptTo(Node) 96 | 97 | expect: 98 | node.name == "test" 99 | node.path == "/test" 100 | } 101 | 102 | 103 | def "NodeResource can be adapted to a ValueMap"() { 104 | def node = new NodeImpl(session, "/test") 105 | node.setProperty("string", "hello") 106 | node.setProperty("long", Long.valueOf(1000l)) 107 | def resource = new NodeResourceImpl(new ResourceResolverImpl(repository), node) 108 | def map = resource.adaptTo(ValueMap) 109 | 110 | expect: 111 | map.get("string") == "hello" 112 | map.get("long") == Long.valueOf(1000l) 113 | } 114 | 115 | 116 | def "it can be adapted to an Asset"() { 117 | def resource = resource( 118 | node("/test.csv", 119 | property(JCR_PRIMARYTYPE, "dam:Asset"), 120 | node("jcr:content", 121 | property(JCR_PRIMARYTYPE, "dam:AssetContent"), 122 | node("renditions", 123 | property(JCR_PRIMARYTYPE, "nt:folder"), 124 | node("original", 125 | property(JCR_PRIMARYTYPE, "nt:file"), 126 | node("jcr:content", 127 | property("jcr:mimeType", "text/csv"))))))).build() 128 | when: 129 | def asset = resource.adaptTo(Asset) 130 | 131 | then: 132 | asset != null 133 | asset instanceof Asset 134 | } 135 | 136 | 137 | def "it can be adapted to an InputStream"() { 138 | def resource = resource(node("original", 139 | property(JCR_PRIMARYTYPE, "nt:unstructured"), 140 | property("jcr:data", new ValueFactoryImpl().createBinary("hello, world")))).build() 141 | when: 142 | def stream = resource.adaptTo(InputStream) 143 | 144 | then: 145 | stream != null 146 | IOUtils.toString(stream) == "hello, world" 147 | } 148 | 149 | 150 | def "it uses the jcr:data property of the jcr:content node when the primary type is nt:file and it is adapted to an InputStream"() { 151 | def resource = resource(node("original", 152 | property(JCR_PRIMARYTYPE, "nt:file"), 153 | node("jcr:content", 154 | property("jcr:data", new ValueFactoryImpl().createBinary("hello, world"))))).build() 155 | when: 156 | def stream = resource.adaptTo(InputStream) 157 | 158 | then: 159 | stream != null 160 | IOUtils.toString(stream) == "hello, world" 161 | } 162 | 163 | 164 | def "returns null when adapted to an InputStream and no jcr:data"() { 165 | def resource = resource(node("original", 166 | property(JCR_PRIMARYTYPE, "nt:unstructured"))).build() 167 | when: 168 | def stream = resource.adaptTo(InputStream) 169 | 170 | then: 171 | stream == null 172 | } 173 | 174 | } 175 | -------------------------------------------------------------------------------- /src/main/java/com/twcable/jackalope/impl/sling/NodeResourceImpl.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Time Warner Cable, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.twcable.jackalope.impl.sling; 18 | 19 | import com.day.cq.dam.api.Asset; 20 | import com.day.cq.wcm.api.Page; 21 | import com.twcable.jackalope.JcrConstants; 22 | import com.twcable.jackalope.impl.common.Paths; 23 | import com.twcable.jackalope.impl.cq.AssetImpl; 24 | import com.twcable.jackalope.impl.cq.PageImpl; 25 | import com.twcable.jackalope.impl.jcr.JcrUtils; 26 | import org.apache.sling.api.resource.ModifiableValueMap; 27 | import org.apache.sling.api.resource.PersistableValueMap; 28 | import org.apache.sling.api.resource.Resource; 29 | import org.apache.sling.api.resource.ResourceResolver; 30 | import org.apache.sling.api.resource.ResourceUtil; 31 | import org.apache.sling.api.resource.ValueMap; 32 | import org.apache.sling.jcr.resource.JcrModifiablePropertyMap; 33 | import org.apache.sling.jcr.resource.JcrPropertyMap; 34 | import org.apache.sling.jcr.resource.internal.JcrModifiableValueMap; 35 | 36 | import javax.jcr.Node; 37 | import javax.jcr.Property; 38 | import javax.jcr.RepositoryException; 39 | import java.io.InputStream; 40 | import java.util.ArrayList; 41 | import java.util.Iterator; 42 | import java.util.List; 43 | import java.util.Map; 44 | 45 | import static org.apache.jackrabbit.JcrConstants.JCR_CONTENT; 46 | import static org.apache.jackrabbit.JcrConstants.JCR_DATA; 47 | import static org.apache.jackrabbit.JcrConstants.JCR_PRIMARYTYPE; 48 | import static org.apache.jackrabbit.JcrConstants.NT_FILE; 49 | 50 | /** 51 | * Implement sling Resource for a jcr Node 52 | */ 53 | public class NodeResourceImpl extends ItemResourceImpl { 54 | private final Node node; 55 | 56 | 57 | /** 58 | * Construct a new NodeResourceImpl. 59 | * 60 | * @param resourceResolver ResourceResolver that constructed this NodeResourceImpl 61 | * @param node The constructed ResourceNodeImpl 62 | */ 63 | public NodeResourceImpl(ResourceResolver resourceResolver, Node node) { 64 | super(resourceResolver, node); 65 | this.node = node; 66 | } 67 | 68 | 69 | @Override 70 | public Iterator listChildren() { 71 | return getChildren().iterator(); 72 | } 73 | 74 | 75 | @Override 76 | public Iterable getChildren() { 77 | try { 78 | List children = new ArrayList<>(); 79 | for (Node child : JcrUtils.getChildNodes(node)) 80 | children.add(resourceResolver.getResource(this, child.getName())); 81 | return children; 82 | } 83 | catch (RepositoryException re) { 84 | return new ArrayList<>(); 85 | } 86 | } 87 | 88 | 89 | @Override 90 | public Resource getChild(String relPath) { 91 | try { 92 | return resourceResolver.getResource(Paths.resolve(node.getPath(), relPath)); 93 | } 94 | catch (RepositoryException re) { 95 | throw new SlingRepositoryException(re); 96 | } 97 | } 98 | 99 | 100 | @Override 101 | public String getResourceType() { 102 | try { 103 | return getResourceTypeForNode(node); 104 | } 105 | catch (RepositoryException re) { 106 | return null; /* ignore */ 107 | } 108 | } 109 | 110 | 111 | @Override 112 | public String getResourceSuperType() { 113 | return null; //TODO: implement resource supertypes 114 | } 115 | 116 | 117 | @Override 118 | public boolean hasChildren() { 119 | return listChildren().hasNext(); 120 | } 121 | 122 | 123 | @Override 124 | public ValueMap getValueMap() { 125 | return this.adaptTo(ValueMap.class); 126 | } 127 | 128 | 129 | @Override 130 | @SuppressWarnings({"unchecked", "deprecation"}) 131 | public AdapterType adaptTo(Class type) { 132 | if (type.equals(Node.class)) return (AdapterType)node; 133 | if (type.equals(ValueMap.class) || type.equals(Map.class)) return (AdapterType)new JcrPropertyMap(node); 134 | if (type.equals(PersistableValueMap.class)) return (AdapterType)new JcrModifiablePropertyMap(node); 135 | if (type.equals(ModifiableValueMap.class)) return (AdapterType)new JcrModifiableValueMap(node, null); 136 | 137 | if (type.equals(Page.class)) { 138 | try { 139 | ValueMap properties = this.adaptTo(ValueMap.class); 140 | if (properties == null) return null; 141 | String primaryType = properties.get(JCR_PRIMARYTYPE, String.class); 142 | if (primaryType == null || !primaryType.equals(JcrConstants.CQ_PAGE)) return null; 143 | if (!node.hasNode(JCR_CONTENT)) return null; 144 | return (AdapterType)new PageImpl(new NodeResourceImpl(resourceResolver, node)); 145 | } 146 | catch (RepositoryException e) { 147 | return null; 148 | } 149 | } 150 | 151 | if (type.equals(Asset.class)) { 152 | try { 153 | ValueMap properties = ResourceUtil.getValueMap(this); 154 | return !"dam:Asset".equals(properties.get(JCR_PRIMARYTYPE, String.class)) ? null : 155 | !node.hasNode(JCR_CONTENT) ? null : 156 | (AdapterType)new AssetImpl(this); 157 | } 158 | catch (RepositoryException e) { 159 | return null; 160 | } 161 | } 162 | 163 | if (type.equals(InputStream.class)) { 164 | try { 165 | Node content = node.isNodeType(NT_FILE) ? node.getNode(JCR_CONTENT) : node; 166 | Property data = content.hasProperty(JCR_DATA) ? content.getProperty(JCR_DATA) : null; 167 | return data != null ? (AdapterType)data.getBinary().getStream() : null; 168 | 169 | } 170 | catch (RepositoryException e) { 171 | return null; 172 | } 173 | } 174 | 175 | return super.adaptTo(type); 176 | } 177 | 178 | } 179 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Jackalope](jackalope.png) 2 | 3 | [![Build Status](https://travis-ci.org/TWCable/jackalope.svg?branch=master)](https://travis-ci.org/TWCable/jackalope) 4 | 5 | [ ![Download](https://api.bintray.com/packages/twcable/aem/jackalope/images/download.svg) ](https://bintray.com/twcable/aem/jackalope/_latestVersion) 6 | 7 | ## PURPOSE 8 | 9 | Jackalope is an in-memory implementation of the JCR with stubbing capabilities for Apache Sling. 10 | 11 | The goal of Jackalope is to better enable unit testing JCR representations of complex objects structures. 12 | Java objects can normally be simply represented in the JCR as nodes with properties. 13 | 14 | Simple mocking of the underlying JCR and Sling interfaces *can* be used to unit test reading and writing 15 | these kinds of objects. However, when object aggregations like Sling component structures need to be 16 | represented, the inability of simple mock objects to manage state become a real limitation. 17 | 18 | The obvious solution is to use a JCR implementation that uses memory as its storage. There are, in fact, memory-based JCR implementations -- including one that is distributed with the Jackrabbit project. (Jackrabbit is the reference implementation for the JCR JSR.) All of these, however, require far too much in setup and resources to be useful for unit testing. They are designed to be long running, multi-threaded processes. 19 | 20 | Our solution to this problem is to develop a simple and fast JCR implementation that can be used in unit tests. 21 | It does not implement the complete JCR spec, but just the parts that we've needed, including the basic facilities for reading and writing repository workspaces. 22 | 23 | There is another great project from [Citytech called Prosper](https://github.com/Citytechinc/prosper), which was released after Jackalope was written internally at Time Warner Cable. Each project has its own set of features, but the *primary* difference between Jackalope and Prosper is that Prosper uses Groovy string-based metaprogramming for building its trees, which has all the advantages and disadvantages of a fully dynamic API. Choose the one that best your needs/style: Choice is good. :-) 24 | 25 | 26 | ## Versions 27 | 28 | The 3.x line targets AEM 6.x, whereas the 2.x versions target AEM 5.6. 29 | 30 | Versioning follows the [Semantic Versioning standard](http://semver.org/) 31 | 32 | ## Packages 33 | 34 | The library is composed of 3 main packages. 35 | 36 | ### Builder (com.twcable.jackalope) 37 | 38 | This is the primary interface that should be used by clients of the library. 39 | The `JCRBuilder` class contains factory methods that can be used to create a virtual repository for use in test cases. 40 | 41 | To bring this class's methods in, typically you would do: 42 | ```groovy 43 | import static JCRBuilder.repostory 44 | import static JCRBuilder.node 45 | import static JCRBuilder.property 46 | ``` 47 | 48 | ### Sling Implementation (com.twcable.jackalope.sling) 49 | 50 | This package contains the classes that implement the primary Sling API classes like `Resource` and `ResourceResolver`. 51 | Users may want to use `SimpleResourceResolverFactory` to inject into services and servlets. 52 | 53 | ### JCR Implementation (com.twcable.jackalope.jcr) 54 | 55 | This package contains the classes that implement the primary JCR API classes like `Node` and `Property`. 56 | 57 | ## Examples 58 | 59 | ### Building and using a partial repository 60 | 61 | ```groovy 62 | given: 63 | def repository = repository( 64 | node("content", 65 | node("test1", 66 | node("callingrates", 67 | node("intl-direct-dial", 68 | property("sling:resourceType", "admin/components/content/callingratetable"), 69 | node("france", 70 | property("sling:resourceType", "admin/components/content/callingrate"), 71 | property("additional-minute-rate", "0.60"))))))).build() 72 | def resolver = new SimpleResourceResolverFactory(repository).administrativeResourceResolver 73 | def resource = resolver.getResource("/content/test1/callingrates/intl-direct-dial") 74 | 75 | when: 76 | def callingRateTable = new CallingRateTable(resource) 77 | 78 | then: 79 | callingRateTable.getRate("france") 80 | callingRateTable.getRate("france").additionalMinuteRate == "0.60" 81 | ``` 82 | 83 | The resource resolver factory can be used to test servlets and services by injection. 84 | 85 | ```groovy 86 | def repository = repository( 87 | node("content", 88 | node("test1", 89 | node("callingrates", 90 | node("intl-direct-dial", 91 | property("sling:resourceType", "admin/components/content/callingratetable"), 92 | node("france", 93 | property("sling:resourceType", "admin/components/content/callingrate"), 94 | property("additional-minute-rate", "0.60"))))))).build() 95 | def servlet = new CallingRatesImportServlet(new SimpleResourceResolverFactory(repository)) 96 | ``` 97 | 98 | ### Building and using a node tree 99 | 100 | Some classes are designed to read and write node trees and do not require the full repository 101 | implementation. These classes can build and use nodes directly. 102 | 103 | ```groovy 104 | def node = node("content", 105 | node("test1", 106 | node("callingrates", 107 | node("intl-direct-dial", 108 | property("sling:resourceType", "admin/components/content/callingratetable"), 109 | node("france", 110 | property("sling:resourceType", "admin/components/content/callingrate"), 111 | property("additional-minute-rate", "0.60"))))))).build() 112 | ``` 113 | 114 | ### Returning a query result 115 | 116 | Queries are also supported. A JCRQueryBuilder is used to create a query manager that associates queries 117 | with fixed result sets that can be used for testing. 118 | 119 | ```groovy 120 | def node = node("result").build() 121 | JCRQueryBuilder.queryManager(node.session, query("query", "language", result(node))).build() 122 | 123 | when: 124 | def queryResult = node.session.workspace.queryManager.createQuery("query", "language").execute() 125 | 126 | then: 127 | queryResult.nodes.hasNext() 128 | 129 | when: 130 | def results = Lists.newArrayList(queryResult.nodes) 131 | 132 | then: 133 | results[0] == node 134 | ``` 135 | 136 | ## BUILDING 137 | 138 | Jackalope uses gradle as its build system: 139 | 140 | `./gradlew build` 141 | 142 | # Including In Your Build 143 | 144 | [ ![Download](https://api.bintray.com/packages/twcable/aem/jackalope/images/download.svg) ](https://bintray.com/twcable/aem/jackalope/_latestVersion) 145 | 146 | Jackalope can be used by including the following in your 147 | build files (assuming Gradle): 148 | 149 | ```groovy 150 | repositories { 151 | maven { 152 | url "http://dl.bintray.com/twcable/aem" 153 | } 154 | } 155 | 156 | testCompile 'com.twcable.jackalope:jackalope:3.0.2' 157 | ``` 158 | 159 | # Releasing 160 | 161 | For the developers of Jackalope, please read [the documentation for creating and releasing a new version](docs/RELEASING.adoc). 162 | 163 | # LICENSE 164 | 165 | Copyright 2015 Time Warner Cable, Inc. 166 | 167 | Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance 168 | with the License. You may obtain a copy of the License at 169 | 170 | http://www.apache.org/licenses/LICENSE-2.0 171 | 172 | Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on 173 | an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for 174 | the specific language governing permissions and limitations under the License. 175 | -------------------------------------------------------------------------------- /src/main/java/com/twcable/jackalope/impl/cq/PageImpl.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Time Warner Cable, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.twcable.jackalope.impl.cq; 18 | 19 | import com.day.cq.commons.Filter; 20 | import com.day.cq.commons.LabeledResource; 21 | import com.day.cq.tagging.Tag; 22 | import com.day.cq.tagging.TagManager; 23 | import com.day.cq.wcm.api.Page; 24 | import com.day.cq.wcm.api.PageFilter; 25 | import com.day.cq.wcm.api.PageManager; 26 | import com.day.cq.wcm.api.Template; 27 | import com.day.cq.wcm.api.WCMException; 28 | import com.day.text.Text; 29 | import org.apache.sling.api.resource.Resource; 30 | import org.apache.sling.api.resource.ValueMap; 31 | 32 | import javax.jcr.Node; 33 | import java.util.Calendar; 34 | import java.util.Iterator; 35 | import java.util.Locale; 36 | 37 | /** 38 | * Page implementation. 39 | */ 40 | public class PageImpl implements Page { 41 | 42 | private final Resource resource; 43 | private Tag[] tagCache; 44 | 45 | 46 | public PageImpl(Resource resource) { 47 | this.resource = resource; 48 | } 49 | 50 | 51 | @Override 52 | public String getPath() { 53 | return this.resource != null ? this.resource.getPath() : null; 54 | } 55 | 56 | 57 | @Override 58 | public PageManager getPageManager() { 59 | return resource != null ? resource.getResourceResolver().adaptTo(PageManager.class) : null; 60 | } 61 | 62 | 63 | @Override 64 | public Resource getContentResource() { 65 | return resource != null ? resource.getChild("jcr:content") : null; 66 | } 67 | 68 | 69 | @Override 70 | public Resource getContentResource(String s) { 71 | if (s == null || s.isEmpty()) return getContentResource(); 72 | if (s.startsWith("/")) throw new IllegalArgumentException("Relative path expected."); 73 | return resource.getChild("jcr:content/" + s); 74 | } 75 | 76 | 77 | @Override 78 | public Iterator listChildren() { 79 | return listChildren(new PageFilter()); 80 | } 81 | 82 | 83 | @Override 84 | public Iterator listChildren(Filter pageFilter) { 85 | return listChildren(pageFilter, false); 86 | } 87 | 88 | 89 | @Override 90 | public Iterator listChildren(Filter pageFilter, boolean deep) { 91 | return new PageIteratorImpl(resource, pageFilter, deep); 92 | } 93 | 94 | 95 | @Override 96 | public boolean hasChild(String s) { 97 | return resource != null && resource.getChild(s) != null; 98 | } 99 | 100 | 101 | @Override 102 | public int getDepth() { 103 | throw new UnsupportedOperationException(); 104 | } 105 | 106 | 107 | @Override 108 | public Page getParent() { 109 | return getParent(1); 110 | } 111 | 112 | 113 | @Override 114 | public Page getParent(int i) { 115 | if (i == 0) return this; 116 | String path = Text.getRelativeParent(resource.getPath(), i); 117 | if (!path.isEmpty() && !path.equals("/")) return null; 118 | Resource parent = resource.getResourceResolver().getResource(path); 119 | return parent != null ? parent.adaptTo(Page.class) : null; 120 | } 121 | 122 | 123 | @Override 124 | public Page getAbsoluteParent(int i) { 125 | String path = Text.getAbsoluteParent(resource.getPath(), i); 126 | Resource parent = resource.getResourceResolver().getResource(path); 127 | return parent == null ? null : parent.adaptTo(Page.class); 128 | } 129 | 130 | 131 | @Override 132 | public ValueMap getProperties() { 133 | Resource contentResource = getContentResource(); 134 | return contentResource != null ? contentResource.adaptTo(ValueMap.class) : null; 135 | } 136 | 137 | 138 | @Override 139 | public ValueMap getProperties(String s) { 140 | throw new UnsupportedOperationException(); 141 | } 142 | 143 | 144 | @Override 145 | public String getName() { 146 | return resource != null ? resource.getName() : null; 147 | } 148 | 149 | 150 | @Override 151 | public String getTitle() { 152 | return getProperties().get("jcr:title", String.class); 153 | } 154 | 155 | 156 | @Override 157 | public String getDescription() { 158 | return getProperties().get("jcr:description", String.class); 159 | } 160 | 161 | 162 | @Override 163 | public String getPageTitle() { 164 | return getProperties().get("pageTitle", String.class); 165 | } 166 | 167 | 168 | @Override 169 | public String getNavigationTitle() { 170 | return getProperties().get("navTitle", String.class); 171 | } 172 | 173 | 174 | @Override 175 | public boolean isHideInNav() { 176 | ValueMap props = getProperties(); 177 | return props.containsKey("hideInNav") ? props.get("hideInNav", Boolean.class) : false; 178 | } 179 | 180 | 181 | @Override 182 | public boolean hasContent() { 183 | return resource != null && resource.getChild("jcr:content") != null; 184 | } 185 | 186 | 187 | @Override 188 | public boolean isValid() { 189 | return timeUntilValid() == 0; 190 | } 191 | 192 | 193 | @Override 194 | public long timeUntilValid() { 195 | if (!hasContent()) return Long.MIN_VALUE; 196 | 197 | Calendar onTime = getOnTime(), offTime = getOffTime(); 198 | long now = System.currentTimeMillis(); 199 | long on = onTime != null ? onTime.getTimeInMillis() : Long.MIN_VALUE; 200 | long off = offTime != null ? offTime.getTimeInMillis() : Long.MAX_VALUE; 201 | return now < on ? on - now : 202 | now >= off ? off - now : 203 | 0; 204 | } 205 | 206 | 207 | @Override 208 | public Calendar getOnTime() { 209 | ValueMap properties = getProperties(); 210 | return properties != null ? properties.get("onTime", Calendar.class) : null; 211 | } 212 | 213 | 214 | @Override 215 | public Calendar getOffTime() { 216 | ValueMap properties = getProperties(); 217 | return properties != null ? properties.get("offTime", Calendar.class) : null; 218 | } 219 | 220 | 221 | @Override 222 | public String getLastModifiedBy() { 223 | ValueMap properties = getProperties(); 224 | String lastModifiedBy = properties.get("cq:lastModifiedBy", String.class); 225 | return lastModifiedBy != null ? lastModifiedBy : properties.get("jcr:lastModifiedBy", String.class); 226 | } 227 | 228 | 229 | @Override 230 | public Calendar getLastModified() { 231 | ValueMap properties = getProperties(); 232 | Calendar lastModified = properties.get("cq:lastModified", Calendar.class); 233 | return lastModified != null ? lastModified : properties.get("jcr:lastModified", Calendar.class); 234 | } 235 | 236 | 237 | @Override 238 | public String getVanityUrl() { 239 | return getProperties().get("vanityUrl", String.class); 240 | } 241 | 242 | 243 | @Override 244 | public com.day.cq.tagging.Tag[] getTags() { 245 | if (tagCache != null) return tagCache; 246 | 247 | Resource contentResource = getContentResource(); 248 | TagManager tagManager = contentResource.getResourceResolver().adaptTo(TagManager.class); 249 | tagCache = tagManager == null ? new Tag[0] : tagManager.getTags(contentResource); 250 | return tagCache; 251 | } 252 | 253 | 254 | @Override 255 | public void lock() throws WCMException { 256 | throw new UnsupportedOperationException(); 257 | } 258 | 259 | 260 | @Override 261 | public boolean isLocked() { 262 | throw new UnsupportedOperationException(); 263 | } 264 | 265 | 266 | @Override 267 | public String getLockOwner() { 268 | throw new UnsupportedOperationException(); 269 | } 270 | 271 | 272 | @Override 273 | public boolean canUnlock() { 274 | throw new UnsupportedOperationException(); 275 | } 276 | 277 | 278 | @Override 279 | public void unlock() throws WCMException { 280 | throw new UnsupportedOperationException(); 281 | } 282 | 283 | 284 | @Override 285 | public Template getTemplate() { 286 | throw new UnsupportedOperationException(); 287 | } 288 | 289 | 290 | @Override 291 | public Locale getLanguage(boolean ignoreContent) { 292 | throw new UnsupportedOperationException(); 293 | } 294 | 295 | 296 | @Override 297 | @SuppressWarnings("unchecked") 298 | public AdapterType adaptTo(Class type) { 299 | if (resource == null) return null; 300 | if (type.equals(Resource.class)) return (AdapterType)resource; 301 | if (type.equals(LabeledResource.class)) return (AdapterType)this; 302 | if (type.equals(Node.class)) return (AdapterType)resource.adaptTo(Node.class); 303 | return resource.adaptTo(type); 304 | } 305 | } 306 | -------------------------------------------------------------------------------- /src/test/groovy/com/twcable/jackalope/impl/jcr/NodeImplSpec.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Time Warner Cable, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.twcable.jackalope.impl.jcr 18 | 19 | import com.google.common.collect.Lists 20 | import spock.lang.Specification 21 | import spock.lang.Subject 22 | 23 | import javax.jcr.Value 24 | import javax.jcr.nodetype.NodeType 25 | 26 | @Subject(NodeImpl) 27 | class NodeImplSpec extends Specification { 28 | def "A new node can be created"() { 29 | when: 30 | def node = new NodeImpl(new SessionImpl(), "test") 31 | 32 | then: 33 | node.isNode() 34 | node.getPath() == "test" 35 | } 36 | 37 | 38 | def "Properties can be set on nodes"() { 39 | def node = new NodeImpl(new SessionImpl(), "test") 40 | node.getSession().save() 41 | 42 | when: 43 | node.setProperty("prop", "hello") 44 | then: 45 | node.isModified() 46 | node.hasProperties() 47 | node.hasProperty("prop") 48 | node.getProperty("prop").getString() == "hello" 49 | node.getProperty("prop").isNew() 50 | when: 51 | node.setProperty("prop", 2.5d) 52 | then: 53 | node.getProperty("prop").getDouble().doubleValue() == 2.5d 54 | when: 55 | node.setProperty("prop", 100l) 56 | then: 57 | node.getProperty("prop").getLong().longValue() == 100l 58 | when: 59 | node.setProperty("prop", BigDecimal.valueOf(5000l)) 60 | then: 61 | node.getProperty("prop").getDecimal().longValue() == 5000l 62 | when: 63 | node.setProperty("prop", Boolean.TRUE) 64 | then: 65 | node.getProperty("prop").getBoolean() == Boolean.TRUE 66 | when: 67 | node.setProperty("prop", [new ValueImpl("a"), new ValueImpl("b"), new ValueImpl("c")] as Value[]) 68 | then: 69 | node.getProperty("prop").isMultiple() 70 | node.getProperty("prop").getValues().length == 3 71 | node.getProperty("prop").getValues()[0].getString() == "a" 72 | node.getProperty("prop").getValues()[1].getString() == "b" 73 | node.getProperty("prop").getValues()[2].getString() == "c" 74 | when: 75 | node.setProperty("prop", ["a", "b", "c"] as String[]) 76 | then: 77 | node.getProperty("prop").isMultiple() 78 | node.getProperty("prop").getValues().length == 3 79 | node.getProperty("prop").getValues()[0].getString() == "a" 80 | node.getProperty("prop").getValues()[1].getString() == "b" 81 | node.getProperty("prop").getValues()[2].getString() == "c" 82 | } 83 | 84 | 85 | def "Nodes have a set of properties"() { 86 | def node = new NodeImpl(new SessionImpl(), "test") 87 | node.setProperty("first", "a") 88 | node.setProperty("second", "b") 89 | node.setProperty("third", "c") 90 | 91 | when: 92 | def properties = Lists.newArrayList(node.getProperties()) 93 | 94 | then: 95 | properties.find { it.getName() == "first" }.getString() == "a" 96 | properties.find { it.getName() == "second" }.getString() == "b" 97 | properties.find { it.getName() == "third" }.getString() == "c" 98 | properties.size() == 3 99 | } 100 | 101 | 102 | def "Child nodes can be created"() { 103 | def node = new NodeImpl(new SessionImpl(), "test") 104 | node.getSession().save() 105 | 106 | when: 107 | def child = node.addNode("child") 108 | 109 | then: 110 | node.hasNodes() 111 | node.hasNode("child") 112 | child.isNode() 113 | child.isNew() 114 | node.isModified() 115 | node.getNode("child") == child 116 | node.getNode("child").isNodeType(NodeType.NT_UNSTRUCTURED) 117 | } 118 | 119 | 120 | def "Child nodes can be created with a specific node type"() { 121 | def node = new NodeImpl(new RepositoryImpl().login(), "test") 122 | node.getSession().save() 123 | 124 | when: 125 | node.addNode("child", NodeType.NT_FOLDER) 126 | 127 | then: 128 | node.getNode("child").isNodeType(NodeType.NT_FOLDER) 129 | } 130 | 131 | 132 | def "A node can have multiple child nodes"() { 133 | def node = new NodeImpl(new SessionImpl(), "test") 134 | node.addNode("first") 135 | node.addNode("second") 136 | node.addNode("third") 137 | node.getSession().save() 138 | 139 | when: 140 | def nodes = Lists.newArrayList(node.getNodes()) 141 | 142 | then: 143 | nodes.find { it.getName() == "first" } 144 | nodes.find { it.getName() == "second" } 145 | nodes.find { it.getName() == "third" } 146 | nodes.size() == 3 147 | } 148 | 149 | 150 | def "A node can be saved"() { 151 | when: 152 | def node = new NodeImpl(new SessionImpl(), "test") 153 | then: 154 | node.isNew() 155 | node.getSession().hasPendingChanges() 156 | 157 | when: 158 | node.save() 159 | then: 160 | !node.isNew() 161 | !node.getSession().hasPendingChanges() 162 | 163 | when: 164 | node.setProperty("prop", "a") 165 | then: 166 | node.isModified() 167 | node.getSession().hasPendingChanges() 168 | 169 | when: 170 | node.save() 171 | then: 172 | !node.isModified() 173 | !node.getSession().save() 174 | } 175 | 176 | 177 | def "Saving a node saves its children"() { 178 | when: 179 | def node = new NodeImpl(new SessionImpl(), "test") 180 | def child = node.addNode("child") 181 | then: 182 | node.isNew() 183 | child.isNew() 184 | node.getSession().hasPendingChanges() 185 | 186 | when: 187 | node.save() 188 | then: 189 | !node.isNew() 190 | !child.isNew() 191 | !node.getSession().hasPendingChanges() 192 | 193 | when: 194 | child.setProperty("prop", "a") 195 | then: 196 | child.isModified() 197 | node.getSession().hasPendingChanges() 198 | 199 | when: 200 | node.save() 201 | then: 202 | !child.isModified() 203 | !node.getSession().save() 204 | } 205 | 206 | 207 | def "Saving a node does not save any other node"() { 208 | def session = new SessionImpl() 209 | 210 | when: 211 | def node1 = new NodeImpl(session, "node1") 212 | def node2 = new NodeImpl(session, "node2") 213 | then: 214 | node1.isNew() 215 | node2.isNew() 216 | session.hasPendingChanges() 217 | 218 | when: 219 | node1.save() 220 | then: 221 | !node1.isNew() 222 | node2.isNew() 223 | session.hasPendingChanges() 224 | 225 | when: 226 | node2.save() 227 | then: 228 | !node2.isNew() 229 | !session.hasPendingChanges() 230 | 231 | when: 232 | node1.setProperty("prop", "a") 233 | node2.setProperty("prop", "b") 234 | then: 235 | node1.isModified() 236 | node2.isModified() 237 | session.hasPendingChanges() 238 | 239 | when: 240 | node1.save() 241 | then: 242 | !node1.isModified() 243 | node2.isModified() 244 | session.hasPendingChanges() 245 | } 246 | 247 | 248 | def "remove() deletes the node from the session"() { 249 | def session = new SessionImpl() 250 | def parent = new NodeImpl(session, "parent") 251 | def child = parent.addNode("child") 252 | session.nodeExists("parent/child") 253 | 254 | when: 255 | child.remove() 256 | 257 | then: 258 | !session.nodeExists("parent/child") 259 | } 260 | 261 | 262 | def "remove deletes descendent nodes from the session"() { 263 | def session = new SessionImpl() 264 | def parent = new NodeImpl(session, "parent") 265 | def child = parent.addNode("child") 266 | def grandchild = child.addNode("grandchild") 267 | def greatgrandchild = grandchild.addNode("greatgrandchild") 268 | session.nodeExists("parent/child") 269 | session.nodeExists("parent/child/grandchild") 270 | session.nodeExists("parent/child/grandchild/greatgrandchild") 271 | 272 | when: 273 | child.remove() 274 | 275 | then: 276 | !session.nodeExists("parent/child") 277 | !session.nodeExists("parent/child/grandchild") 278 | !session.nodeExists("parent/child/grandchild/greatgrandchild") 279 | } 280 | 281 | 282 | def "getParent for a standalone node returns the virtual root"() { 283 | def session = new SessionImpl() 284 | def node = new NodeImpl(session, "node") 285 | 286 | when: 287 | def parent = node.getParent() 288 | 289 | then: 290 | parent == session.getRootNode() 291 | } 292 | } 293 | -------------------------------------------------------------------------------- /src/test/groovy/com/twcable/jackalope/JCRBuilderSpec.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Time Warner Cable, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.twcable.jackalope 18 | 19 | import com.day.cq.wcm.api.PageManager 20 | import com.twcable.jackalope.impl.jcr.NodeImpl 21 | import com.twcable.jackalope.impl.jcr.SessionImpl 22 | import com.twcable.jackalope.impl.sling.ResourceResolverImpl 23 | import com.twcable.jackalope.impl.sling.SimpleResourceResolverFactory 24 | import com.twcable.jackalope.impl.sling.SlingRepositoryImpl 25 | import org.apache.sling.api.resource.Resource 26 | import org.apache.sling.api.resource.ValueMap 27 | import spock.lang.Specification 28 | import spock.lang.Subject 29 | 30 | import javax.jcr.Node 31 | import javax.jcr.Property 32 | 33 | import static com.twcable.jackalope.JCRBuilder.node 34 | import static com.twcable.jackalope.JCRBuilder.property 35 | import static com.twcable.jackalope.JCRBuilder.repository 36 | import static com.twcable.jackalope.JCRBuilder.resource 37 | import static com.twcable.jackalope.JcrConstants.CQ_PAGE 38 | import static com.twcable.jackalope.JcrConstants.CQ_PAGE_CONTENT 39 | import static com.twcable.jackalope.JcrConstants.NT_FILE 40 | import static com.twcable.jackalope.JcrConstants.NT_UNSTRUCTURED 41 | 42 | @Subject(JCRBuilder) 43 | @SuppressWarnings("GroovyAccessibility") 44 | class JCRBuilderSpec extends Specification { 45 | 46 | def "Build a single value property"() { 47 | def property = property("prop", "hello").build(new NodeImpl(new SessionImpl(), "test")) 48 | 49 | expect: 50 | property instanceof Property 51 | !property.multiple 52 | property.name == "prop" 53 | property.path == "test/prop" 54 | property.string == "hello" 55 | } 56 | 57 | 58 | def "Build a multi-value property"() { 59 | def property = property("prop", ["hello", "world"] as String[]).build(new NodeImpl(new SessionImpl(), "test")) 60 | 61 | expect: 62 | property instanceof Property 63 | property.multiple 64 | property.name == "prop" 65 | property.path == "test/prop" 66 | property.values[0].string == "hello" 67 | property.values[1].string == "world" 68 | } 69 | 70 | 71 | def "Build a multi-value double property"() { 72 | def doubles = [new Double(1.0), new Double(2.0)] as Double[] 73 | def property = property("prop", doubles).build(new NodeImpl(new SessionImpl(), "test")) 74 | 75 | expect: 76 | property instanceof Property 77 | property.multiple 78 | property.name == "prop" 79 | property.path == "test/prop" 80 | property.values[0].double == 1.0 81 | property.values[1].double == 2.0 82 | } 83 | 84 | 85 | def "Build a simple node"() { 86 | def node = node("node").build() 87 | 88 | expect: 89 | node instanceof Node 90 | node.name == "node" 91 | node.path == "node" 92 | node.isNodeType(NT_UNSTRUCTURED) 93 | } 94 | 95 | 96 | def "Build a node of a specific type"() { 97 | def node = node("node", NT_FILE).build() 98 | 99 | expect: 100 | node instanceof Node 101 | node.name == "node" 102 | node.path == "node" 103 | node.isNodeType(NT_FILE) 104 | } 105 | 106 | 107 | def "Build a node with child nodes and properties"() { 108 | def node = node("node", 109 | property("prop1", "a"), 110 | property("prop2", "b"), 111 | node("child1"), 112 | node("child2")).build() 113 | 114 | expect: 115 | node.name == "node" 116 | node.path == "node" 117 | node.getNode("child1").path == "node/child1" 118 | node.getNode("child2").path == "node/child2" 119 | node.getProperty("prop1").string == "a" 120 | node.getProperty("prop2").string == "b" 121 | } 122 | 123 | 124 | def "Build a resource with child resources and properties"() { 125 | def resource = resource(node("resource", 126 | property("prop1", "a"), 127 | property("prop2", "b"), 128 | node("child1"), 129 | node("child2"))).build() 130 | 131 | expect: 132 | resource.name == "resource" 133 | resource.path == "resource" 134 | resource.getChild("child1").path == "resource/child1" 135 | resource.getChild("child2").path == "resource/child2" 136 | resource.adaptTo(ValueMap).get("prop1", "") == "a" 137 | resource.adaptTo(ValueMap).get("prop2", "") == "b" 138 | } 139 | 140 | 141 | def "Create a page manually..."() { 142 | def repository = repository(node("page", CQ_PAGE, node("jcr:content", CQ_PAGE_CONTENT, 143 | property("jcr:title", "title"), 144 | property("pageTitle", "pageTitle")))).build(); 145 | def manager = new ResourceResolverImpl(repository).adaptTo(PageManager) 146 | def page = manager.getPage("/page") 147 | 148 | expect: 149 | page != null 150 | page.path == "/page" 151 | page.contentResource.name == "jcr:content" 152 | page.title == "title" 153 | page.pageTitle == "pageTitle" 154 | } 155 | 156 | 157 | def "...or, use PageManager to create a page."() { 158 | def resolver = new SimpleResourceResolverFactory(new SlingRepositoryImpl()).getAdministrativeResourceResolver([:]) 159 | def manager = resolver.adaptTo(PageManager.class) 160 | def page = manager.create("/content/test1", "page1", null, "title") 161 | 162 | expect: 163 | page.path == "/content/test1/page1" 164 | page.name == "page1" 165 | page.title == "title" 166 | } 167 | 168 | 169 | def "Page's jcr:content is a resource!"() { 170 | def repository = repository( 171 | node("page", CQ_PAGE, node("jcr:content", CQ_PAGE_CONTENT, 172 | property("jcr:title", "title")))).build(); 173 | def manager = new ResourceResolverImpl(repository).adaptTo(PageManager) 174 | def page = manager.getPage("/page") 175 | 176 | expect: 177 | page.contentResource instanceof Resource 178 | } 179 | 180 | 181 | def "PageManager can copy (like a real PageManager)"() { 182 | def repository = repository(node("content", node("test1", 183 | node("page1", CQ_PAGE, node("jcr:content", CQ_PAGE_CONTENT, 184 | node("anotherResource"), 185 | property("jcr:title", "title")))))).build() 186 | def resolver = new SimpleResourceResolverFactory(repository).resourceResolver 187 | def manager = resolver.adaptTo(PageManager.class) 188 | 189 | def page = manager.getPage("/content/test1/page1") 190 | def pageCopy = manager.copy(page, "/content/test1/copy", null, true, false) 191 | 192 | expect: 193 | page != null 194 | pageCopy != null 195 | pageCopy.path == "/content/test1/copy/page1" 196 | pageCopy.title == "title" 197 | pageCopy.contentResource.adaptTo(Node).primaryNodeType.name.equals(CQ_PAGE_CONTENT) 198 | pageCopy.contentResource.getChild("anotherResource") != null 199 | } 200 | 201 | 202 | def "PageManager can delete."() { 203 | def repository = repository(node("content", node("test1", 204 | node("page1", CQ_PAGE, node("jcr:content", CQ_PAGE_CONTENT, property("jcr:title", "title")))))).build() 205 | def resolver = new SimpleResourceResolverFactory(repository).resourceResolver 206 | def manager = resolver.adaptTo(PageManager) 207 | 208 | def page = manager.getPage("/content/test1/page1") 209 | manager.delete(page, false); 210 | 211 | expect: 212 | manager.getPage("/content/test1/page1") == null 213 | } 214 | 215 | 216 | def "PageManager can delete recursively."() { 217 | def repository = repository(node("content", node("test1", 218 | node("page1", CQ_PAGE, node("jcr:content", CQ_PAGE_CONTENT, property("jcr:title", "title")), 219 | node("page2", CQ_PAGE, node("jcr:content", CQ_PAGE_CONTENT, property("jcr:title", "title2"))))))).build() 220 | def resolver = new SimpleResourceResolverFactory(repository).resourceResolver 221 | def manager = resolver.adaptTo(PageManager.class) 222 | 223 | def page = manager.getPage("/content/test1/page1") 224 | def childPage = manager.getPage("/content/test1/page1/page2") 225 | manager.delete(page, false); 226 | 227 | expect: 228 | page != null 229 | childPage != null 230 | manager.getPage("/content/test1/page1/page2") == null 231 | manager.getPage("/content/test1/page1") == null 232 | } 233 | 234 | 235 | def "Build a repository"() { 236 | def repository = repository(node("node", 237 | property("prop1", "a"), 238 | property("prop2", "b"), 239 | node("child1"), 240 | node("child2"))).build() 241 | def resolver = new SimpleResourceResolverFactory(repository).getAdministrativeResourceResolver([:]) 242 | def resource = resolver.getResource("/node") 243 | 244 | expect: 245 | resolver.getResource("/node") 246 | resource.name == "node" 247 | resource.path == "/node" 248 | resource.getChild("child1").path == "/node/child1" 249 | resource.getChild("child2").path == "/node/child2" 250 | resource.adaptTo(ValueMap).get("prop1") == "a" 251 | resource.adaptTo(ValueMap).get("prop2") == "b" 252 | } 253 | 254 | } 255 | -------------------------------------------------------------------------------- /src/main/java/com/twcable/jackalope/impl/sling/ResourceResolverImpl.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Time Warner Cable, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.twcable.jackalope.impl.sling; 18 | 19 | import com.day.cq.wcm.api.PageManager; 20 | import com.twcable.jackalope.impl.common.Paths; 21 | import com.twcable.jackalope.impl.cq.PageManagerImpl; 22 | import com.twcable.jackalope.impl.jcr.NodeImpl; 23 | import com.twcable.jackalope.impl.jcr.SessionImpl; 24 | import com.twcable.jackalope.impl.jcr.ValueImpl; 25 | import org.apache.sling.api.resource.LoginException; 26 | import org.apache.sling.api.resource.NonExistingResource; 27 | import org.apache.sling.api.resource.PersistenceException; 28 | import org.apache.sling.api.resource.Resource; 29 | import org.apache.sling.api.resource.ResourceResolver; 30 | import org.apache.sling.jcr.api.SlingRepository; 31 | import org.slf4j.Logger; 32 | import org.slf4j.LoggerFactory; 33 | 34 | import javax.annotation.Nonnull; 35 | import javax.annotation.Nullable; 36 | import javax.jcr.Item; 37 | import javax.jcr.ItemExistsException; 38 | import javax.jcr.ItemNotFoundException; 39 | import javax.jcr.Node; 40 | import javax.jcr.Property; 41 | import javax.jcr.RepositoryException; 42 | import javax.jcr.Session; 43 | import javax.servlet.http.HttpServletRequest; 44 | import java.util.Collections; 45 | import java.util.Iterator; 46 | import java.util.Map; 47 | 48 | /** 49 | * A simple {@link ResourceResolver} suitable for testing. 50 | */ 51 | public class ResourceResolverImpl implements ResourceResolver { 52 | private static final Logger LOG = LoggerFactory.getLogger(ResourceResolverImpl.class); 53 | 54 | private final Session session; 55 | private boolean isLive = true; 56 | 57 | 58 | @SuppressWarnings("ConstantConditions") 59 | public ResourceResolverImpl(@Nonnull SlingRepository repository) { 60 | if (repository == null) throw new IllegalArgumentException("Repository cannot be null."); 61 | try { 62 | this.session = repository.login(); 63 | } 64 | catch (RepositoryException re) { 65 | throw new SlingRepositoryException(re); 66 | } 67 | } 68 | 69 | 70 | @Override 71 | public Resource resolve(HttpServletRequest request, String absPath) { 72 | String path = absPath != null ? absPath : "/"; 73 | Resource resource = getResource(path); 74 | return resource != null ? resource : new NonExistingResource(this, path); 75 | } 76 | 77 | 78 | @Override 79 | public Resource resolve(String absPath) { 80 | return resolve(null, absPath); 81 | } 82 | 83 | 84 | @Override 85 | public Resource resolve(HttpServletRequest request) { 86 | return resolve(request, null); 87 | } 88 | 89 | 90 | @Override 91 | public String map(String resourcePath) { 92 | LOG.warn("Resource mapping is not implemented"); 93 | return resourcePath; //TODO: implement resource mapping 94 | } 95 | 96 | 97 | @Override 98 | public String map(HttpServletRequest request, String resourcePath) { 99 | LOG.warn("Resource mapping is not implemented"); 100 | return resourcePath; //TODO: implement resource mapping 101 | } 102 | 103 | 104 | @Override 105 | public Resource getResource(String path) { 106 | try { 107 | return (session.itemExists(path)) ? constructResource(session.getItem(path)) : null; 108 | } 109 | catch (RepositoryException re) { 110 | throw new SlingRepositoryException(re); 111 | } 112 | } 113 | 114 | 115 | @Override 116 | public Resource getResource(Resource base, String path) { 117 | return getResource(Paths.resolve(base.getPath(), path)); 118 | } 119 | 120 | 121 | @Override 122 | public String[] getSearchPath() { 123 | return new String[0]; //TODO 124 | } 125 | 126 | 127 | @Override 128 | public Iterator listChildren(Resource parent) { 129 | return parent.listChildren(); 130 | } 131 | 132 | 133 | @Override 134 | public Iterable getChildren(Resource resource) { 135 | return resource.getChildren(); 136 | } 137 | 138 | 139 | @Override 140 | public Iterator findResources(String query, String language) { 141 | return null; //TODO: Implement queries 142 | } 143 | 144 | 145 | @Override 146 | public Iterator> queryResources(String query, String language) { 147 | return null; //TODO: Implement queries 148 | } 149 | 150 | 151 | @Override 152 | public boolean hasChildren(Resource resource) { 153 | return listChildren(resource).hasNext(); 154 | } 155 | 156 | 157 | @Override 158 | public ResourceResolver clone(Map authenticationInfo) throws LoginException { 159 | return null; //TODO: Implement authentication 160 | } 161 | 162 | 163 | @Override 164 | public boolean isLive() { 165 | return isLive; 166 | } 167 | 168 | 169 | @Override 170 | public void close() { 171 | isLive = false; 172 | } 173 | 174 | 175 | @Override 176 | public String getUserID() { 177 | return null; //TODO: Implement authentication 178 | } 179 | 180 | 181 | @Override 182 | public Iterator getAttributeNames() { 183 | return null; //TODO: Implement attributes 184 | } 185 | 186 | 187 | @Override 188 | public Object getAttribute(String name) { 189 | return null; //TODO: Implement attributes 190 | } 191 | 192 | 193 | @Override 194 | public void delete(Resource resource) throws PersistenceException { 195 | try { 196 | session.removeItem(resource.getPath()); 197 | } 198 | catch (RepositoryException e) { 199 | throw new PersistenceException("Could not delete " + resource.getPath(), e); 200 | } 201 | } 202 | 203 | 204 | @Override 205 | public Resource create(@Nonnull Resource parent, @Nonnull String name, @Nullable Map properties) throws PersistenceException { 206 | //noinspection ConstantConditions 207 | if (parent == null) 208 | throw new IllegalArgumentException("Could not create a node for \"" + name + "\" because the parent is null"); 209 | String parentPath = parent.getPath(); 210 | 211 | // remove any trailing slash (or sole-slash if the root) 212 | if (parentPath.endsWith("/")) parentPath = parentPath.substring(0, parentPath.length() - 1); 213 | String path = parentPath + "/" + name; 214 | 215 | return createNodeResource(path, properties != null ? properties : Collections.emptyMap()); 216 | } 217 | 218 | 219 | private Resource createNodeResource(@Nonnull String path, @Nonnull Map properties) { 220 | try { 221 | if (session.nodeExists(path)) return new NodeResourceImpl(this, session.getNode(path)); 222 | } 223 | catch (RepositoryException e) { 224 | //should be impossible since this is in-memory 225 | throw new IllegalStateException(e); 226 | } 227 | 228 | try { 229 | NodeImpl node = createNode(path, properties); 230 | return new NodeResourceImpl(this, node); 231 | } 232 | catch (ItemNotFoundException | ItemExistsException e) { 233 | // should be impossible since we're checking first 234 | throw new IllegalStateException(e); 235 | } 236 | } 237 | 238 | 239 | private NodeImpl createNode(@Nonnull String path, @Nonnull Map properties) throws ItemNotFoundException, ItemExistsException { 240 | NodeImpl node = new NodeImpl((SessionImpl)session, path); 241 | 242 | for (String propName : properties.keySet()) { 243 | Object mapVal = properties.get(propName); 244 | setNodeProperty(node, propName, mapVal); 245 | } 246 | return node; 247 | } 248 | 249 | 250 | private static void setNodeProperty(@Nonnull NodeImpl node, @Nonnull String propName, @Nonnull Object propertyVal) { 251 | try { 252 | node.setProperty(propName, new ValueImpl(propertyVal)); 253 | } 254 | catch (RepositoryException ignore) { 255 | // ignore 256 | } 257 | } 258 | 259 | 260 | @Override 261 | public void revert() { 262 | } 263 | 264 | 265 | @Override 266 | public void commit() throws PersistenceException { 267 | } 268 | 269 | 270 | @Override 271 | public boolean hasChanges() { 272 | return false; 273 | } 274 | 275 | 276 | @Override 277 | public String getParentResourceType(Resource resource) { 278 | return resource.getParent().getResourceType(); 279 | } 280 | 281 | 282 | @Override 283 | public String getParentResourceType(String s) { 284 | return null; 285 | } 286 | 287 | 288 | @Override 289 | public boolean isResourceType(Resource resource, String resourceType) { 290 | return resource.isResourceType(resourceType); 291 | } 292 | 293 | 294 | @Override 295 | public void refresh() { 296 | } 297 | 298 | 299 | @Override 300 | @SuppressWarnings("unchecked") 301 | public AdapterType adaptTo(Class type) { 302 | if (type.equals(Session.class)) return (AdapterType)session; 303 | if (type.equals(PageManager.class)) return (AdapterType)new PageManagerImpl(this); 304 | else return null; 305 | } 306 | 307 | 308 | private Resource constructResource(Item item) { 309 | return (item.isNode()) ? new NodeResourceImpl(this, (Node)item) : new PropertyResourceImpl(this, (Property)item); 310 | } 311 | 312 | } 313 | -------------------------------------------------------------------------------- /src/main/java/com/twcable/jackalope/JCRBuilder.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Time Warner Cable, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.twcable.jackalope; 18 | 19 | import com.google.common.base.Strings; 20 | import com.twcable.jackalope.impl.common.Values; 21 | import com.twcable.jackalope.impl.jcr.NodeImpl; 22 | import com.twcable.jackalope.impl.jcr.SessionImpl; 23 | import com.twcable.jackalope.impl.sling.NodeResourceImpl; 24 | import com.twcable.jackalope.impl.sling.ResourceResolverImpl; 25 | import com.twcable.jackalope.impl.sling.SimpleResourceResolverFactory; 26 | import com.twcable.jackalope.impl.sling.SlingRepositoryException; 27 | import com.twcable.jackalope.impl.sling.SlingRepositoryImpl; 28 | import org.apache.sling.api.resource.LoginException; 29 | import org.apache.sling.api.resource.Resource; 30 | import org.apache.sling.jcr.api.SlingRepository; 31 | 32 | import javax.annotation.Nonnull; 33 | import javax.jcr.Node; 34 | import javax.jcr.Property; 35 | import javax.jcr.RepositoryException; 36 | import javax.jcr.Session; 37 | import javax.jcr.Value; 38 | import java.io.File; 39 | 40 | /** 41 | * Utilities for building an in-memory jcr implementation for use in unit tests. 42 | */ 43 | public class JCRBuilder { 44 | 45 | private JCRBuilder() { 46 | } 47 | 48 | 49 | @Nonnull 50 | public static RepositoryBuilder repository(NodeBuilder... nodeBuilders) { 51 | return new RepositoryBuilderImpl(nodeBuilders); 52 | } 53 | 54 | 55 | /** 56 | * Creates a ResourceBuilder with the given node. 57 | *

58 | * The ResourceBuilder will create a resource from the node built by nodeBuilder. 59 | * 60 | * @param nodeBuilder The node builder that will build the node that the resource will use 61 | * @return the ResourceBuilder 62 | */ 63 | @Nonnull 64 | public static ResourceBuilder resource(NodeBuilder nodeBuilder) { 65 | return new ResourceBuilderImpl(nodeBuilder); 66 | } 67 | 68 | 69 | /** 70 | * Creates a NodeBuilder with the given contents. 71 | *

72 | * The NodeBuilder will build an nt:unstructured node. Its name is derived from the path by default. If you pass 73 | * in a NodeBuilder, the Node it creates will be added as a child of the Node. If you pass in a PropertyBuilderImpl, 74 | * it will set a property of the Node. 75 | *

76 | * 77 | * @param path the path of the node 78 | * @param values builders for the Node's child items (properties, child nodes) 79 | * @return the NodeBuilder 80 | */ 81 | @Nonnull 82 | public static NodeBuilder node(String path, ItemBuilder... values) { 83 | return node(path, JcrConstants.NT_UNSTRUCTURED, values); 84 | } 85 | 86 | 87 | /** 88 | * Creates a NodeBuilder with the given contents. 89 | *

90 | * The NodeBuilder will build a node of the specified type. Its name is derived from the path by default. If you pass 91 | * in a NodeBuilder, the Node it creates will be added as a child of the Node. If you pass in a PropertyBuilderImpl, 92 | * it will set a property of the Node. 93 | * 94 | * @param path the path of the node 95 | * @param nodeTypeName the node type to use for this node 96 | * @param values builders for the Node's child items (properties, child nodes) 97 | * @return the NodeBuilder 98 | */ 99 | @Nonnull 100 | public static NodeBuilder node(String path, String nodeTypeName, ItemBuilder... values) { 101 | return new NodeBuilderImpl(path, nodeTypeName, values); 102 | } 103 | 104 | 105 | /** 106 | * Creates a PropertyBuilderImpl with the given name and value. 107 | *

108 | * 109 | * @param name the name of the property 110 | * @param value the value of the property 111 | * @return the PropertyBuilderImpl 112 | */ 113 | @Nonnull 114 | public static PropertyBuilder property(String name, Object value) { 115 | return new PropertyBuilderImpl(name, value); 116 | } 117 | 118 | 119 | /** 120 | * Creates a PropertyBuilderImpl that will build a multi-value property with the given name and values. 121 | *

122 | * 123 | * @param name the name of the property 124 | * @param values the values of the property 125 | * @return the PropertyBuilderImpl 126 | */ 127 | @Nonnull 128 | public static PropertyBuilder property(String name, Object[] values) { 129 | return new PropertyBuilderImpl(name, values); 130 | } 131 | 132 | 133 | // ********************************************************************** 134 | // 135 | // INNER CLASSES 136 | // 137 | // ********************************************************************** 138 | 139 | 140 | static class RepositoryBuilderImpl implements RepositoryBuilder { 141 | NodeBuilder[] nodeBuilders; 142 | 143 | 144 | RepositoryBuilderImpl(NodeBuilder... nodeBuilders) { 145 | this.nodeBuilders = nodeBuilders; 146 | } 147 | 148 | 149 | public SlingRepository build() { 150 | SlingRepositoryImpl repository = new SlingRepositoryImpl(); 151 | try { 152 | for (NodeBuilder nodeBuilder : nodeBuilders) 153 | ((NodeBuilderImpl)nodeBuilder).build(repository.login().getRootNode()); 154 | } 155 | catch (RepositoryException re) { 156 | throw new SlingRepositoryException(re); 157 | } 158 | return repository; 159 | } 160 | } 161 | 162 | static class NodeBuilderImpl implements NodeBuilder { 163 | private final String path; 164 | private final String nodeTypeName; 165 | private final ItemBuilder[] childBuilders; 166 | 167 | 168 | NodeBuilderImpl(String path, String nodeTypeName, ItemBuilder... childBuilders) { 169 | this.path = path; 170 | this.nodeTypeName = nodeTypeName; 171 | this.childBuilders = childBuilders; 172 | } 173 | 174 | 175 | /** 176 | * Builds the requested node. 177 | * 178 | * @return the Node 179 | */ 180 | public Node build() { 181 | return build((NodeImpl)null); 182 | } 183 | 184 | 185 | /** 186 | * Builds the requested node. 187 | * 188 | * @param parent The parent node of the node to be built. If the node does not need a parent, null can be used. 189 | * @return the Node 190 | */ 191 | public NodeImpl build(Node parent) { 192 | try { 193 | NodeImpl node = (parent != null) ? (NodeImpl)parent.addNode(getName(), nodeTypeName) : new NodeImpl(new SessionImpl(), getName()); 194 | if (!Strings.isNullOrEmpty(nodeTypeName)) 195 | node.setPrimaryType(nodeTypeName); 196 | for (ItemBuilder builder : childBuilders) 197 | builder.build(node); 198 | node.getSession().save(); 199 | return node; 200 | } 201 | catch (RepositoryException re) { 202 | throw new SlingRepositoryException(re); 203 | } 204 | } 205 | 206 | 207 | /** 208 | * Builds the requested node. 209 | * 210 | * @param session Session to be associated with the new node 211 | * @return the Node 212 | */ 213 | protected NodeImpl build(Session session) { 214 | try { 215 | NodeImpl node = new NodeImpl((SessionImpl)session, getName()); 216 | if (!Strings.isNullOrEmpty(nodeTypeName)) 217 | node.setPrimaryType(nodeTypeName); 218 | for (ItemBuilder builder : childBuilders) 219 | builder.build(node); 220 | node.getSession().save(); 221 | return node; 222 | } 223 | catch (RepositoryException re) { 224 | throw new SlingRepositoryException(re); 225 | } 226 | } 227 | 228 | 229 | private String getName() { 230 | return new File(path).getName(); 231 | } 232 | } 233 | 234 | static class ResourceBuilderImpl implements ResourceBuilder { 235 | private final NodeBuilder builder; 236 | 237 | 238 | ResourceBuilderImpl(NodeBuilder builder) { 239 | this.builder = builder; 240 | } 241 | 242 | 243 | /** 244 | * Builds the requested resource. 245 | * 246 | * @return the Resource 247 | */ 248 | public Resource build() { 249 | try { 250 | ResourceResolverImpl resolver = (ResourceResolverImpl)new SimpleResourceResolverFactory(new SlingRepositoryImpl()).getAdministrativeResourceResolver(null); 251 | return new NodeResourceImpl(resolver, ((NodeBuilderImpl)builder).build(resolver.adaptTo(Session.class))); 252 | } 253 | catch (LoginException le) { 254 | throw new SlingRepositoryException(le); 255 | } 256 | } 257 | } 258 | 259 | static class PropertyBuilderImpl implements PropertyBuilder { 260 | private final String name; 261 | private final Value[] values; 262 | private final boolean hasMultiple; 263 | 264 | 265 | PropertyBuilderImpl(String name, Object value) { 266 | this.name = name; 267 | this.values = Values.convertObjectsToValues(value); 268 | this.hasMultiple = false; 269 | } 270 | 271 | 272 | PropertyBuilderImpl(String name, Object[] values) { 273 | this.name = name; 274 | this.values = Values.convertObjectsToValues(values); 275 | this.hasMultiple = true; 276 | } 277 | 278 | 279 | public Property build(Node parent) { 280 | if (parent == null) return null; // properties only exist in nodes so there is nothing to build. 281 | try { 282 | if (hasMultiple) 283 | parent.setProperty(name, values); 284 | else 285 | parent.setProperty(name, values[0]); 286 | return parent.getProperty(name); 287 | } 288 | catch (RepositoryException re) { 289 | throw new SlingRepositoryException(re); 290 | } 291 | } 292 | } 293 | 294 | } 295 | 296 | --------------------------------------------------------------------------------