Skip to content
This repository was archived by the owner on Sep 16, 2024. It is now read-only.

Commit 572df79

Browse files
authored
Merge pull request #186 from BillFarber/feature/cascadeCollectionsAndPermissions
DEVEXP-527: Starting on tests for cascading collections and permissions.
2 parents 58cbacd + 87b771d commit 572df79

35 files changed

+263
-66
lines changed
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/*
2+
* Copyright (c) 2023 MarkLogic Corporation
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+
package com.marklogic.client.ext.file;
17+
18+
import java.io.File;
19+
import java.io.IOException;
20+
import java.nio.file.Path;
21+
import java.util.Properties;
22+
import java.util.Stack;
23+
24+
/**
25+
* Adds a stack to store Properties objects while traversing a directory tree.
26+
*/
27+
abstract class CascadingPropertiesDrivenDocumentFileProcessor extends PropertiesDrivenDocumentFileProcessor {
28+
final private Stack<Properties> propertiesStack = new Stack<>();
29+
30+
protected CascadingPropertiesDrivenDocumentFileProcessor(String propertiesFilename) {
31+
super(propertiesFilename);
32+
}
33+
34+
protected void preVisitDirectory(Path dir) throws IOException {
35+
File collectionsPropertiesFile = new File(dir.toFile(), this.getPropertiesFilename());
36+
if (collectionsPropertiesFile.exists()) {
37+
this.loadProperties(collectionsPropertiesFile);
38+
} else {
39+
if (!propertiesStack.isEmpty()) {
40+
this.setProperties(propertiesStack.peek());
41+
} else {
42+
this.setProperties(new Properties());
43+
}
44+
}
45+
propertiesStack.push(this.getProperties());
46+
}
47+
48+
protected void postVisitDirectory() {
49+
propertiesStack.pop();
50+
if (!propertiesStack.isEmpty()) {
51+
this.setProperties(propertiesStack.peek());
52+
}
53+
}
54+
}

