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

Commit 2f655ab

Browse files
authored
Merge pull request #116 from rjrudin/feature/60-amps
#60 Can now deploy/undeploy amps correctly - at least on 8.0-5.8
2 parents 09dd0f2 + b92a191 commit 2f655ab

File tree

4 files changed

+247
-126
lines changed

4 files changed

+247
-126
lines changed
Lines changed: 175 additions & 110 deletions
Original file line numberDiff line numberDiff line change
@@ -1,120 +1,185 @@
11
package com.marklogic.mgmt.security;
22

3-
import java.util.ArrayList;
4-
import java.util.List;
5-
63
import com.fasterxml.jackson.databind.JsonNode;
74
import com.marklogic.mgmt.AbstractResourceManager;
85
import com.marklogic.mgmt.ManageClient;
6+
import com.marklogic.mgmt.SaveReceipt;
97
import com.marklogic.rest.util.Fragment;
8+
import com.marklogic.rest.util.ResourcesFragment;
9+
import org.springframework.http.ResponseEntity;
10+
11+
import java.util.ArrayList;
12+
import java.util.List;
1013

1114
public class AmpManager extends AbstractResourceManager {
1215

13-
private String namespace;
14-
private String documentUri;
15-
private String modulesDatabase;
16-
17-
public AmpManager(ManageClient client) {
18-
super(client);
19-
/*
20-
* Turning this off as part of version 2.0b10 - having issues with ML being able to create an amp but then
21-
* getting a 404 when it tries to update the amp. Seems to be due to using a modules database - viewing the amp
22-
* via the Mgmt API shows the modules database as being "filesystem", but the Admin app correctly shows the
23-
* modules database.
24-
*/
25-
setUpdateAllowed(false);
26-
}
27-
28-
public String getResourcePath(String resourceNameOrId) {
29-
return super.getResourcePath(resourceNameOrId, "namespace", namespace, "document-uri", documentUri,
30-
"modules-database", modulesDatabase);
31-
}
32-
33-
public String getPropertiesPath(String resourceNameOrId) {
34-
return super.getPropertiesPath(resourceNameOrId, "namespace", namespace, "document-uri", documentUri,
35-
"modules-database", modulesDatabase);
36-
}
37-
38-
@Override
39-
protected boolean useAdminUser() {
40-
return true;
41-
}
42-
43-
@Override
44-
protected String getIdFieldName() {
45-
return "local-name";
46-
}
47-
48-
@Override
49-
protected String[] getUpdateResourceParams(String payload) {
50-
List<String> params = new ArrayList<String>();
51-
params.add("document-uri");
52-
if (payloadParser.isJsonPayload(payload)) {
53-
JsonNode node = payloadParser.parseJson(payload);
54-
params.add(node.get("document-uri").asText());
55-
if (node.has("namespace")) {
56-
params.add("namespace");
57-
params.add(node.get("namespace").asText());
58-
}
59-
if (node.has("modules-database")) {
60-
params.add("modules-database");
61-
params.add(node.get("modules-database").asText());
62-
}
63-
} else {
64-
Fragment f = new Fragment(payload);
65-
params.add(f.getElementValue("/node()/*[local-name(.) = 'document-uri']"));
66-
67-
String val = f.getElementValue("/node()/*[local-name(.) = 'namespace']");
68-
if (val != null) {
69-
params.add("namespace");
70-
params.add(val);
71-
}
72-
73-
val = f.getElementValue("/node()/*[local-name(.) = 'modules-database']");
74-
if (val != null) {
75-
params.add("modules-database");
76-
params.add(val);
77-
}
78-
}
79-
return params.toArray(new String[] {});
80-
}
81-
82-
/**
83-
* In ML 8.0-5.1 deleting an amp fails if the database is specified on the
84-
* delete url
85-
*/
86-
@Override
87-
protected String[] getDeleteResourceParams(String payload) {
88-
List<String> params = new ArrayList<String>();
89-
params.add("document-uri");
90-
if (payloadParser.isJsonPayload(payload)) {
91-
JsonNode node = payloadParser.parseJson(payload);
92-
params.add(node.get("document-uri").asText());
93-
if (node.has("namespace")) {
94-
params.add("namespace");
95-
params.add(node.get("namespace").asText());
96-
}
97-
} else {
98-
Fragment f = new Fragment(payload);
99-
params.add(f.getElementValue("/node()/*[local-name(.) = 'document-uri']"));
100-
101-
String val = f.getElementValue("/node()/*[local-name(.) = 'namespace']");
102-
if (val != null) {
103-
params.add("namespace");
104-
params.add(val);
105-
}
106-
}
107-
return params.toArray(new String[] {}); }
108-
109-
public void setNamespace(String namespace) {
110-
this.namespace = namespace;
111-
}
112-
113-
public void setDocumentUri(String documentUri) {
114-
this.documentUri = documentUri;
115-
}
116-
117-
public void setModulesDatabase(String modulesDatabase) {
118-
this.modulesDatabase = modulesDatabase;
119-
}
16+
private String namespace;
17+
private String documentUri;
18+
private String modulesDatabase;
19+
20+
public AmpManager(ManageClient client) {
21+
super(client);
22+
}
23+
24+
public String getResourcePath(String resourceNameOrId) {
25+
return super.getResourcePath(resourceNameOrId, "namespace", namespace, "document-uri", documentUri,
26+
"modules-database", modulesDatabase);
27+
}
28+
29+
public String getPropertiesPath(String resourceNameOrId) {
30+
return super.getPropertiesPath(resourceNameOrId, "namespace", namespace, "document-uri", documentUri,
31+
"modules-database", modulesDatabase);
32+
}
33+
34+
@Override
35+
protected boolean useAdminUser() {
36+
return true;
37+
}
38+
39+
@Override
40+
protected String getIdFieldName() {
41+
return "local-name";
42+
}
43+
44+
/**
45+
* We have to override how this works in the parent class because the parent class assumes that existence
46+
* can be based solely on the resource ID. But for an amp, we need to use the resource ID (local-name),
47+
* document-uri, namespace, and modules-database.
48+
*/
49+
@Override
50+
public SaveReceipt save(String payload) {
51+
String resourceId = getResourceId(payload);
52+
String label = getResourceName();
53+
String path = null;
54+
ResponseEntity<String> response = null;
55+
if (ampExists(payload)) {
56+
return updateResource(payload, resourceId);
57+
} else {
58+
logger.info(format("Creating %s: %s", label, resourceId));
59+
path = getCreateResourcePath(payload);
60+
response = postPayload(getManageClient(), path, payload);
61+
logger.info(format("Created %s: %s", label, resourceId));
62+
}
63+
return new SaveReceipt(resourceId, payload, path, response);
64+
}
65+
66+
/**
67+
* Uses local-name, document-uri, modules-database, and namespace to determine if the amp exists. Since we
68+
* do this comparison against the XML returned by /manage/v2/amps, if no modules-database is set, we have
69+
* to substitute in the value "filesystem", as that's what is returned by that endpoint.
70+
*
71+
* @param payload
72+
* @return
73+
*/
74+
public boolean ampExists(String payload) {
75+
String resourceId = getResourceId(payload);
76+
AmpParams params = getAmpParams(payload);
77+
ResourcesFragment resources = getAsXml();
78+
79+
String xpath = "/node()/*[local-name(.) = 'list-items']/node()[" +
80+
"(*[local-name(.) = 'nameref'] = '%s' or *[local-name(.) = 'idref'] = '%s')" +
81+
" and *[local-name(.) = 'document-uri'] = '%s'";
82+
xpath = format(xpath, resourceId, resourceId, params.documentUri);
83+
if (params.namespace != null) {
84+
xpath += format(" and *[local-name(.) = 'namespace'] = '%s'", params.namespace);
85+
}
86+
if (params.modulesDatabase != null) {
87+
xpath += format(" and *[local-name(.) = 'modules-database'] = '%s'", params.modulesDatabase);
88+
} else {
89+
xpath += format(" and *[local-name(.) = 'modules-database'] = 'filesystem'");
90+
}
91+
xpath += "]";
92+
return resources.elementExists(xpath);
93+
}
94+
95+
@Override
96+
protected String[] getUpdateResourceParams(String payload) {
97+
List<String> params = new ArrayList<String>();
98+
AmpParams ampParams = getAmpParams(payload);
99+
params.add("document-uri");
100+
params.add(ampParams.documentUri);
101+
if (ampParams.namespace != null) {
102+
params.add("namespace");
103+
params.add(ampParams.namespace);
104+
}
105+
if (ampParams.modulesDatabase != null) {
106+
params.add("modules-database");
107+
params.add(ampParams.modulesDatabase);
108+
}
109+
return params.toArray(new String[]{});
110+
}
111+
112+
/**
113+
* Note that the parent class's delete method does an existence check based on just the resource ID - in an
114+
* amp's case, the local name. That will still work, because when the parent class builds the properties path
115+
* for the amp resource, it will call this method and include the other parameters needed to uniquely refer
116+
* to the amp. Worst case - a call is made to delete an amp that no longer exists, but another amp exists with
117+
* the same local name. The parent class will think the amp exists, but when it tries the delete call, it
118+
* won't find the amp with all the params, and the call will succeed without deleting anything.
119+
*/
120+
@Override
121+
protected String[] getDeleteResourceParams(String payload) {
122+
List<String> params = new ArrayList<String>();
123+
AmpParams ampParams = getAmpParams(payload);
124+
params.add("document-uri");
125+
params.add(ampParams.documentUri);
126+
if (ampParams.namespace != null) {
127+
params.add("namespace");
128+
params.add(ampParams.namespace);
129+
}
130+
if (ampParams.modulesDatabase != null) {
131+
params.add("modules-database");
132+
params.add(ampParams.modulesDatabase);
133+
}
134+
return params.toArray(new String[]{});
135+
}
136+
137+
/**
138+
* @param payload
139+
* @return an AmpParams object containing the values of the 3 amp properties - besides local name - that are
140+
* needed to uniquely refer to an amp.
141+
*/
142+
public AmpParams getAmpParams(String payload) {
143+
AmpParams params = new AmpParams();
144+
if (payloadParser.isJsonPayload(payload)) {
145+
JsonNode node = payloadParser.parseJson(payload);
146+
params.documentUri = node.get("document-uri").asText();
147+
if (node.has("namespace")) {
148+
params.namespace = node.get("namespace").asText();
149+
}
150+
if (node.has("modules-database")) {
151+
params.modulesDatabase = node.get("modules-database").asText();
152+
}
153+
} else {
154+
Fragment f = new Fragment(payload);
155+
params.documentUri = f.getElementValue("/node()/*[local-name(.) = 'document-uri']");
156+
String val = f.getElementValue("/node()/*[local-name(.) = 'namespace']");
157+
if (val != null) {
158+
params.namespace = val;
159+
}
160+
val = f.getElementValue("/node()/*[local-name(.) = 'modules-database']");
161+
if (val != null) {
162+
params.modulesDatabase = val;
163+
}
164+
}
165+
return params;
166+
}
167+
168+
public void setNamespace(String namespace) {
169+
this.namespace = namespace;
170+
}
171+
172+
public void setDocumentUri(String documentUri) {
173+
this.documentUri = documentUri;
174+
}
175+
176+
public void setModulesDatabase(String modulesDatabase) {
177+
this.modulesDatabase = modulesDatabase;
178+
}
179+
}
180+
181+
class AmpParams {
182+
public String documentUri;
183+
public String modulesDatabase;
184+
public String namespace;
120185
}

