Merge topic 'cleanup/composite-node'
[controller.git] / opendaylight / md-sal / sal-rest-connector / src / test / java / org / opendaylight / controller / sal / restconf / impl / test / RestGetOperationTest.java
1 /*
2  * Copyright (c) 2014 Cisco Systems, Inc. and others.  All rights reserved.
3  *
4  * This program and the accompanying materials are made available under the
5  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6  * and is available at http://www.eclipse.org/legal/epl-v10.html
7  */
8 package org.opendaylight.controller.sal.restconf.impl.test;
9
10 import static org.junit.Assert.assertEquals;
11 import static org.junit.Assert.assertFalse;
12 import static org.junit.Assert.assertNotNull;
13 import static org.junit.Assert.assertNull;
14 import static org.junit.Assert.assertTrue;
15 import static org.junit.Assert.fail;
16 import static org.mockito.Matchers.any;
17 import static org.mockito.Matchers.eq;
18 import static org.mockito.Mockito.mock;
19 import static org.mockito.Mockito.when;
20 import com.google.common.base.Optional;
21 import com.google.common.collect.Lists;
22 import com.google.common.collect.Maps;
23 import java.io.FileNotFoundException;
24 import java.io.UnsupportedEncodingException;
25 import java.net.URI;
26 import java.net.URISyntaxException;
27 import java.text.ParseException;
28 import java.text.SimpleDateFormat;
29 import java.util.ArrayList;
30 import java.util.Date;
31 import java.util.HashSet;
32 import java.util.List;
33 import java.util.Map;
34 import java.util.Set;
35 import java.util.regex.Matcher;
36 import java.util.regex.Pattern;
37 import javax.ws.rs.core.Application;
38 import javax.ws.rs.core.MediaType;
39 import javax.ws.rs.core.MultivaluedHashMap;
40 import javax.ws.rs.core.MultivaluedMap;
41 import javax.ws.rs.core.Response;
42 import javax.ws.rs.core.UriInfo;
43 import org.glassfish.jersey.server.ResourceConfig;
44 import org.glassfish.jersey.test.JerseyTest;
45 import org.junit.BeforeClass;
46 import org.junit.Ignore;
47 import org.junit.Test;
48 import org.mockito.invocation.InvocationOnMock;
49 import org.mockito.stubbing.Answer;
50 import org.opendaylight.controller.md.sal.dom.api.DOMMountPoint;
51 import org.opendaylight.controller.md.sal.dom.api.DOMMountPointService;
52 import org.opendaylight.controller.sal.rest.impl.JsonNormalizedNodeBodyReader;
53 import org.opendaylight.controller.sal.rest.impl.JsonToCompositeNodeProvider;
54 import org.opendaylight.controller.sal.rest.impl.NormalizedNodeJsonBodyWriter;
55 import org.opendaylight.controller.sal.rest.impl.NormalizedNodeXmlBodyWriter;
56 import org.opendaylight.controller.sal.rest.impl.RestconfApplication;
57 import org.opendaylight.controller.sal.rest.impl.RestconfDocumentedExceptionMapper;
58 import org.opendaylight.controller.sal.rest.impl.StructuredDataToJsonProvider;
59 import org.opendaylight.controller.sal.rest.impl.StructuredDataToXmlProvider;
60 import org.opendaylight.controller.sal.rest.impl.XmlNormalizedNodeBodyReader;
61 import org.opendaylight.controller.sal.rest.impl.XmlToCompositeNodeProvider;
62 import org.opendaylight.controller.sal.restconf.impl.BrokerFacade;
63 import org.opendaylight.controller.sal.restconf.impl.ControllerContext;
64 import org.opendaylight.controller.sal.restconf.impl.RestconfDocumentedException;
65 import org.opendaylight.controller.sal.restconf.impl.RestconfImpl;
66 import org.opendaylight.yangtools.yang.common.QName;
67 import org.opendaylight.yangtools.yang.data.api.CompositeNode;
68 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
69 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
70 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
71 import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
72 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
73 import org.opendaylight.yangtools.yang.data.impl.ImmutableCompositeNode;
74 import org.opendaylight.yangtools.yang.data.impl.schema.Builders;
75 import org.opendaylight.yangtools.yang.data.impl.schema.builder.impl.ImmutableLeafNodeBuilder;
76 import org.opendaylight.yangtools.yang.data.impl.schema.builder.impl.ImmutableMapEntryNodeBuilder;
77 import org.opendaylight.yangtools.yang.data.impl.util.CompositeNodeBuilder;
78 import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
79 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
80 import org.opendaylight.yangtools.yang.model.api.Module;
81 import org.opendaylight.yangtools.yang.model.api.RpcDefinition;
82 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
83 import org.w3c.dom.Document;
84 import org.w3c.dom.Element;
85 import org.w3c.dom.NodeList;
86
87 public class RestGetOperationTest extends JerseyTest {
88
89     static class NodeData {
90         Object key;
91         Object data; // List for a CompositeNode, value Object for a SimpleNode
92
93         NodeData(final Object key, final Object data) {
94             this.key = key;
95             this.data = data;
96         }
97     }
98
99     private static BrokerFacade brokerFacade;
100     private static RestconfImpl restconfImpl;
101     private static SchemaContext schemaContextYangsIetf;
102     private static SchemaContext schemaContextTestModule;
103     private static NormalizedNode answerFromGet;
104
105     private static SchemaContext schemaContextModules;
106     private static SchemaContext schemaContextBehindMountPoint;
107
108     private static final String RESTCONF_NS = "urn:ietf:params:xml:ns:yang:ietf-restconf";
109
110     @BeforeClass
111     public static void init() throws FileNotFoundException, ParseException {
112         schemaContextYangsIetf = TestUtils.loadSchemaContext("/full-versions/yangs");
113         schemaContextTestModule = TestUtils.loadSchemaContext("/full-versions/test-module");
114         brokerFacade = mock(BrokerFacade.class);
115         restconfImpl = RestconfImpl.getInstance();
116         restconfImpl.setBroker(brokerFacade);
117         answerFromGet = TestUtils.prepareNormalizedNodeWithIetfInterfacesInterfacesData();
118
119         schemaContextModules = TestUtils.loadSchemaContext("/modules");
120         schemaContextBehindMountPoint = TestUtils.loadSchemaContext("/modules/modules-behind-mount-point");
121     }
122
123     @Override
124     protected Application configure() {
125         /* enable/disable Jersey logs to console */
126         // enable(TestProperties.LOG_TRAFFIC);
127         // enable(TestProperties.DUMP_ENTITY);
128         // enable(TestProperties.RECORD_LOG_LEVEL);
129         // set(TestProperties.RECORD_LOG_LEVEL, Level.ALL.intValue());
130         ResourceConfig resourceConfig = new ResourceConfig();
131         resourceConfig = resourceConfig.registerInstances(restconfImpl, StructuredDataToXmlProvider.INSTANCE,
132                 StructuredDataToJsonProvider.INSTANCE, XmlToCompositeNodeProvider.INSTANCE,
133                 JsonToCompositeNodeProvider.INSTANCE, new NormalizedNodeJsonBodyWriter(), new NormalizedNodeXmlBodyWriter(),
134                 new XmlNormalizedNodeBodyReader(), new JsonNormalizedNodeBodyReader());
135         resourceConfig.registerClasses(RestconfDocumentedExceptionMapper.class);
136         resourceConfig.registerClasses(new RestconfApplication().getClasses());
137         return resourceConfig;
138     }
139
140     private void setControllerContext(final SchemaContext schemaContext) {
141         final ControllerContext controllerContext = ControllerContext.getInstance();
142         controllerContext.setSchemas(schemaContext);
143         restconfImpl.setControllerContext(controllerContext);
144     }
145
146     /**
147      * Tests of status codes for "/operational/{identifier}".
148      */
149     @Test
150     public void getOperationalStatusCodes() throws UnsupportedEncodingException {
151         setControllerContext(schemaContextYangsIetf);
152         mockReadOperationalDataMethod();
153         String uri = "/operational/ietf-interfaces:interfaces/interface/eth0";
154         assertEquals(200, get(uri, MediaType.APPLICATION_XML));
155
156         uri = "/operational/wrong-module:interfaces/interface/eth0";
157         assertEquals(400, get(uri, MediaType.APPLICATION_XML));
158     }
159
160     /**
161      * Tests of status codes for "/config/{identifier}".
162      */
163     @Test
164     public void getConfigStatusCodes() throws UnsupportedEncodingException {
165         setControllerContext(schemaContextYangsIetf);
166         mockReadConfigurationDataMethod();
167         String uri = "/config/ietf-interfaces:interfaces/interface/eth0";
168         assertEquals(200, get(uri, MediaType.APPLICATION_XML));
169
170         uri = "/config/wrong-module:interfaces/interface/eth0";
171         assertEquals(400, get(uri, MediaType.APPLICATION_XML));
172     }
173
174     /**
175      * MountPoint test. URI represents mount point.
176      */
177     @Test
178     public void getDataWithUrlMountPoint() throws UnsupportedEncodingException, URISyntaxException, ParseException {
179         when(brokerFacade.readConfigurationData(any(DOMMountPoint.class), any(YangInstanceIdentifier.class))).thenReturn(
180                 prepareCnDataForMountPointTest(false));
181         final DOMMountPoint mountInstance = mock(DOMMountPoint.class);
182         when(mountInstance.getSchemaContext()).thenReturn(schemaContextTestModule);
183         final DOMMountPointService mockMountService = mock(DOMMountPointService.class);
184         when(mockMountService.getMountPoint(any(YangInstanceIdentifier.class))).thenReturn(Optional.of(mountInstance));
185
186         ControllerContext.getInstance().setMountService(mockMountService);
187
188         String uri = "/config/ietf-interfaces:interfaces/interface/0/yang-ext:mount/test-module:cont/cont1";
189         assertEquals(200, get(uri, MediaType.APPLICATION_XML));
190
191         uri = "/config/ietf-interfaces:interfaces/yang-ext:mount/test-module:cont/cont1";
192         assertEquals(200, get(uri, MediaType.APPLICATION_XML));
193     }
194
195     /**
196      * MountPoint test. URI represents mount point.
197      *
198      * Slashes in URI behind mount point. lst1 element with key GigabitEthernet0%2F0%2F0%2F0 (GigabitEthernet0/0/0/0) is
199      * requested via GET HTTP operation. It is tested whether %2F character is replaced with simple / in
200      * InstanceIdentifier parameter in method
201      * {@link BrokerFacade#readConfigurationDataBehindMountPoint(MountInstance, YangInstanceIdentifier)} which is called in
202      * method {@link RestconfImpl#readConfigurationData}
203      *
204      * @throws ParseException
205      */
206     @Test
207     public void getDataWithSlashesBehindMountPoint() throws UnsupportedEncodingException, URISyntaxException,
208             ParseException {
209         final YangInstanceIdentifier awaitedInstanceIdentifier = prepareInstanceIdentifierForList();
210         when(brokerFacade.readConfigurationData(any(DOMMountPoint.class), eq(awaitedInstanceIdentifier))).thenReturn(
211                 prepareCnDataForSlashesBehindMountPointTest());
212         final DOMMountPoint mountInstance = mock(DOMMountPoint.class);
213         when(mountInstance.getSchemaContext()).thenReturn(schemaContextTestModule);
214         final DOMMountPointService mockMountService = mock(DOMMountPointService.class);
215         when(mockMountService.getMountPoint(any(YangInstanceIdentifier.class))).thenReturn(Optional.of(mountInstance));
216
217         ControllerContext.getInstance().setMountService(mockMountService);
218
219         final String uri = "/config/ietf-interfaces:interfaces/interface/0/yang-ext:mount/test-module:cont/lst1/GigabitEthernet0%2F0%2F0%2F0";
220         assertEquals(200, get(uri, MediaType.APPLICATION_XML));
221     }
222
223     private YangInstanceIdentifier prepareInstanceIdentifierForList() throws URISyntaxException, ParseException {
224         final List<PathArgument> parameters = new ArrayList<>();
225
226         final Date revision = new SimpleDateFormat("yyyy-MM-dd").parse("2014-01-09");
227         final URI uri = new URI("test:module");
228         final QName qNameCont = QName.create(uri, revision, "cont");
229         final QName qNameList = QName.create(uri, revision, "lst1");
230         final QName qNameKeyList = QName.create(uri, revision, "lf11");
231
232         parameters.add(new YangInstanceIdentifier.NodeIdentifier(qNameCont));
233         parameters.add(new YangInstanceIdentifier.NodeIdentifier(qNameList));
234         parameters.add(new YangInstanceIdentifier.NodeIdentifierWithPredicates(qNameList, qNameKeyList,
235                 "GigabitEthernet0/0/0/0"));
236         return YangInstanceIdentifier.create(parameters);
237     }
238
239     @Test
240     public void getDataMountPointIntoHighestElement() throws UnsupportedEncodingException, URISyntaxException,
241             ParseException {
242         when(brokerFacade.readConfigurationData(any(DOMMountPoint.class), any(YangInstanceIdentifier.class))).thenReturn(
243                 prepareCnDataForMountPointTest(true));
244         final DOMMountPoint mountInstance = mock(DOMMountPoint.class);
245         when(mountInstance.getSchemaContext()).thenReturn(schemaContextTestModule);
246         final DOMMountPointService mockMountService = mock(DOMMountPointService.class);
247         when(mockMountService.getMountPoint(any(YangInstanceIdentifier.class))).thenReturn(Optional.of(mountInstance));
248
249         ControllerContext.getInstance().setMountService(mockMountService);
250
251         final String uri = "/config/ietf-interfaces:interfaces/interface/0/yang-ext:mount/test-module:cont";
252         assertEquals(200, get(uri, MediaType.APPLICATION_XML));
253     }
254
255     // /modules
256     @Test
257     public void getModulesTest() throws UnsupportedEncodingException, FileNotFoundException {
258         final ControllerContext controllerContext = ControllerContext.getInstance();
259         controllerContext.setGlobalSchema(schemaContextModules);
260         restconfImpl.setControllerContext(controllerContext);
261
262         final String uri = "/modules";
263
264         Response response = target(uri).request("application/yang.api+json").get();
265         validateModulesResponseJson(response);
266
267         response = target(uri).request("application/yang.api+xml").get();
268         validateModulesResponseXml(response,schemaContextModules);
269     }
270
271     // /streams/
272     @Test
273     @Ignore // FIXME : find why it is fail by in gerrit build
274     public void getStreamsTest() throws UnsupportedEncodingException, FileNotFoundException {
275         setControllerContext(schemaContextModules);
276
277         final String uri = "/streams";
278
279         Response response = target(uri).request("application/yang.api+json").get();
280         final String responseBody = response.readEntity(String.class);
281         assertEquals(200, response.getStatus());
282         assertNotNull(responseBody);
283         assertTrue(responseBody.contains("streams"));
284
285         response = target(uri).request("application/yang.api+xml").get();
286         assertEquals(200, response.getStatus());
287         final Document responseXmlBody = response.readEntity(Document.class);
288         assertNotNull(responseXmlBody);
289         final Element rootNode = responseXmlBody.getDocumentElement();
290
291         assertEquals("streams", rootNode.getLocalName());
292         assertEquals(RESTCONF_NS, rootNode.getNamespaceURI());
293     }
294
295     // /modules/module
296     @Test
297     public void getModuleTest() throws FileNotFoundException, UnsupportedEncodingException {
298         setControllerContext(schemaContextModules);
299
300         final String uri = "/modules/module/module2/2014-01-02";
301
302         Response response = target(uri).request("application/yang.api+xml").get();
303         assertEquals(200, response.getStatus());
304         final Document responseXml = response.readEntity(Document.class);
305
306         final QName qname = assertedModuleXmlToModuleQName(responseXml.getDocumentElement());
307         assertNotNull(qname);
308
309         assertEquals("module2", qname.getLocalName());
310         assertEquals("module:2", qname.getNamespace().toString());
311         assertEquals("2014-01-02", qname.getFormattedRevision());
312
313         response = target(uri).request("application/yang.api+json").get();
314         assertEquals(200, response.getStatus());
315         final String responseBody = response.readEntity(String.class);
316         assertTrue("Module2 in json wasn't found", prepareJsonRegex("module2", "2014-01-02", "module:2", responseBody)
317                 .find());
318         final String[] split = responseBody.split("\"module\"");
319         assertEquals("\"module\" element is returned more then once", 2, split.length);
320
321     }
322
323     // /operations
324     @Test
325     public void getOperationsTest() throws FileNotFoundException, UnsupportedEncodingException {
326         setControllerContext(schemaContextModules);
327
328         final String uri = "/operations";
329
330         Response response = target(uri).request("application/yang.api+xml").get();
331         assertEquals(200, response.getStatus());
332         final Document responseDoc = response.readEntity(Document.class);
333         validateOperationsResponseXml(responseDoc, schemaContextModules);
334
335         response = target(uri).request("application/yang.api+json").get();
336         assertEquals(200, response.getStatus());
337         final String responseBody = response.readEntity(String.class);
338         assertTrue("Json response for /operations dummy-rpc1-module1 is incorrect",
339                 validateOperationsResponseJson(responseBody, "dummy-rpc1-module1", "module1").find());
340         assertTrue("Json response for /operations dummy-rpc2-module1 is incorrect",
341                 validateOperationsResponseJson(responseBody, "dummy-rpc2-module1", "module1").find());
342         assertTrue("Json response for /operations dummy-rpc1-module2 is incorrect",
343                 validateOperationsResponseJson(responseBody, "dummy-rpc1-module2", "module2").find());
344         assertTrue("Json response for /operations dummy-rpc2-module2 is incorrect",
345                 validateOperationsResponseJson(responseBody, "dummy-rpc2-module2", "module2").find());
346
347     }
348
349     private void validateOperationsResponseXml(final Document responseDoc, final SchemaContext schemaContext) {
350         final Element operationsElem = responseDoc.getDocumentElement();
351         assertEquals(RESTCONF_NS, operationsElem.getNamespaceURI());
352         assertEquals("operations", operationsElem.getLocalName());
353
354
355         final HashSet<QName> foundOperations = new HashSet<>();
356
357         final NodeList operationsList = operationsElem.getChildNodes();
358         for(int i = 0;i < operationsList.getLength();i++) {
359             final org.w3c.dom.Node operation = operationsList.item(i);
360
361             final String namespace = operation.getNamespaceURI();
362             final String name = operation.getLocalName();
363             final QName opQName = QName.create(URI.create(namespace), null, name);
364             foundOperations.add(opQName);
365         }
366
367         for(final RpcDefinition schemaOp : schemaContext.getOperations()) {
368             assertTrue(foundOperations.contains(schemaOp.getQName().withoutRevision()));
369         }
370
371     }
372
373     // /operations/pathToMountPoint/yang-ext:mount
374     @Test
375     public void getOperationsBehindMountPointTest() throws FileNotFoundException, UnsupportedEncodingException {
376         setControllerContext(schemaContextModules);
377
378         final DOMMountPoint mountInstance = mock(DOMMountPoint.class);
379         when(mountInstance.getSchemaContext()).thenReturn(schemaContextBehindMountPoint);
380         final DOMMountPointService mockMountService = mock(DOMMountPointService.class);
381         when(mockMountService.getMountPoint(any(YangInstanceIdentifier.class))).thenReturn(Optional.of(mountInstance));
382
383         ControllerContext.getInstance().setMountService(mockMountService);
384
385         final String uri = "/operations/ietf-interfaces:interfaces/interface/0/yang-ext:mount/";
386
387         Response response = target(uri).request("application/yang.api+xml").get();
388         assertEquals(200, response.getStatus());
389
390         final Document responseDoc = response.readEntity(Document.class);
391         validateOperationsResponseXml(responseDoc, schemaContextBehindMountPoint);
392
393         response = target(uri).request("application/yang.api+json").get();
394         assertEquals(200, response.getStatus());
395         final String responseBody = response.readEntity(String.class);
396         assertTrue("Json response for /operations/mount_point rpc-behind-module1 is incorrect",
397                 validateOperationsResponseJson(responseBody, "rpc-behind-module1", "module1-behind-mount-point").find());
398         assertTrue("Json response for /operations/mount_point rpc-behind-module2 is incorrect",
399                 validateOperationsResponseJson(responseBody, "rpc-behind-module2", "module2-behind-mount-point").find());
400
401     }
402
403     private Matcher validateOperationsResponseJson(final String searchIn, final String rpcName, final String moduleName) {
404         final StringBuilder regex = new StringBuilder();
405         regex.append("^");
406
407         regex.append(".*\\{");
408         regex.append(".*\"");
409
410         // operations prefix optional
411         regex.append("(");
412         regex.append("ietf-restconf:");
413         regex.append("|)");
414         // :operations prefix optional
415
416         regex.append("operations\"");
417         regex.append(".*:");
418         regex.append(".*\\{");
419
420         regex.append(".*\"" + moduleName);
421         regex.append(":");
422         regex.append(rpcName + "\"");
423         regex.append(".*\\[");
424         regex.append(".*null");
425         regex.append(".*\\]");
426
427         regex.append(".*\\}");
428         regex.append(".*\\}");
429
430         regex.append(".*");
431         regex.append("$");
432         final Pattern ptrn = Pattern.compile(regex.toString(), Pattern.DOTALL);
433         return ptrn.matcher(searchIn);
434
435     }
436
437     private Matcher validateOperationsResponseXml(final String searchIn, final String rpcName, final String namespace) {
438         final StringBuilder regex = new StringBuilder();
439
440         regex.append("^");
441
442         regex.append(".*<operations");
443         regex.append(".*xmlns=\"urn:ietf:params:xml:ns:yang:ietf-restconf\"");
444         regex.append(".*>");
445
446         regex.append(".*<");
447         regex.append(".*" + rpcName);
448         regex.append(".*" + namespace);
449         regex.append(".*/");
450         regex.append(".*>");
451
452         regex.append(".*</operations.*");
453         regex.append(".*>");
454
455         regex.append(".*");
456         regex.append("$");
457         final Pattern ptrn = Pattern.compile(regex.toString(), Pattern.DOTALL);
458         return ptrn.matcher(searchIn);
459     }
460
461     // /restconf/modules/pathToMountPoint/yang-ext:mount
462     @Test
463     public void getModulesBehindMountPoint() throws FileNotFoundException, UnsupportedEncodingException {
464         setControllerContext(schemaContextModules);
465
466         final DOMMountPoint mountInstance = mock(DOMMountPoint.class);
467         when(mountInstance.getSchemaContext()).thenReturn(schemaContextBehindMountPoint);
468         final DOMMountPointService mockMountService = mock(DOMMountPointService.class);
469         when(mockMountService.getMountPoint(any(YangInstanceIdentifier.class))).thenReturn(Optional.of(mountInstance));
470
471         ControllerContext.getInstance().setMountService(mockMountService);
472
473         final String uri = "/modules/ietf-interfaces:interfaces/interface/0/yang-ext:mount/";
474
475         Response response = target(uri).request("application/yang.api+json").get();
476         assertEquals(200, response.getStatus());
477         final String responseBody = response.readEntity(String.class);
478
479         assertTrue(
480                 "module1-behind-mount-point in json wasn't found",
481                 prepareJsonRegex("module1-behind-mount-point", "2014-02-03", "module:1:behind:mount:point",
482                         responseBody).find());
483         assertTrue(
484                 "module2-behind-mount-point in json wasn't found",
485                 prepareJsonRegex("module2-behind-mount-point", "2014-02-04", "module:2:behind:mount:point",
486                         responseBody).find());
487
488         response = target(uri).request("application/yang.api+xml").get();
489         assertEquals(200, response.getStatus());
490         validateModulesResponseXml(response, schemaContextBehindMountPoint);
491
492     }
493
494     // /restconf/modules/module/pathToMountPoint/yang-ext:mount/moduleName/revision
495     @Test
496     public void getModuleBehindMountPoint() throws FileNotFoundException, UnsupportedEncodingException {
497         setControllerContext(schemaContextModules);
498
499         final DOMMountPoint mountInstance = mock(DOMMountPoint.class);
500         when(mountInstance.getSchemaContext()).thenReturn(schemaContextBehindMountPoint);
501         final DOMMountPointService mockMountService = mock(DOMMountPointService.class);
502         when(mockMountService.getMountPoint(any(YangInstanceIdentifier.class))).thenReturn(Optional.of(mountInstance));
503
504         ControllerContext.getInstance().setMountService(mockMountService);
505
506         final String uri = "/modules/module/ietf-interfaces:interfaces/interface/0/yang-ext:mount/module1-behind-mount-point/2014-02-03";
507
508         Response response = target(uri).request("application/yang.api+json").get();
509         assertEquals(200, response.getStatus());
510         final String responseBody = response.readEntity(String.class);
511
512         assertTrue(
513                 "module1-behind-mount-point in json wasn't found",
514                 prepareJsonRegex("module1-behind-mount-point", "2014-02-03", "module:1:behind:mount:point",
515                         responseBody).find());
516         final String[] split = responseBody.split("\"module\"");
517         assertEquals("\"module\" element is returned more then once", 2, split.length);
518
519         response = target(uri).request("application/yang.api+xml").get();
520         assertEquals(200, response.getStatus());
521         final Document responseXml = response.readEntity(Document.class);
522
523         final QName module = assertedModuleXmlToModuleQName(responseXml.getDocumentElement());
524
525         assertEquals("module1-behind-mount-point", module.getLocalName());
526         assertEquals("2014-02-03", module.getFormattedRevision());
527         assertEquals("module:1:behind:mount:point", module.getNamespace().toString());
528
529
530     }
531
532     private void validateModulesResponseXml(final Response response, final SchemaContext schemaContext) {
533         assertEquals(200, response.getStatus());
534         final Document responseBody = response.readEntity(Document.class);
535         final NodeList moduleNodes = responseBody.getDocumentElement().getElementsByTagNameNS(RESTCONF_NS, "module");
536
537         assertTrue(moduleNodes.getLength() > 0);
538
539         final HashSet<QName> foundModules = new HashSet<>();
540
541         for(int i=0;i < moduleNodes.getLength();i++) {
542             final org.w3c.dom.Node module = moduleNodes.item(i);
543
544             final QName name = assertedModuleXmlToModuleQName(module);
545             foundModules.add(name);
546         }
547
548         assertAllModules(foundModules,schemaContext);
549     }
550
551     private void assertAllModules(final Set<QName> foundModules, final SchemaContext schemaContext) {
552         for(final Module module : schemaContext.getModules()) {
553             final QName current = QName.create(module.getQNameModule(),module.getName());
554             assertTrue("Module not found in response.",foundModules.contains(current));
555         }
556
557     }
558
559     private QName assertedModuleXmlToModuleQName(final org.w3c.dom.Node module) {
560         assertEquals("module", module.getLocalName());
561         assertEquals(RESTCONF_NS, module.getNamespaceURI());
562         String revision = null;
563         String namespace = null;
564         String name = null;
565
566
567         final NodeList childNodes = module.getChildNodes();
568
569         for(int i =0;i < childNodes.getLength(); i++) {
570             final org.w3c.dom.Node child = childNodes.item(i);
571             assertEquals(RESTCONF_NS, child.getNamespaceURI());
572
573             switch(child.getLocalName()) {
574                 case "name":
575                     assertNull("Name element appeared multiple times",name);
576                     name = child.getTextContent().trim();
577                     break;
578                 case "revision":
579                     assertNull("Revision element appeared multiple times",revision);
580                     revision = child.getTextContent().trim();
581                     break;
582
583                 case "namespace":
584                     assertNull("Namespace element appeared multiple times",namespace);
585                     namespace = child.getTextContent().trim();
586                     break;
587             }
588         }
589
590         assertNotNull("Revision was not part of xml",revision);
591         assertNotNull("Module namespace was not part of xml",namespace);
592         assertNotNull("Module identiffier was not part of xml",name);
593
594
595         // TODO Auto-generated method stub
596
597         return QName.create(namespace,revision,name);
598     }
599
600     private void validateModulesResponseJson(final Response response) {
601         assertEquals(200, response.getStatus());
602         final String responseBody = response.readEntity(String.class);
603
604         assertTrue("Module1 in json wasn't found", prepareJsonRegex("module1", "2014-01-01", "module:1", responseBody)
605                 .find());
606         assertTrue("Module2 in json wasn't found", prepareJsonRegex("module2", "2014-01-02", "module:2", responseBody)
607                 .find());
608         assertTrue("Module3 in json wasn't found", prepareJsonRegex("module3", "2014-01-03", "module:3", responseBody)
609                 .find());
610     }
611
612     private Matcher prepareJsonRegex(final String module, final String revision, final String namespace,
613             final String searchIn) {
614         final StringBuilder regex = new StringBuilder();
615         regex.append("^");
616
617         regex.append(".*\\{");
618         regex.append(".*\"name\"");
619         regex.append(".*:");
620         regex.append(".*\"" + module + "\",");
621
622         regex.append(".*\"revision\"");
623         regex.append(".*:");
624         regex.append(".*\"" + revision + "\",");
625
626         regex.append(".*\"namespace\"");
627         regex.append(".*:");
628         regex.append(".*\"" + namespace + "\"");
629
630         regex.append(".*\\}");
631
632         regex.append(".*");
633         regex.append("$");
634         final Pattern ptrn = Pattern.compile(regex.toString(), Pattern.DOTALL);
635         return ptrn.matcher(searchIn);
636
637     }
638
639
640     private void prepareMockForModulesTest(final ControllerContext mockedControllerContext)
641             throws FileNotFoundException {
642         final SchemaContext schemaContext = TestUtils.loadSchemaContext("/modules");
643         mockedControllerContext.setGlobalSchema(schemaContext);
644         // when(mockedControllerContext.getGlobalSchema()).thenReturn(schemaContext);
645     }
646
647     private int get(final String uri, final String mediaType) {
648         return target(uri).request(mediaType).get().getStatus();
649     }
650
651     /**
652     container cont {
653         container cont1 {
654             leaf lf11 {
655                 type string;
656             }
657     */
658     private NormalizedNode prepareCnDataForMountPointTest(final boolean wrapToCont) throws URISyntaxException, ParseException {
659         final String testModuleDate = "2014-01-09";
660         final ContainerNode contChild = Builders
661                 .containerBuilder()
662                 .withNodeIdentifier(TestUtils.getNodeIdentifier("cont1", "test:module", testModuleDate))
663                 .withChild(
664                         Builders.leafBuilder()
665                                 .withNodeIdentifier(TestUtils.getNodeIdentifier("lf11", "test:module", testModuleDate))
666                                 .withValue("lf11 value").build()).build();
667
668         if (wrapToCont) {
669             return Builders.containerBuilder()
670                     .withNodeIdentifier(TestUtils.getNodeIdentifier("cont", "test:module", testModuleDate))
671                     .withChild(contChild).build();
672         }
673         return contChild;
674
675     }
676
677     private void mockReadOperationalDataMethod() {
678         when(brokerFacade.readOperationalData(any(YangInstanceIdentifier.class))).thenReturn(answerFromGet);
679     }
680
681     private void mockReadConfigurationDataMethod() {
682         when(brokerFacade.readConfigurationData(any(YangInstanceIdentifier.class))).thenReturn(answerFromGet);
683     }
684
685     private NormalizedNode prepareCnDataForSlashesBehindMountPointTest() throws ParseException {
686         return ImmutableMapEntryNodeBuilder
687                 .create()
688                 .withNodeIdentifier(
689                         TestUtils.getNodeIdentifierPredicate("lst1", "test:module", "2014-01-09", "lf11",
690                                 "GigabitEthernet0/0/0/0"))
691                 .withChild(
692                         ImmutableLeafNodeBuilder.create()
693                                 .withNodeIdentifier(TestUtils.getNodeIdentifier("lf11", "test:module", "2014-01-09"))
694                                 .withValue("GigabitEthernet0/0/0/0").build()).build();
695
696     }
697
698     /**
699      * If includeWhiteChars URI parameter is set to false then no white characters can be included in returned output
700      *
701      * @throws UnsupportedEncodingException
702      */
703     @Test
704     public void getDataWithUriIncludeWhiteCharsParameterTest() throws UnsupportedEncodingException {
705         getDataWithUriIncludeWhiteCharsParameter("config");
706         getDataWithUriIncludeWhiteCharsParameter("operational");
707     }
708
709     private void getDataWithUriIncludeWhiteCharsParameter(final String target) throws UnsupportedEncodingException {
710         mockReadConfigurationDataMethod();
711         mockReadOperationalDataMethod();
712         final String uri = "/" + target + "/ietf-interfaces:interfaces/interface/eth0";
713         Response response = target(uri).queryParam("prettyPrint", "false").request("application/xml").get();
714         final String xmlData = response.readEntity(String.class);
715
716         Pattern pattern = Pattern.compile(".*(>\\s+|\\s+<).*", Pattern.DOTALL);
717         Matcher matcher = pattern.matcher(xmlData);
718         // XML element can't surrounded with white character (e.g ">    " or
719         // "    <")
720         assertFalse(matcher.matches());
721
722         response = target(uri).queryParam("prettyPrint", "false").request("application/json").get();
723         final String jsonData = response.readEntity(String.class);
724         pattern = Pattern.compile(".*(\\}\\s+|\\s+\\{|\\]\\s+|\\s+\\[|\\s+:|:\\s+).*", Pattern.DOTALL);
725         matcher = pattern.matcher(jsonData);
726         // JSON element can't surrounded with white character (e.g "} ", " {",
727         // "] ", " [", " :" or ": ")
728         assertFalse(matcher.matches());
729     }
730
731     @Test
732     @Ignore
733     public void getDataWithUriDepthParameterTest() throws UnsupportedEncodingException {
734         setControllerContext(schemaContextModules);
735
736         final CompositeNode depth1Cont = toCompositeNode(toCompositeNodeData(
737                 toNestedQName("depth1-cont"),
738                 toCompositeNodeData(
739                         toNestedQName("depth2-cont1"),
740                         toCompositeNodeData(
741                                 toNestedQName("depth3-cont1"),
742                                 toCompositeNodeData(toNestedQName("depth4-cont1"),
743                                         toSimpleNodeData(toNestedQName("depth5-leaf1"), "depth5-leaf1-value")),
744                                 toSimpleNodeData(toNestedQName("depth4-leaf1"), "depth4-leaf1-value")),
745                         toSimpleNodeData(toNestedQName("depth3-leaf1"), "depth3-leaf1-value")),
746                 toCompositeNodeData(
747                         toNestedQName("depth2-cont2"),
748                         toCompositeNodeData(
749                                 toNestedQName("depth3-cont2"),
750                                 toCompositeNodeData(toNestedQName("depth4-cont2"),
751                                         toSimpleNodeData(toNestedQName("depth5-leaf2"), "depth5-leaf2-value")),
752                                 toSimpleNodeData(toNestedQName("depth4-leaf2"), "depth4-leaf2-value")),
753                         toSimpleNodeData(toNestedQName("depth3-leaf2"), "depth3-leaf2-value")),
754                 toSimpleNodeData(toNestedQName("depth2-leaf1"), "depth2-leaf1-value")));
755
756         final Module module = TestUtils.findModule(schemaContextModules.getModules(), "nested-module");
757         assertNotNull(module);
758
759         final DataSchemaNode dataSchemaNode = TestUtils.resolveDataSchemaNode("depth1-cont", module);
760         assertNotNull(dataSchemaNode);
761
762         when(brokerFacade.readConfigurationData(any(YangInstanceIdentifier.class))).thenReturn(
763                 TestUtils.compositeNodeToDatastoreNormalizedNode(depth1Cont, dataSchemaNode));
764
765         // Test config with depth 1
766
767         Response response = target("/config/nested-module:depth1-cont").queryParam("depth", "1")
768                 .request("application/xml").get();
769
770         verifyXMLResponse(response, expectEmptyContainer("depth1-cont"));
771
772         // Test config with depth 2
773
774         response = target("/config/nested-module:depth1-cont").queryParam("depth", "2").request("application/xml")
775                 .get();
776
777         // String
778         // xml="<depth1-cont><depth2-cont1/><depth2-cont2/><depth2-leaf1>depth2-leaf1-value</depth2-leaf1></depth1-cont>";
779         // Response mr=mock(Response.class);
780         // when(mr.getEntity()).thenReturn( new
781         // java.io.StringBufferInputStream(xml) );
782
783         verifyXMLResponse(
784                 response,
785                 expectContainer("depth1-cont", expectEmptyContainer("depth2-cont1"),
786                         expectEmptyContainer("depth2-cont2"), expectLeaf("depth2-leaf1", "depth2-leaf1-value")));
787
788         // Test config with depth 3
789
790         response = target("/config/nested-module:depth1-cont").queryParam("depth", "3").request("application/xml")
791                 .get();
792
793         verifyXMLResponse(
794                 response,
795                 expectContainer(
796                         "depth1-cont",
797                         expectContainer("depth2-cont1", expectEmptyContainer("depth3-cont1"),
798                                 expectLeaf("depth3-leaf1", "depth3-leaf1-value")),
799                         expectContainer("depth2-cont2", expectEmptyContainer("depth3-cont2"),
800                                 expectLeaf("depth3-leaf2", "depth3-leaf2-value")),
801                         expectLeaf("depth2-leaf1", "depth2-leaf1-value")));
802
803         // Test config with depth 4
804
805         response = target("/config/nested-module:depth1-cont").queryParam("depth", "4").request("application/xml")
806                 .get();
807
808         verifyXMLResponse(
809                 response,
810                 expectContainer(
811                         "depth1-cont",
812                         expectContainer(
813                                 "depth2-cont1",
814                                 expectContainer("depth3-cont1", expectEmptyContainer("depth4-cont1"),
815                                         expectLeaf("depth4-leaf1", "depth4-leaf1-value")),
816                                 expectLeaf("depth3-leaf1", "depth3-leaf1-value")),
817                         expectContainer(
818                                 "depth2-cont2",
819                                 expectContainer("depth3-cont2", expectEmptyContainer("depth4-cont2"),
820                                         expectLeaf("depth4-leaf2", "depth4-leaf2-value")),
821                                 expectLeaf("depth3-leaf2", "depth3-leaf2-value")),
822                         expectLeaf("depth2-leaf1", "depth2-leaf1-value")));
823
824         // Test config with depth 5
825
826         response = target("/config/nested-module:depth1-cont").queryParam("depth", "5").request("application/xml")
827                 .get();
828
829         verifyXMLResponse(
830                 response,
831                 expectContainer(
832                         "depth1-cont",
833                         expectContainer(
834                                 "depth2-cont1",
835                                 expectContainer(
836                                         "depth3-cont1",
837                                         expectContainer("depth4-cont1",
838                                                 expectLeaf("depth5-leaf1", "depth5-leaf1-value")),
839                                         expectLeaf("depth4-leaf1", "depth4-leaf1-value")),
840                                 expectLeaf("depth3-leaf1", "depth3-leaf1-value")),
841                         expectContainer(
842                                 "depth2-cont2",
843                                 expectContainer(
844                                         "depth3-cont2",
845                                         expectContainer("depth4-cont2",
846                                                 expectLeaf("depth5-leaf2", "depth5-leaf2-value")),
847                                         expectLeaf("depth4-leaf2", "depth4-leaf2-value")),
848                                 expectLeaf("depth3-leaf2", "depth3-leaf2-value")),
849                         expectLeaf("depth2-leaf1", "depth2-leaf1-value")));
850
851         // Test config with depth unbounded
852
853         response = target("/config/nested-module:depth1-cont").queryParam("depth", "unbounded")
854                 .request("application/xml").get();
855
856         verifyXMLResponse(
857                 response,
858                 expectContainer(
859                         "depth1-cont",
860                         expectContainer(
861                                 "depth2-cont1",
862                                 expectContainer(
863                                         "depth3-cont1",
864                                         expectContainer("depth4-cont1",
865                                                 expectLeaf("depth5-leaf1", "depth5-leaf1-value")),
866                                         expectLeaf("depth4-leaf1", "depth4-leaf1-value")),
867                                 expectLeaf("depth3-leaf1", "depth3-leaf1-value")),
868                         expectContainer(
869                                 "depth2-cont2",
870                                 expectContainer(
871                                         "depth3-cont2",
872                                         expectContainer("depth4-cont2",
873                                                 expectLeaf("depth5-leaf2", "depth5-leaf2-value")),
874                                         expectLeaf("depth4-leaf2", "depth4-leaf2-value")),
875                                 expectLeaf("depth3-leaf2", "depth3-leaf2-value")),
876                         expectLeaf("depth2-leaf1", "depth2-leaf1-value")));
877
878         // Test operational
879
880         final CompositeNode depth2Cont1 = toCompositeNode(toCompositeNodeData(
881                 toNestedQName("depth2-cont1"),
882                 toCompositeNodeData(
883                         toNestedQName("depth3-cont1"),
884                         toCompositeNodeData(toNestedQName("depth4-cont1"),
885                                 toSimpleNodeData(toNestedQName("depth5-leaf1"), "depth5-leaf1-value")),
886                         toSimpleNodeData(toNestedQName("depth4-leaf1"), "depth4-leaf1-value")),
887                 toSimpleNodeData(toNestedQName("depth3-leaf1"), "depth3-leaf1-value")));
888
889         assertTrue(dataSchemaNode instanceof DataNodeContainer);
890         DataSchemaNode depth2cont1Schema = null;
891         for (final DataSchemaNode childNode : ((DataNodeContainer) dataSchemaNode).getChildNodes()) {
892             if (childNode.getQName().getLocalName().equals("depth2-cont1")) {
893                 depth2cont1Schema = childNode;
894                 break;
895             }
896         }
897         assertNotNull(depth2Cont1);
898
899         when(brokerFacade.readOperationalData(any(YangInstanceIdentifier.class))).thenReturn(
900                 TestUtils.compositeNodeToDatastoreNormalizedNode(depth2Cont1, depth2cont1Schema));
901
902         response = target("/operational/nested-module:depth1-cont/depth2-cont1").queryParam("depth", "3")
903                 .request("application/xml").get();
904
905         verifyXMLResponse(
906                 response,
907                 expectContainer(
908                         "depth2-cont1",
909                         expectContainer("depth3-cont1", expectEmptyContainer("depth4-cont1"),
910                                 expectLeaf("depth4-leaf1", "depth4-leaf1-value")),
911                         expectLeaf("depth3-leaf1", "depth3-leaf1-value")));
912     }
913
914     /**
915      * Tests behavior when invalid value of depth URI parameter
916      */
917     @Test
918     @Ignore
919     public void getDataWithInvalidDepthParameterTest() {
920         setControllerContext(schemaContextModules);
921
922         final MultivaluedMap<String, String> paramMap = new MultivaluedHashMap<>();
923         paramMap.putSingle("depth", "1o");
924         final UriInfo mockInfo = mock(UriInfo.class);
925         when(mockInfo.getQueryParameters(false)).thenAnswer(new Answer<MultivaluedMap<String, String>>() {
926             @Override
927             public MultivaluedMap<String, String> answer(final InvocationOnMock invocation) {
928                 return paramMap;
929             }
930         });
931
932         getDataWithInvalidDepthParameterTest(mockInfo);
933
934         paramMap.putSingle("depth", "0");
935         getDataWithInvalidDepthParameterTest(mockInfo);
936
937         paramMap.putSingle("depth", "-1");
938         getDataWithInvalidDepthParameterTest(mockInfo);
939     }
940
941     private void getDataWithInvalidDepthParameterTest(final UriInfo uriInfo) {
942         try {
943             final QName qNameDepth1Cont = QName.create("urn:nested:module", "2014-06-3", "depth1-cont");
944             final YangInstanceIdentifier ii = YangInstanceIdentifier.builder().node(qNameDepth1Cont).build();
945             final NormalizedNode value = (Builders.containerBuilder().withNodeIdentifier(new NodeIdentifier(qNameDepth1Cont)).build());
946             when(brokerFacade.readConfigurationData(eq(ii))).thenReturn(value);
947             restconfImpl.readConfigurationData("nested-module:depth1-cont", uriInfo);
948             fail("Expected RestconfDocumentedException");
949         } catch (final RestconfDocumentedException e) {
950             assertTrue("Unexpected error message: " + e.getErrors().get(0).getErrorMessage(), e.getErrors().get(0)
951                     .getErrorMessage().contains("depth"));
952         }
953     }
954
955     private void verifyXMLResponse(final Response response, final NodeData nodeData) {
956         final Document doc = response.readEntity(Document.class);
957 //        Document doc = TestUtils.loadDocumentFrom((InputStream) response.getEntity());
958 //        System.out.println();
959         assertNotNull("Could not parse XML document", doc);
960
961         // System.out.println(TestUtils.getDocumentInPrintableForm( doc ));
962
963         verifyContainerElement(doc.getDocumentElement(), nodeData);
964     }
965
966     @SuppressWarnings("unchecked")
967     private void verifyContainerElement(final Element element, final NodeData nodeData) {
968
969         assertEquals("Element local name", nodeData.key, element.getLocalName());
970
971         final NodeList childNodes = element.getChildNodes();
972         if (nodeData.data == null) { // empty container
973             assertTrue("Expected no child elements for \"" + element.getLocalName() + "\"", childNodes.getLength() == 0);
974             return;
975         }
976
977         final Map<String, NodeData> expChildMap = Maps.newHashMap();
978         for (final NodeData expChild : (List<NodeData>) nodeData.data) {
979             expChildMap.put(expChild.key.toString(), expChild);
980         }
981
982         for (int i = 0; i < childNodes.getLength(); i++) {
983             final org.w3c.dom.Node actualChild = childNodes.item(i);
984             if (!(actualChild instanceof Element)) {
985                 continue;
986             }
987
988             final Element actualElement = (Element) actualChild;
989             final NodeData expChild = expChildMap.remove(actualElement.getLocalName());
990             assertNotNull(
991                     "Unexpected child element for parent \"" + element.getLocalName() + "\": "
992                             + actualElement.getLocalName(), expChild);
993
994             if (expChild.data == null || expChild.data instanceof List) {
995                 verifyContainerElement(actualElement, expChild);
996             } else {
997                 assertEquals("Text content for element: " + actualElement.getLocalName(), expChild.data,
998                         actualElement.getTextContent());
999             }
1000         }
1001
1002         if (!expChildMap.isEmpty()) {
1003             fail("Missing elements for parent \"" + element.getLocalName() + "\": " + expChildMap.keySet());
1004         }
1005     }
1006
1007     private NodeData expectContainer(final String name, final NodeData... childData) {
1008         return new NodeData(name, Lists.newArrayList(childData));
1009     }
1010
1011     private NodeData expectEmptyContainer(final String name) {
1012         return new NodeData(name, null);
1013     }
1014
1015     private NodeData expectLeaf(final String name, final Object value) {
1016         return new NodeData(name, value);
1017     }
1018
1019     private QName toNestedQName(final String localName) {
1020         return QName.create("urn:nested:module", "2014-06-3", localName);
1021     }
1022
1023     @SuppressWarnings("unchecked")
1024     private CompositeNode toCompositeNode(final NodeData nodeData) {
1025         final CompositeNodeBuilder<ImmutableCompositeNode> builder = ImmutableCompositeNode.builder();
1026         builder.setQName((QName) nodeData.key);
1027
1028         for (final NodeData child : (List<NodeData>) nodeData.data) {
1029             if (child.data instanceof List) {
1030                 builder.add(toCompositeNode(child));
1031             } else {
1032                 builder.addLeaf((QName) child.key, child.data);
1033             }
1034         }
1035
1036         return builder.toInstance();
1037     }
1038
1039     private NodeData toCompositeNodeData(final QName key, final NodeData... childData) {
1040         return new NodeData(key, Lists.newArrayList(childData));
1041     }
1042
1043     private NodeData toSimpleNodeData(final QName key, final Object value) {
1044         return new NodeData(key, value);
1045     }
1046
1047 }