Thomas Vachuska
Committed by Gerrit Code Review

ONOS-542 Added ability for app bundle to carry it's own artifacts, including fea…

…ture repo. Fixed onos-package script. Added JSON output to CLI.

Change-Id: If4f2c774d3fc2d68c0a8e91b3084b99d7c75d927
...@@ -18,6 +18,7 @@ package org.onosproject.cli; ...@@ -18,6 +18,7 @@ package org.onosproject.cli;
18 import java.util.Comparator; 18 import java.util.Comparator;
19 19
20 import org.onosproject.cluster.ControllerNode; 20 import org.onosproject.cluster.ControllerNode;
21 +import org.onosproject.core.Application;
21 import org.onosproject.core.ApplicationId; 22 import org.onosproject.core.ApplicationId;
22 import org.onosproject.net.ConnectPoint; 23 import org.onosproject.net.ConnectPoint;
23 import org.onosproject.net.Element; 24 import org.onosproject.net.Element;
...@@ -43,6 +44,13 @@ public final class Comparators { ...@@ -43,6 +44,13 @@ public final class Comparators {
43 } 44 }
44 }; 45 };
45 46
47 + public static final Comparator<Application> APP_COMPARATOR = new Comparator<Application>() {
48 + @Override
49 + public int compare(Application app1, Application app2) {
50 + return app1.id().id() - app2.id().id();
51 + }
52 + };
53 +
46 public static final Comparator<ElementId> ELEMENT_ID_COMPARATOR = new Comparator<ElementId>() { 54 public static final Comparator<ElementId> ELEMENT_ID_COMPARATOR = new Comparator<ElementId>() {
47 @Override 55 @Override
48 public int compare(ElementId id1, ElementId id2) { 56 public int compare(ElementId id1, ElementId id2) {
......
...@@ -15,11 +15,19 @@ ...@@ -15,11 +15,19 @@
15 */ 15 */
16 package org.onosproject.cli.app; 16 package org.onosproject.cli.app;
17 17
18 +import com.fasterxml.jackson.databind.JsonNode;
19 +import com.fasterxml.jackson.databind.ObjectMapper;
20 +import com.fasterxml.jackson.databind.node.ArrayNode;
18 import org.apache.karaf.shell.commands.Command; 21 import org.apache.karaf.shell.commands.Command;
19 import org.onosproject.app.ApplicationService; 22 import org.onosproject.app.ApplicationService;
20 import org.onosproject.cli.AbstractShellCommand; 23 import org.onosproject.cli.AbstractShellCommand;
24 +import org.onosproject.cli.Comparators;
21 import org.onosproject.core.Application; 25 import org.onosproject.core.Application;
22 26
27 +import java.util.Collections;
28 +import java.util.List;
29 +
30 +import static com.google.common.collect.Lists.newArrayList;
23 import static org.onosproject.app.ApplicationState.ACTIVE; 31 import static org.onosproject.app.ApplicationState.ACTIVE;
24 32
25 /** 33 /**
...@@ -36,11 +44,44 @@ public class ApplicationsListCommand extends AbstractShellCommand { ...@@ -36,11 +44,44 @@ public class ApplicationsListCommand extends AbstractShellCommand {
36 @Override 44 @Override
37 protected void execute() { 45 protected void execute() {
38 ApplicationService service = get(ApplicationService.class); 46 ApplicationService service = get(ApplicationService.class);
39 - for (Application app : service.getApplications()) { 47 + List<Application> apps = newArrayList(service.getApplications());
48 + Collections.sort(apps, Comparators.APP_COMPARATOR);
49 +
50 + if (outputJson()) {
51 + print("%s", json(service, apps));
52 + } else {
53 + for (Application app : apps) {
40 print(FMT, service.getState(app.id()) == ACTIVE ? "*" : " ", 54 print(FMT, service.getState(app.id()) == ACTIVE ? "*" : " ",
41 app.id().id(), app.id().name(), app.version(), app.origin(), 55 app.id().id(), app.id().name(), app.version(), app.origin(),
42 - app.description(), app.features(), app.featuresRepo(), app.permissions()); 56 + app.description(), app.features(),
57 + app.featuresRepo().isPresent() ? app.featuresRepo().get().toString() : "",
58 + app.permissions());
59 + }
60 + }
43 } 61 }
62 +
63 + private JsonNode json(ApplicationService service, List<Application> apps) {
64 + ObjectMapper mapper = new ObjectMapper();
65 + ArrayNode result = mapper.createArrayNode();
66 + for (Application app : apps) {
67 + result.add(json(service, mapper, app));
68 + }
69 + return result;
70 + }
71 +
72 + protected JsonNode json(ApplicationService service, ObjectMapper mapper,
73 + Application app) {
74 + return mapper.createObjectNode()
75 + .put("name", app.id().name())
76 + .put("id", app.id().id())
77 + .put("version", app.version().toString())
78 + .put("description", app.description())
79 + .put("origin", app.origin())
80 + .put("permissions", app.permissions().toString())
81 + .put("featuresRepo", app.featuresRepo().isPresent() ?
82 + app.featuresRepo().get().toString() : "")
83 + .put("features", app.features().toString())
84 + .put("state", service.getState(app.id()).toString());
44 } 85 }
45 86
46 } 87 }
......
...@@ -19,6 +19,7 @@ import org.onosproject.core.Permission; ...@@ -19,6 +19,7 @@ import org.onosproject.core.Permission;
19 import org.onosproject.core.Version; 19 import org.onosproject.core.Version;
20 20
21 import java.net.URI; 21 import java.net.URI;
22 +import java.util.List;
22 import java.util.Optional; 23 import java.util.Optional;
23 import java.util.Set; 24 import java.util.Set;
24 25
...@@ -71,10 +72,10 @@ public interface ApplicationDescription { ...@@ -71,10 +72,10 @@ public interface ApplicationDescription {
71 Optional<URI> featuresRepo(); 72 Optional<URI> featuresRepo();
72 73
73 /** 74 /**
74 - * Returns the set of features comprising the application. At least one 75 + * Returns the list of features comprising the application. At least one
75 * feature must be given. 76 * feature must be given.
76 * 77 *
77 * @return application features 78 * @return application features
78 */ 79 */
79 - Set<String> features(); 80 + List<String> features();
80 } 81 }
......
...@@ -19,6 +19,7 @@ import org.onosproject.core.Permission; ...@@ -19,6 +19,7 @@ import org.onosproject.core.Permission;
19 import org.onosproject.core.Version; 19 import org.onosproject.core.Version;
20 20
21 import java.net.URI; 21 import java.net.URI;
22 +import java.util.List;
22 import java.util.Optional; 23 import java.util.Optional;
23 import java.util.Set; 24 import java.util.Set;
24 25
...@@ -37,7 +38,7 @@ public class DefaultApplicationDescription implements ApplicationDescription { ...@@ -37,7 +38,7 @@ public class DefaultApplicationDescription implements ApplicationDescription {
37 private final String origin; 38 private final String origin;
38 private final Set<Permission> permissions; 39 private final Set<Permission> permissions;
39 private final Optional<URI> featuresRepo; 40 private final Optional<URI> featuresRepo;
40 - private final Set<String> features; 41 + private final List<String> features;
41 42
42 /** 43 /**
43 * Creates a new application descriptor using the supplied data. 44 * Creates a new application descriptor using the supplied data.
...@@ -53,7 +54,7 @@ public class DefaultApplicationDescription implements ApplicationDescription { ...@@ -53,7 +54,7 @@ public class DefaultApplicationDescription implements ApplicationDescription {
53 public DefaultApplicationDescription(String name, Version version, 54 public DefaultApplicationDescription(String name, Version version,
54 String description, String origin, 55 String description, String origin,
55 Set<Permission> permissions, 56 Set<Permission> permissions,
56 - URI featuresRepo, Set<String> features) { 57 + URI featuresRepo, List<String> features) {
57 this.name = checkNotNull(name, "Name cannot be null"); 58 this.name = checkNotNull(name, "Name cannot be null");
58 this.version = checkNotNull(version, "Version cannot be null"); 59 this.version = checkNotNull(version, "Version cannot be null");
59 this.description = checkNotNull(description, "Description cannot be null"); 60 this.description = checkNotNull(description, "Description cannot be null");
...@@ -95,7 +96,7 @@ public class DefaultApplicationDescription implements ApplicationDescription { ...@@ -95,7 +96,7 @@ public class DefaultApplicationDescription implements ApplicationDescription {
95 } 96 }
96 97
97 @Override 98 @Override
98 - public Set<String> features() { 99 + public List<String> features() {
99 return features; 100 return features;
100 } 101 }
101 102
......
...@@ -16,6 +16,7 @@ ...@@ -16,6 +16,7 @@
16 package org.onosproject.core; 16 package org.onosproject.core;
17 17
18 import java.net.URI; 18 import java.net.URI;
19 +import java.util.List;
19 import java.util.Optional; 20 import java.util.Optional;
20 import java.util.Set; 21 import java.util.Set;
21 22
...@@ -68,10 +69,10 @@ public interface Application { ...@@ -68,10 +69,10 @@ public interface Application {
68 Optional<URI> featuresRepo(); 69 Optional<URI> featuresRepo();
69 70
70 /** 71 /**
71 - * Returns the set of features comprising the application. At least one 72 + * Returns the list of features comprising the application. At least one
72 * feature must be given. 73 * feature must be given.
73 * 74 *
74 * @return application features 75 * @return application features
75 */ 76 */
76 - Set<String> features(); 77 + List<String> features();
77 } 78 }
......
...@@ -16,6 +16,7 @@ ...@@ -16,6 +16,7 @@
16 package org.onosproject.core; 16 package org.onosproject.core;
17 17
18 import java.net.URI; 18 import java.net.URI;
19 +import java.util.List;
19 import java.util.Objects; 20 import java.util.Objects;
20 import java.util.Optional; 21 import java.util.Optional;
21 import java.util.Set; 22 import java.util.Set;
...@@ -35,7 +36,7 @@ public class DefaultApplication implements Application { ...@@ -35,7 +36,7 @@ public class DefaultApplication implements Application {
35 private final String origin; 36 private final String origin;
36 private final Set<Permission> permissions; 37 private final Set<Permission> permissions;
37 private final Optional<URI> featuresRepo; 38 private final Optional<URI> featuresRepo;
38 - private final Set<String> features; 39 + private final List<String> features;
39 40
40 /** 41 /**
41 * Creates a new application descriptor using the supplied data. 42 * Creates a new application descriptor using the supplied data.
...@@ -51,7 +52,7 @@ public class DefaultApplication implements Application { ...@@ -51,7 +52,7 @@ public class DefaultApplication implements Application {
51 public DefaultApplication(ApplicationId appId, Version version, 52 public DefaultApplication(ApplicationId appId, Version version,
52 String description, String origin, 53 String description, String origin,
53 Set<Permission> permissions, 54 Set<Permission> permissions,
54 - Optional<URI> featuresRepo, Set<String> features) { 55 + Optional<URI> featuresRepo, List<String> features) {
55 this.appId = checkNotNull(appId, "ID cannot be null"); 56 this.appId = checkNotNull(appId, "ID cannot be null");
56 this.version = checkNotNull(version, "Version cannot be null"); 57 this.version = checkNotNull(version, "Version cannot be null");
57 this.description = checkNotNull(description, "Description cannot be null"); 58 this.description = checkNotNull(description, "Description cannot be null");
...@@ -93,7 +94,7 @@ public class DefaultApplication implements Application { ...@@ -93,7 +94,7 @@ public class DefaultApplication implements Application {
93 } 94 }
94 95
95 @Override 96 @Override
96 - public Set<String> features() { 97 + public List<String> features() {
97 return features; 98 return features;
98 } 99 }
99 100
......
...@@ -15,12 +15,14 @@ ...@@ -15,12 +15,14 @@
15 */ 15 */
16 package org.onosproject.app; 16 package org.onosproject.app;
17 17
18 +import com.google.common.collect.ImmutableList;
18 import com.google.common.collect.ImmutableSet; 19 import com.google.common.collect.ImmutableSet;
19 import org.junit.Test; 20 import org.junit.Test;
20 import org.onosproject.core.Permission; 21 import org.onosproject.core.Permission;
21 import org.onosproject.core.Version; 22 import org.onosproject.core.Version;
22 23
23 import java.net.URI; 24 import java.net.URI;
25 +import java.util.List;
24 import java.util.Set; 26 import java.util.Set;
25 27
26 import static org.junit.Assert.assertEquals; 28 import static org.junit.Assert.assertEquals;
...@@ -37,7 +39,7 @@ public class DefaultApplicationDescriptionTest { ...@@ -37,7 +39,7 @@ public class DefaultApplicationDescriptionTest {
37 public static final String ORIGIN = "Circus"; 39 public static final String ORIGIN = "Circus";
38 public static final Set<Permission> PERMS = ImmutableSet.of(); 40 public static final Set<Permission> PERMS = ImmutableSet.of();
39 public static final URI FURL = URI.create("mvn:org.foo-features/1.2a/xml/features"); 41 public static final URI FURL = URI.create("mvn:org.foo-features/1.2a/xml/features");
40 - public static final Set<String> FEATURES = ImmutableSet.of("foo"); 42 + public static final List<String> FEATURES = ImmutableList.of("foo", "bar");
41 43
42 @Test 44 @Test
43 public void basics() { 45 public void basics() {
......
...@@ -15,6 +15,7 @@ ...@@ -15,6 +15,7 @@
15 */ 15 */
16 package org.onosproject.common.app; 16 package org.onosproject.common.app;
17 17
18 +import com.google.common.collect.ImmutableList;
18 import com.google.common.collect.ImmutableSet; 19 import com.google.common.collect.ImmutableSet;
19 import com.google.common.io.ByteStreams; 20 import com.google.common.io.ByteStreams;
20 import com.google.common.io.Files; 21 import com.google.common.io.Files;
...@@ -40,6 +41,7 @@ import java.io.IOException; ...@@ -40,6 +41,7 @@ import java.io.IOException;
40 import java.io.InputStream; 41 import java.io.InputStream;
41 import java.net.URI; 42 import java.net.URI;
42 import java.nio.file.NoSuchFileException; 43 import java.nio.file.NoSuchFileException;
44 +import java.util.List;
43 import java.util.Set; 45 import java.util.Set;
44 import java.util.zip.ZipEntry; 46 import java.util.zip.ZipEntry;
45 import java.util.zip.ZipInputStream; 47 import java.util.zip.ZipInputStream;
...@@ -191,8 +193,7 @@ public class ApplicationArchive ...@@ -191,8 +193,7 @@ public class ApplicationArchive
191 ZipEntry entry; 193 ZipEntry entry;
192 while ((entry = zis.getNextEntry()) != null) { 194 while ((entry = zis.getNextEntry()) != null) {
193 if (entry.getName().equals(APP_XML)) { 195 if (entry.getName().equals(APP_XML)) {
194 - byte[] data = new byte[(int) entry.getSize()]; 196 + byte[] data = ByteStreams.toByteArray(zis);
195 - ByteStreams.readFully(zis, data);
196 XMLConfiguration cfg = new XMLConfiguration(); 197 XMLConfiguration cfg = new XMLConfiguration();
197 try { 198 try {
198 cfg.load(new ByteArrayInputStream(data)); 199 cfg.load(new ByteArrayInputStream(data));
...@@ -209,6 +210,7 @@ public class ApplicationArchive ...@@ -209,6 +210,7 @@ public class ApplicationArchive
209 210
210 private ApplicationDescription loadAppDescription(XMLConfiguration cfg) { 211 private ApplicationDescription loadAppDescription(XMLConfiguration cfg) {
211 cfg.setAttributeSplittingDisabled(true); 212 cfg.setAttributeSplittingDisabled(true);
213 + cfg.setDelimiterParsingDisabled(true);
212 String name = cfg.getString(NAME); 214 String name = cfg.getString(NAME);
213 Version version = Version.version(cfg.getString(VERSION)); 215 Version version = Version.version(cfg.getString(VERSION));
214 String desc = cfg.getString(DESCRIPTION); 216 String desc = cfg.getString(DESCRIPTION);
...@@ -216,7 +218,7 @@ public class ApplicationArchive ...@@ -216,7 +218,7 @@ public class ApplicationArchive
216 Set<Permission> perms = ImmutableSet.of(); 218 Set<Permission> perms = ImmutableSet.of();
217 String featRepo = cfg.getString(FEATURES_REPO); 219 String featRepo = cfg.getString(FEATURES_REPO);
218 URI featuresRepo = featRepo != null ? URI.create(featRepo) : null; 220 URI featuresRepo = featRepo != null ? URI.create(featRepo) : null;
219 - Set<String> features = ImmutableSet.copyOf(cfg.getString(FEATURES).split(",")); 221 + List<String> features = ImmutableList.copyOf(cfg.getStringArray(FEATURES));
220 222
221 return new DefaultApplicationDescription(name, version, desc, origin, 223 return new DefaultApplicationDescription(name, version, desc, origin,
222 perms, featuresRepo, features); 224 perms, featuresRepo, features);
...@@ -229,14 +231,15 @@ public class ApplicationArchive ...@@ -229,14 +231,15 @@ public class ApplicationArchive
229 ZipEntry entry; 231 ZipEntry entry;
230 File appDir = new File(appsDir, desc.name()); 232 File appDir = new File(appsDir, desc.name());
231 while ((entry = zis.getNextEntry()) != null) { 233 while ((entry = zis.getNextEntry()) != null) {
232 - byte[] data = new byte[(int) entry.getSize()]; 234 + if (!entry.isDirectory()) {
233 - ByteStreams.readFully(zis, data); 235 + byte[] data = ByteStreams.toByteArray(zis);
234 zis.closeEntry(); 236 zis.closeEntry();
235 237
236 File file = new File(appDir, entry.getName()); 238 File file = new File(appDir, entry.getName());
237 createParentDirs(file); 239 createParentDirs(file);
238 write(data, file); 240 write(data, file);
239 } 241 }
242 + }
240 zis.close(); 243 zis.close();
241 } 244 }
242 245
......
...@@ -46,9 +46,16 @@ sed "s/\$KARAF_VERSION/$KARAF_VERSION/g" \ ...@@ -46,9 +46,16 @@ sed "s/\$KARAF_VERSION/$KARAF_VERSION/g" \
46 sed "s/\$KARAF_VERSION/$KARAF_VERSION/g" \ 46 sed "s/\$KARAF_VERSION/$KARAF_VERSION/g" \
47 $ONOS_ROOT/tools/package/bin/onos > bin/onos 47 $ONOS_ROOT/tools/package/bin/onos > bin/onos
48 48
49 -# Stage the ONOS bundles 49 +# Stage the ONOS bundles, but only those that match the version
50 mkdir -p $KARAF_DIST/system/org/onosproject 50 mkdir -p $KARAF_DIST/system/org/onosproject
51 -cp -r $M2_REPO/org/onosproject $KARAF_DIST/system/org/ 51 +# cp -r $M2_REPO/org/onosproject/ $KARAF_DIST/system/org/
52 +find $M2_REPO/org/onosproject/ -type d -name $ONOS_POM_VERSION | while read line; do
53 + path=${line#*/onosproject/}
54 + artifact=${path%/$ONOS_POM_VERSION}
55 + mkdir -p $KARAF_DIST/system/org/onosproject/$artifact
56 + cp -r $M2_REPO/org/onosproject/$artifact/$ONOS_POM_VERSION \
57 + $KARAF_DIST/system/org/onosproject/$artifact/$ONOS_POM_VERSION
58 +done
52 59
53 export ONOS_FEATURES="${ONOS_FEATURES:-webconsole,onos-api,onos-core,onos-cli,onos-rest,onos-gui,onos-openflow,onos-app-fwd,onos-app-foo}" 60 export ONOS_FEATURES="${ONOS_FEATURES:-webconsole,onos-api,onos-core,onos-cli,onos-rest,onos-gui,onos-openflow,onos-app-fwd,onos-app-foo}"
54 61
......
...@@ -40,7 +40,7 @@ public final class ApplicationCodec extends JsonCodec<Application> { ...@@ -40,7 +40,7 @@ public final class ApplicationCodec extends JsonCodec<Application> {
40 .put("origin", app.origin()) 40 .put("origin", app.origin())
41 .put("permissions", app.permissions().toString()) 41 .put("permissions", app.permissions().toString())
42 .put("featuresRepo", app.featuresRepo().isPresent() ? 42 .put("featuresRepo", app.featuresRepo().isPresent() ?
43 - app.featuresRepo().toString() : "") 43 + app.featuresRepo().get().toString() : "")
44 .put("features", app.features().toString()) 44 .put("features", app.features().toString())
45 .put("state", service.getState(app.id()).toString()); 45 .put("state", service.getState(app.id()).toString());
46 return result; 46 return result;
......
...@@ -15,10 +15,11 @@ ...@@ -15,10 +15,11 @@
15 */ 15 */
16 package org.onosproject.rest; 16 package org.onosproject.rest;
17 17
18 -import java.io.InputStream; 18 +import com.eclipsesource.json.JsonArray;
19 -import java.net.URI; 19 +import com.eclipsesource.json.JsonObject;
20 -import java.util.Optional; 20 +import com.google.common.collect.ImmutableList;
21 - 21 +import com.google.common.collect.ImmutableSet;
22 +import com.sun.jersey.api.client.WebResource;
22 import org.hamcrest.Description; 23 import org.hamcrest.Description;
23 import org.hamcrest.TypeSafeMatcher; 24 import org.hamcrest.TypeSafeMatcher;
24 import org.junit.After; 25 import org.junit.After;
...@@ -40,22 +41,14 @@ import org.onosproject.core.DefaultApplication; ...@@ -40,22 +41,14 @@ import org.onosproject.core.DefaultApplication;
40 import org.onosproject.core.DefaultApplicationId; 41 import org.onosproject.core.DefaultApplicationId;
41 import org.onosproject.core.Version; 42 import org.onosproject.core.Version;
42 43
43 -import com.eclipsesource.json.JsonArray; 44 +import java.io.InputStream;
44 -import com.eclipsesource.json.JsonObject; 45 +import java.net.URI;
45 -import com.google.common.collect.ImmutableSet; 46 +import java.util.Optional;
46 -import com.sun.jersey.api.client.WebResource;
47 47
48 -import static org.easymock.EasyMock.createMock; 48 +import static org.easymock.EasyMock.*;
49 -import static org.easymock.EasyMock.expect;
50 -import static org.easymock.EasyMock.expectLastCall;
51 import static org.easymock.EasyMock.isA; 49 import static org.easymock.EasyMock.isA;
52 -import static org.easymock.EasyMock.replay;
53 -import static org.easymock.EasyMock.verify;
54 import static org.hamcrest.MatcherAssert.assertThat; 50 import static org.hamcrest.MatcherAssert.assertThat;
55 -import static org.hamcrest.Matchers.containsString; 51 +import static org.hamcrest.Matchers.*;
56 -import static org.hamcrest.Matchers.hasSize;
57 -import static org.hamcrest.Matchers.is;
58 -import static org.hamcrest.Matchers.notNullValue;
59 52
60 /** 53 /**
61 * Unit tests for applications REST APIs. 54 * Unit tests for applications REST APIs.
...@@ -89,19 +82,19 @@ public class ApplicationsResourceTest extends ResourceTest { ...@@ -89,19 +82,19 @@ public class ApplicationsResourceTest extends ResourceTest {
89 private Application app1 = 82 private Application app1 =
90 new DefaultApplication(id1, VER, 83 new DefaultApplication(id1, VER,
91 "app1", "origin1", ImmutableSet.of(), Optional.of(FURL), 84 "app1", "origin1", ImmutableSet.of(), Optional.of(FURL),
92 - ImmutableSet.of("My Feature")); 85 + ImmutableList.of("My Feature"));
93 private Application app2 = 86 private Application app2 =
94 new DefaultApplication(id2, VER, 87 new DefaultApplication(id2, VER,
95 "app2", "origin2", ImmutableSet.of(), Optional.of(FURL), 88 "app2", "origin2", ImmutableSet.of(), Optional.of(FURL),
96 - ImmutableSet.of("My Feature")); 89 + ImmutableList.of("My Feature"));
97 private Application app3 = 90 private Application app3 =
98 new DefaultApplication(id3, VER, 91 new DefaultApplication(id3, VER,
99 "app3", "origin3", ImmutableSet.of(), Optional.of(FURL), 92 "app3", "origin3", ImmutableSet.of(), Optional.of(FURL),
100 - ImmutableSet.of("My Feature")); 93 + ImmutableList.of("My Feature"));
101 private Application app4 = 94 private Application app4 =
102 new DefaultApplication(id4, VER, 95 new DefaultApplication(id4, VER,
103 "app4", "origin4", ImmutableSet.of(), Optional.of(FURL), 96 "app4", "origin4", ImmutableSet.of(), Optional.of(FURL),
104 - ImmutableSet.of("My Feature")); 97 + ImmutableList.of("My Feature"));
105 98
106 /** 99 /**
107 * Hamcrest matcher to check that an device representation in JSON matches 100 * Hamcrest matcher to check that an device representation in JSON matches
......