src/test/java/com/marklogic/appdeployer/command/security/ManageAmpsTest.java

Lines changed: 68 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,24 +2,80 @@
22

33
import com.marklogic.appdeployer.command.AbstractManageResourceTest;
44
import com.marklogic.appdeployer.command.Command;
5+
import com.marklogic.mgmt.ManageClient;
56
import com.marklogic.mgmt.ResourceManager;
67
import com.marklogic.mgmt.security.AmpManager;
8+
import org.junit.Test;
79

810
public class ManageAmpsTest extends AbstractManageResourceTest {
911

10-
@Override
11-
protected ResourceManager newResourceManager() {
12-
return new AmpManager(manageClient);
13-
}
12+
/**
13+
* This test verifies that AmpManager can correctly handle two amps that have the same local-name, document-uri,
14+
* and namespace, but with a different modules-database.
15+
*
16+
* Note that the module doesn't actually have to exist in order to create an amp.
17+
*/
18+
@Test
19+
public void twoAmpsWithDifferentModulesDatabase() {
20+
String amp1 = "{\n" +
21+
" \"namespace\": \"http://example.com/uri\",\n" +
22+
" \"local-name\": \"ml-app-deployer-test-amp\",\n" +
23+
" \"document-uri\": \"/module/path/name\",\n" +
24+
" \"modules-database\": \"Documents\",\n" +
25+
" \"role\": [\"rest-writer\"]\n" +
26+
"}";
1427

15-
@Override
16-
protected Command newCommand() {
17-
return new DeployAmpsCommand();
18-
}
28+
String amp2 = "{\n" +
29+
" \"namespace\": \"http://example.com/uri\",\n" +
30+
" \"local-name\": \"ml-app-deployer-test-amp\",\n" +
31+
" \"document-uri\": \"/module/path/name\",\n" +
32+
" \"modules-database\": \"Modules\",\n" +
33+
" \"role\": [\"rest-writer\"]\n" +
34+
"}";
1935

20-
@Override
21-
protected String[] getResourceNames() {
22-
return new String[] { "sample-app-amp-1" };
23-
}
36+
ManageClient client = new ManageClient();
37+
AmpManager mgr = new AmpManager(client);
38+
39+
try {
40+
// Create and verify
41+
mgr.save(amp1);
42+
mgr.save(amp2);
43+
assertTrue(mgr.ampExists(amp1));
44+
assertTrue(mgr.ampExists(amp2));
45+
46+
// Update and verify
47+
mgr.save(amp1);
48+
mgr.save(amp2);
49+
assertTrue(mgr.ampExists(amp1));
50+
assertTrue(mgr.ampExists(amp2));
51+
} finally {
52+
// Delete and verify
53+
mgr.delete(amp1);
54+
mgr.delete(amp2);
55+
assertFalse(mgr.ampExists(amp1));
56+
assertFalse(mgr.ampExists(amp2));
57+
}
58+
}
59+
60+
@Override
61+
protected ResourceManager newResourceManager() {
62+
return new AmpManager(manageClient);
63+
}
64+
65+
@Override
66+
protected Command newCommand() {
67+
return new DeployAmpsCommand();
68+
}
69+
70+
/**
71+
* The second amp doesn't have a modules database specified, so we can verify the amp can still be
72+
* created/deleted when it refers to a filesystem module.
73+
*
74+
* @return
75+
*/
76+
@Override
77+
protected String[] getResourceNames() {
78+
return new String[]{"ml-app-deployer-test-1", "ml-app-deployer-test-2"};
79+
}
2480

2581
}
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"namespace": "http://example.com/uri",
3-
"local-name": "sample-app-amp-1",
3+
"local-name": "ml-app-deployer-test-1",
44
"document-uri": "/module/path/name",
5-
"modules-database": "",
5+
"modules-database": "Modules",
66
"role": ["app-user"]
77
}

0 commit comments

Comments
 (0)