diff --git a/.gitignore b/.gitignore
index a5103ff..b43a539 100644
--- a/.gitignore
+++ b/.gitignore
@@ -7,6 +7,7 @@ __pycache__/
# Distribution / packaging
.Python
+.idea
env/
build/
develop-eggs/
diff --git a/examples/data/cma_payload.json b/examples/data/cma_payload.json
new file mode 100644
index 0000000..7609140
--- /dev/null
+++ b/examples/data/cma_payload.json
@@ -0,0 +1,75 @@
+{
+ "params": {
+ "mlRole": "cma_role",
+ "mlUser": "cma-user",
+ "mlUserPassword": "password",
+ "mlForest": "cma_forest",
+ "mlSchemaDatabase": "cma_schema_database",
+ "mlDatabase": "cma_database",
+ "mlAppServer": "cma_appserver",
+ "mlGroup": "cma_group",
+ "mlAuth": "digest",
+ "mlPort": "8005"
+
+ },
+ "config": [{
+ "role": [{
+ "role-name": "%%mlRole%%",
+ "description": "A role that allows the user to read and write to rest",
+ "role": [
+ "rest-reader",
+ "rest-extension-user",
+ "rest-writer"
+ ],
+ "privilege": [{
+ "privilege-name": "xdmp:eval",
+ "action": "http://marklogic.com/xdmp/privileges/xdmp-eval",
+ "kind": "execute"
+ }]
+ }
+ ],
+ "user": [{
+ "user-name": "%%mlUser%%",
+ "description": "A user with the rest role with eval",
+ "password": "%%mlUserPassword%%",
+ "role": ["%%mlRole%%"]
+ }],
+ "forest":[
+ {"forest-name":"%%mlForest%%"}
+ ],
+ "database": [
+ {
+ "database-name": "%%mlSchemaDatabase%%"
+ },
+ {
+ "database-name": "%%mlDatabase%%",
+ "range-element-index": [],
+ "triggers-database": "%%mlSchemaDatabase%%",
+ "triple-index": true,
+ "collection-lexicon": true,
+ "uri-lexicon": true,
+ "forest":["%%mlForest%%"]
+ }
+ ],
+ "group":[
+ {
+ "group-name":"%%mlGroup%%"
+ }
+ ],
+ "server": [{
+ "server-name": "%%mlAppServer%%",
+ "server-type": "http",
+ "root": "/",
+ "group-name": "%%mlGroup%%",
+ "port": "%%mlPort%%",
+ "modules-database": "%%mlDatabase%%",
+ "content-database": "%%mlDatabase%%",
+ "authentication": "%%mlAuth%%",
+ "default-error-format": "json",
+ "error-handler": "/MarkLogic/rest-api/error-handler.xqy",
+ "url-rewriter": "/MarkLogic/rest-api/rewriter.xml",
+ "rewrite-resolves-globally": true
+ }
+ ]
+ }]
+}
\ No newline at end of file
diff --git a/examples/data/cma_payload.xml b/examples/data/cma_payload.xml
new file mode 100644
index 0000000..7a8d19b
--- /dev/null
+++ b/examples/data/cma_payload.xml
@@ -0,0 +1,121 @@
+
+
+
+
+
+ mlAppServer
+ cma_appserver
+
+
+ mlAuth
+ digest
+
+
+ mlDatabase
+ cma_database
+
+
+ mlForest
+ cma_forest
+
+
+ mlGroup
+ cma_group
+
+
+ mlAppServer
+ cma_appserver
+
+
+ mlPort
+ 8005
+
+
+ mlRole
+ cma_role
+
+
+ mlSchemaDatabase
+ cma_schema_database
+
+
+ mlUser
+ cma-user
+
+
+ mlUserPassword
+ password
+
+
+
+
+ %%mlRole%%
+ A role that allows the user to read and write to rest
+
+
+ http://marklogic.com/xdmp/privileges/xdmp-eval
+ execute
+ xdmp:eval
+
+
+
+ rest-reader
+ rest-extension-user
+ rest-writer
+
+
+
+
+
+ %%mlUser%%
+ A user with the rest role with eval
+ %%mlUserPassword%%
+
+ %%mlRole%%
+
+
+
+
+
+ %%mlForest%%
+
+
+
+
+ %%mlSchemaDatabase%%
+
+
+ %%mlDatabase%%
+ true
+ %%mlSchemaDatabase%%
+ true
+ true
+
+ %%mlForest%%
+
+
+
+
+
+ %%mlGroup%%
+
+
+
+
+ %%mlAppServer%%
+ http
+ %%mlAuth%%
+ %%mlDatabase%%
+ json
+ /MarkLogic/rest-api/error-handler.xqy
+ %%mlGroup%%
+ %%mlDatabase%%
+ %%mlPort%%
+ true
+ /
+ /MarkLogic/rest-api/rewriter.xml
+
+
+
+
+
\ No newline at end of file
diff --git a/marklogic/client/cma.py b/marklogic/client/cma.py
new file mode 100644
index 0000000..03e4fe3
--- /dev/null
+++ b/marklogic/client/cma.py
@@ -0,0 +1,121 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright 2015 MarkLogic Corporation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0#
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+"""
+Support the CMA V3 endpoint
+"""
+
+import json, logging
+import requests
+from marklogic.exceptions import *
+
+class CMA:
+ """
+ CMA class provides methods to generate and apply application configuration.
+ Sample payloads in examples/data
+ """
+
+ _cma_version = "v3"
+ _min_support_version = 9.0-5
+
+ def __init__(self, connection, params={}, scenario=None, format="json"):
+ self.connection = connection
+ self.params = params
+ self.scenario = scenario
+ self.format = format
+ self.logger = logging.getLogger("marklogic.client.cma")
+
+ @property
+ def scenario(self):
+ """
+ Scenario property for generate config(ex. ha-local)
+ :return:scenario as string
+ """
+ return self.__scenario
+
+ @scenario.setter
+ def scenario(self, scenario):
+ self.__scenario = scenario
+
+ #
+ @property
+ def params(self):
+ """
+ Params for generate and apply config
+ ref: http://docs.marklogic.com/REST/configuration-management-api
+ :return:params as dict
+ """
+ return self.__params
+
+ @params.setter
+ def params(self, params):
+ if isinstance(params, dict):
+ self.__params = params
+ else:
+ raise Exception("Params should be a dict")
+
+ @property
+ def format(self):
+ return self.__format
+
+ @format.setter
+ def format(self, format):
+ self.__format = format
+
+ def generate_config(self):
+ """
+ Generate configuration for different scenarios
+ :return configuration as string(format : xml/json) or binary(format:zip)
+ """
+
+ cma_url = "http://{0}:{1}/{2}/{3}" \
+ .format(self.connection.host, self.connection.management_port, self.connection.root, self._cma_version)
+
+ cma_url = (cma_url + "?format={}").format(self.format)
+
+ if self.scenario:
+ cma_url = (cma_url+"&scenario={}").format(self.scenario)
+ if self.params:
+ cma_url = (cma_url+"¶ms={}").format(json.dumps(self.params))
+
+ print(cma_url)
+
+ response = requests.get(cma_url, auth=self.connection.auth)
+
+ if response.status_code == 404:
+ return None
+ elif response.status_code == 200:
+ return response.text
+ else:
+ raise UnexpectedAPIResponse(response.text)
+
+ def apply_config(self, config, content_type):
+ """
+ Applies the specified configuration on MarkLogic
+ :param config: configuration to be applied as string
+ :param content_type: application/json or application/xml
+ :return response object
+ """
+ cma_url = "http://{0}:{1}/{2}/{3}" \
+ .format(self.connection.host, self.connection.management_port, self.connection.root, self._cma_version)
+ if self.params:
+ cma_url = (cma_url+"¶ms={}").format(self.params)
+ response = requests.post(cma_url, data=config, auth=self.connection.auth,
+ headers={'content-type': content_type})
+ if response.status_code > 299:
+ raise UnexpectedAPIResponse(response.text)
+ return response
diff --git a/tests/test_cma.py b/tests/test_cma.py
new file mode 100644
index 0000000..1c865e3
--- /dev/null
+++ b/tests/test_cma.py
@@ -0,0 +1,73 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright 2015 MarkLogic Corporation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0#
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+import json
+from mlconfig import MLConfig
+from marklogic.client.cma import CMA
+from marklogic.models import Database
+
+
+class TestCMA(MLConfig):
+
+ def test_generate_config(self):
+ cma = CMA(self.connection)
+ cma.format="xml"
+ config = cma.generate_config()
+ assert config is not None
+ assert json.dumps(config)[1] == '<'
+ cma.format = "json"
+ config = cma.generate_config()
+ assert config is not None
+ assert json.dumps(config)[1] == '{'
+
+ def test_apply_config(self):
+ cma = CMA(self.connection)
+ config1 = {
+ "config": [
+ {
+ "database": [
+ {
+ "database-name": "CMA_Check1"
+ }
+ ]
+ }
+ ]
+ }
+ config2 = "" \
+ "CMA_Check2"
+ cma.apply_config(json.dumps(config1), "application/json")
+ cma.apply_config(config2, "application/xml")
+
+ validate_db1 = Database.lookup(self.connection, "CMA_Check1")
+ try:
+ assert validate_db1 is not None
+ assert 'CMA_Check1' == validate_db1.database_name()
+
+ finally:
+ validate_db1.delete(connection=self.connection)
+ validate_db1 = Database.lookup(self.connection, "CMA_Check1")
+ assert validate_db1 is None
+
+ validate_db2 = Database.lookup(self.connection, "CMA_Check2")
+ try:
+ assert validate_db2 is not None
+ assert 'CMA_Check2' == validate_db2.database_name()
+
+ finally:
+ validate_db2.delete(connection=self.connection)
+ validate_db2 = Database.lookup(self.connection, "CMA_Check2")
+ assert validate_db2 is None
\ No newline at end of file