src/main/java/com/marklogic/client/ext/file/CollectionsFileDocumentFileProcessor.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
* key is the name of a file in the directory, and the value is a comma-delimited list of collections to load the file
2323
* into (which means you can't use a comma in any collection name).
2424
*/
25-
public class CollectionsFileDocumentFileProcessor extends PropertiesDrivenDocumentFileProcessor {
25+
public class CollectionsFileDocumentFileProcessor extends CascadingPropertiesDrivenDocumentFileProcessor {
2626

2727
private String delimiter = ",";
2828

src/main/java/com/marklogic/client/ext/file/DefaultDocumentFileReader.java

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,7 @@
2222
import java.net.URISyntaxException;
2323
import java.nio.file.*;
2424
import java.nio.file.attribute.BasicFileAttributes;
25-
import java.util.ArrayList;
26-
import java.util.List;
25+
import java.util.*;
2726

2827
/**
2928
* Non-threadsafe implementation that implements FileVisitor as a way of descending one or more file paths.
@@ -105,6 +104,10 @@ public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) th
105104
if (logger.isDebugEnabled()) {
106105
logger.debug("Visiting directory: " + dir);
107106
}
107+
108+
collectionsFileDocumentFileProcessor.preVisitDirectory(dir);
109+
permissionsFileDocumentFileProcessor.preVisitDirectory(dir);
110+
108111
return FileVisitResult.CONTINUE;
109112
} else {
110113
if (logger.isDebugEnabled()) {
@@ -127,6 +130,10 @@ public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOEx
127130
if (exc != null) {
128131
logger.warn("Error in postVisitDirectory: " + exc.getMessage(), exc);
129132
}
133+
134+
collectionsFileDocumentFileProcessor.postVisitDirectory();
135+
permissionsFileDocumentFileProcessor.postVisitDirectory();
136+
130137
return FileVisitResult.CONTINUE;
131138
}
132139

src/main/java/com/marklogic/client/ext/file/PermissionsFileDocumentFileProcessor.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
* Looks for a special file in each directory - defaults to permissions.properties - that contains properties where the
2525
* key is the name of a file in the directory, and the value is a comma-delimited list of role,capability,role,capability,etc.
2626
*/
27-
public class PermissionsFileDocumentFileProcessor extends PropertiesDrivenDocumentFileProcessor {
27+
public class PermissionsFileDocumentFileProcessor extends CascadingPropertiesDrivenDocumentFileProcessor {
2828

2929
private DocumentPermissionsParser documentPermissionsParser;
3030

src/main/java/com/marklogic/client/ext/file/PropertiesDrivenDocumentFileProcessor.java

Lines changed: 14 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,6 @@
2222
import java.io.FileFilter;
2323
import java.io.FileReader;
2424
import java.io.IOException;
25-
import java.util.HashMap;
26-
import java.util.Map;
2725
import java.util.Properties;
2826

2927
/**
@@ -34,10 +32,9 @@ public abstract class PropertiesDrivenDocumentFileProcessor extends LoggingObjec
3432

3533
protected final static String WILDCARD_KEY = "*";
3634

37-
private String propertiesFilename;
35+
private final String propertiesFilename;
3836

39-
// Used to avoid checking for and loading the properties for every file in a directory
40-
private Map<File, Properties> propertiesCache = new HashMap<>();
37+
private Properties properties;
4138

4239
private TokenReplacer tokenReplacer;
4340

@@ -60,36 +57,17 @@ public DocumentFile processDocumentFile(DocumentFile documentFile) {
6057
if (!accept(file)) {
6158
return null;
6259
}
63-
64-
File propertiesFile = new File(file.getParentFile(), propertiesFilename);
65-
if (propertiesFile.exists()) {
66-
try {
67-
Properties props = loadProperties(propertiesFile);
68-
processProperties(documentFile, props);
69-
} catch (IOException e) {
70-
logger.warn("Unable to load properties from file: " + propertiesFile.getAbsolutePath(), e);
71-
}
72-
}
73-
60+
processProperties(documentFile, properties);
7461
return documentFile;
7562
}
7663

7764
protected abstract void processProperties(DocumentFile documentFile, Properties properties);
7865

7966
protected Properties loadProperties(File propertiesFile) throws IOException {
80-
Properties props = null;
81-
if (propertiesCache.containsKey(propertiesFile)) {
82-
props = propertiesCache.get(propertiesFile);
83-
}
84-
if (props != null) {
85-
return props;
86-
}
87-
88-
props = new Properties();
67+
properties = new Properties();
8968
try (FileReader reader = new FileReader(propertiesFile)) {
90-
props.load(reader);
91-
propertiesCache.put(propertiesFile, props);
92-
return props;
69+
properties.load(reader);
70+
return properties;
9371
}
9472
}
9573

@@ -101,10 +79,6 @@ protected String getPropertyValue(Properties properties, String propertyName) {
10179
return tokenReplacer != null && value != null ? tokenReplacer.replaceTokens(value) : value;
10280
}
10381

104-
public Map<File, Properties> getPropertiesCache() {
105-
return propertiesCache;
106-
}
107-
10882
public String getPropertiesFilename() {
10983
return propertiesFilename;
11084
}
@@ -116,4 +90,12 @@ public void setTokenReplacer(TokenReplacer tokenReplacer) {
11690
protected TokenReplacer getTokenReplacer() {
11791
return tokenReplacer;
11892
}
93+
94+
protected void setProperties(Properties properties) {
95+
this.properties = properties;
96+
}
97+
98+
protected Properties getProperties() {
99+
return this.properties;
100+
}
119101
}

src/test/java/com/marklogic/client/ext/AbstractIntegrationTest.java

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
package com.marklogic.client.ext;
1717

1818
import com.marklogic.client.DatabaseClient;
19+
import com.marklogic.client.io.DocumentMetadataHandle;
1920
import org.junit.jupiter.api.AfterEach;
2021
import org.junit.jupiter.api.extension.ExtendWith;
2122
import org.springframework.beans.factory.annotation.Autowired;
@@ -27,6 +28,12 @@
2728
import org.springframework.test.context.ContextConfiguration;
2829
import org.springframework.test.context.junit.jupiter.SpringExtension;
2930

31+
import java.util.Set;
32+
import java.util.function.Consumer;
33+
34+
import static org.junit.jupiter.api.Assertions.assertEquals;
35+
import static org.junit.jupiter.api.Assertions.assertTrue;
36+
3037
@ExtendWith(SpringExtension.class)
3138
@ContextConfiguration(classes = {TestConfig.class})
3239
public abstract class AbstractIntegrationTest {
@@ -58,6 +65,36 @@ protected DatabaseClient newClient(String database) {
5865
clientConfig.setDatabase(currentDatabase);
5966
return client;
6067
}
68+
69+
protected final void verifyMetadata(String uri, Consumer<DocumentMetadataHandle> verifier) {
70+
verifier.accept(client.newJSONDocumentManager().readMetadata(uri, new DocumentMetadataHandle()));
71+
}
72+
73+
protected final void verifyCollections(String uri, String... collections) {
74+
verifyMetadata(uri, metadata -> {
75+
assertEquals(collections.length, metadata.getCollections().size());
76+
for (String collection : collections) {
77+
assertTrue(metadata.getCollections().contains(collection), "Did not find expected collection: " +
78+
collection + "; actual collections: " + metadata.getCollections());
79+
}
80+
});
81+
}
82+
83+
protected final void verifyPermissions(String uri, String... permissionsRolesAndCapabilities) {
84+
verifyMetadata(uri, metadata -> {
85+
for (int i = 0; i < permissionsRolesAndCapabilities.length; i += 2) {
86+
String role = permissionsRolesAndCapabilities[i];
87+
assertTrue(metadata.getPermissions().containsKey(role), "Did not find permissions with role: " +
88+
role + "; actual permissions: " + metadata.getPermissions());
89+
90+
DocumentMetadataHandle.Capability capability =
91+
DocumentMetadataHandle.Capability.valueOf(permissionsRolesAndCapabilities[i + 1].toUpperCase());
92+
Set<DocumentMetadataHandle.Capability> capabilities = metadata.getPermissions().get(role);
93+
assertTrue(capabilities.contains(capability), "Did not find permission for role: " + role +
94+
" with capability: " + capability + "; actual capabilities: " + capabilities);
95+
}
96+
});
97+
}
6198
}
6299

63100
@Configuration
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
/*
2+
* Copyright (c) 2023 MarkLogic Corporation
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+
package com.marklogic.client.ext.file;
17+
18+
import com.marklogic.client.DatabaseClient;
19+
import com.marklogic.client.ext.AbstractIntegrationTest;
20+
import org.junit.jupiter.api.BeforeEach;
21+
import org.junit.jupiter.api.Test;
22+
23+
public class CascadeCollectionsAndPermissionsTest extends AbstractIntegrationTest {
24+
25+
final private String PARENT_COLLECTION = "ParentCollection";
26+
final private String CHILD_COLLECTION = "ChildCollection";
27+
28+
@BeforeEach
29+
public void setup() {
30+
client = newClient(MODULES_DATABASE);
31+
DatabaseClient modulesClient = client;
32+
modulesClient.newServerEval().xquery("cts:uris((), (), cts:true-query()) ! xdmp:document-delete(.)").eval();
33+
}
34+
35+
@Test
36+
public void parentWithBothProperties() {
37+
String directory = "src/test/resources/process-files/cascading-metadata-test/parent1-withCP";
38+
GenericFileLoader loader = new GenericFileLoader(client);
39+
loader.loadFiles(directory);
40+
41+
verifyCollections( "/child1_1-noCP/test.json", PARENT_COLLECTION);
42+
verifyPermissions( "/child1_1-noCP/test.json", "rest-writer", "update");
43+
44+
verifyCollections( "/child1_2-withCP/test.json", CHILD_COLLECTION);
45+
verifyPermissions( "/child1_2-withCP/test.json", "rest-reader", "read");
46+
47+
verifyCollections( "/child3_1-withCP/grandchild3_1_1-noCP/test.json", CHILD_COLLECTION);
48+
verifyPermissions( "/child3_1-withCP/grandchild3_1_1-noCP/test.json", "rest-reader", "read");
49+
50+
verifyCollections("/child1/child1.json", "ParentCollection");
51+
verifyPermissions("/child1/child1.json", "rest-writer", "update");
52+
53+
verifyCollections("/child2/child2.json", "child2");
54+
verifyPermissions("/child2/child2.json", "app-user", "read");
55+
56+
verifyCollections("/parent.json", "ParentCollection");
57+
verifyPermissions("/parent.json", "rest-writer", "update");
58+
}
59+
60+
@Test
61+
public void parentWithNoProperties() {
62+
String directory = "src/test/resources/process-files/cascading-metadata-test/parent2-noCP";
63+
GenericFileLoader loader = new GenericFileLoader(client);
64+
loader.loadFiles(directory);
65+
66+
verifyCollections( "/child2_1-withCP/test.json", CHILD_COLLECTION);
67+
verifyPermissions( "/child2_1-withCP/test.json", "rest-reader", "read");
68+
69+
verifyCollections( "/child2_2-noCP/test.json");
70+
verifyPermissions( "/child2_2-noCP/test.json");
71+
72+
verifyCollections( "/child2_3-withCnoP/test.json", PARENT_COLLECTION);
73+
verifyPermissions( "/child2_3-withCnoP/test.json");
74+
75+
verifyCollections( "/child2_3-withCnoP/grandchild2_3_1-withPnoC/test.json", PARENT_COLLECTION);
76+
verifyPermissions( "/child2_3-withCnoP/grandchild2_3_1-withPnoC/test.json", "rest-reader", "read");
77+
}
78+
}

src/test/java/com/marklogic/client/ext/file/CollectionsFileDocumentFileProcessorTest.java

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import org.junit.jupiter.api.Test;
2020

2121
import java.io.File;
22+
import java.io.IOException;
2223
import java.util.Properties;
2324

2425
import static org.junit.jupiter.api.Assertions.*;
@@ -28,9 +29,12 @@ public class CollectionsFileDocumentFileProcessorTest {
2829
private CollectionsFileDocumentFileProcessor processor = new CollectionsFileDocumentFileProcessor();
2930

3031
@Test
31-
public void wildcard() {
32+
public void wildcard() throws IOException {
3233
File testDir = new File("src/test/resources/process-files/wildcard-test");
3334

35+
File collectionsPropertiesFile = new File(testDir, processor.getPropertiesFilename());
36+
processor.loadProperties(collectionsPropertiesFile);
37+
3438
DocumentFile file = new DocumentFile("/test.json", new File(testDir, "test.json"));
3539
processor.processDocumentFile(file);
3640
assertTrue(file.getDocumentMetadata().getCollections().contains("json-data"));
@@ -45,9 +49,12 @@ public void wildcard() {
4549
}
4650

4751
@Test
48-
public void replaceTokens() {
52+
public void replaceTokens() throws IOException {
4953
File testDir = new File("src/test/resources/process-files/token-test");
5054

55+
File collectionsPropertiesFile = new File(testDir, processor.getPropertiesFilename());
56+
processor.loadProperties(collectionsPropertiesFile);
57+
5158
DefaultTokenReplacer tokenReplacer = new DefaultTokenReplacer();
5259
Properties props = new Properties();
5360
props.setProperty("%%someCollection%%", "this-was-replaced");

0 commit comments

Comments
 (0)