/* * Copyright 2015-2016 Red Hat, Inc. and/or its affiliates * and other contributors as indicated by the @author tags. * * 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. */ package org.hawkular.inventory.json; import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; import static org.hawkular.inventory.api.Relationships.WellKnown.contains; import static org.hawkular.inventory.api.filters.Related.by; import static org.hawkular.inventory.api.filters.With.id; import static org.hawkular.inventory.api.filters.With.type; import static org.hawkular.inventory.api.model.MetricDataType.COUNTER; import static org.hawkular.inventory.api.model.MetricDataType.GAUGE; import java.beans.BeanInfo; import java.beans.Introspector; import java.beans.PropertyDescriptor; import java.io.IOException; import java.io.StringWriter; import java.lang.reflect.Array; import java.time.Instant; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.function.BiConsumer; import org.hawkular.inventory.api.Action; import org.hawkular.inventory.api.FilterFragment; import org.hawkular.inventory.api.Query; import org.hawkular.inventory.api.filters.Contained; import org.hawkular.inventory.api.filters.Defined; import org.hawkular.inventory.api.filters.Incorporated; import org.hawkular.inventory.api.filters.Marker; import org.hawkular.inventory.api.filters.RecurseFilter; import org.hawkular.inventory.api.filters.Related; import org.hawkular.inventory.api.filters.RelationWith; import org.hawkular.inventory.api.filters.SwitchElementType; import org.hawkular.inventory.api.filters.With; import org.hawkular.inventory.api.model.Change; import org.hawkular.inventory.api.model.DataEntity; import org.hawkular.inventory.api.model.Entity; import org.hawkular.inventory.api.model.Environment; import org.hawkular.inventory.api.model.Feed; import org.hawkular.inventory.api.model.IdentityHash; import org.hawkular.inventory.api.model.InventoryStructure; import org.hawkular.inventory.api.model.Metric; import org.hawkular.inventory.api.model.MetricType; import org.hawkular.inventory.api.model.MetricUnit; import org.hawkular.inventory.api.model.OperationType; import org.hawkular.inventory.api.model.Resource; import org.hawkular.inventory.api.model.ResourceType; import org.hawkular.inventory.api.model.StructuredData; import org.hawkular.inventory.api.model.SyncHash; import org.hawkular.inventory.api.model.Tenant; import org.hawkular.inventory.api.paging.Order; import org.hawkular.inventory.api.paging.Pager; import org.hawkular.inventory.base.spi.NoopFilter; import org.hawkular.inventory.json.mixins.model.TenantlessCanonicalPathMixin; import org.hawkular.inventory.json.mixins.model.TenantlessRelativePathMixin; import org.hawkular.inventory.paths.CanonicalPath; import org.hawkular.inventory.paths.DataRole; import org.hawkular.inventory.paths.RelativePath; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import com.fasterxml.jackson.core.JsonFactory; import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; /** * @author Lukas Krejci * @since 0.2.0 */ public class SerializationTest { private ObjectMapper mapper; @Before public void setup() { JsonFactory f = new JsonFactory(); mapper = new ObjectMapper(f); InventoryJacksonConfig.configure(mapper); } @Test public void testCanonicalPath() throws Exception { test(CanonicalPath.fromString("/t;t/e;e/r;r")); } @Test public void testTenantlessCanonicalPath() throws Exception { mapper.addMixIn(CanonicalPath.class, TenantlessCanonicalPathMixin.class); DetypedPathDeserializer.setCurrentCanonicalOrigin(CanonicalPath.fromString("/t;t")); DetypedPathDeserializer.setCurrentEntityType(Resource.class); test(CanonicalPath.fromString("/t;t/e;e/r;r")); } @Test public void testRelativePath() throws Exception { test(RelativePath.fromPartiallyUntypedString("../g", CanonicalPath.fromString("/t;t/e;e/r;r"), Metric.SEGMENT_TYPE)); } @Test public void testTenantlessRelativePath() throws Exception { mapper.addMixIn(RelativePath.class, TenantlessRelativePathMixin.class); DetypedPathDeserializer.setCurrentEntityType(Metric.class); DetypedPathDeserializer.setCurrentRelativePathOrigin(CanonicalPath.fromString("/t;t/e;e/r;r")); test(RelativePath.fromPartiallyUntypedString("../g", CanonicalPath.fromString("/t;t/e;e/r;r"), Metric.SEGMENT_TYPE) ); //the test above doesn't test for deserializing a de-typed path. RelativePath rp = deserialize("\"../g\"", RelativePath.class); Assert.assertEquals(RelativePath.fromPartiallyUntypedString("../g", CanonicalPath.fromString("/t;t/e;e/r;r"), Metric.SEGMENT_TYPE), rp); } @Test public void testTenant() throws Exception { Tenant t = new Tenant(CanonicalPath.fromString("/t;c"), "contentHash", new HashMap<String, Object>() {{ put("a", "b"); }}); test(t); } @Test public void testDetypedTenant() throws Exception { DetypedPathDeserializer.setCurrentCanonicalOrigin(null); Tenant t = new Tenant(CanonicalPath.fromString("/t;c"), "contentHash", new HashMap<String, Object>() {{ put("a", "b"); }}); String ser = "{\"path\":\"/c\",\"properties\":{\"a\":\"b\"}}"; testDetyped(t, ser); } @Test public void testEnvironment() throws Exception { Environment env = new Environment(CanonicalPath.fromString("/t;t/e;c"), "contentHash", new HashMap<String, Object>() {{ put("a", "b"); }}); test(env); } @Test public void testDetypedEnvironment() throws Exception { DetypedPathDeserializer.setCurrentCanonicalOrigin(CanonicalPath.fromString("/t;t")); Environment env = new Environment(CanonicalPath.fromString("/t;t/e;c"), "contentHash", new HashMap<String, Object>() {{ put("a", "b"); }}); testDetyped(env, "{\"path\":\"/e;c\",\"properties\":{\"a\":\"b\"}}"); testDetyped(env, "{\"path\":\"/t;t/c\",\"properties\":{\"a\":\"b\"}}"); testDetyped(env, "{\"path\":\"/c\",\"properties\":{\"a\":\"b\"}}"); } @Test public void testResourceType() throws Exception { ResourceType rt = new ResourceType(CanonicalPath.fromString("/t;t/rt;c"), "a", "b", "c", new HashMap<String, Object>() {{ put("a", "b"); }}); test(rt); } @Test public void testDetypedResourceType() throws Exception { DetypedPathDeserializer.setCurrentCanonicalOrigin(CanonicalPath.fromString("/t;t")); ResourceType rt = new ResourceType(CanonicalPath.fromString("/t;t/rt;c"), null, null, null, new HashMap<String, Object>() {{ put("a", "b"); }}); testDetyped(rt, "{\"path\":\"/t;t/rt;c\",\"properties\":{\"a\":\"b\"}}"); testDetyped(rt, "{\"path\":\"/t;t/c\",\"properties\":{\"a\":\"b\"}}"); testDetyped(rt, "{\"path\":\"/c\",\"properties\":{\"a\":\"b\"}}"); } @Test public void testMetricType() throws Exception { MetricType mt = new MetricType(CanonicalPath.fromString("/t;t/mt;c"), "a", null, null, MetricUnit.BYTES, COUNTER, new HashMap<String, Object>() {{ put("a", "b"); }}, 0L); test(mt); } @Test @Deprecated public void testMetricTypeIncludesDeprecatedFields() throws Exception { MetricType mt = new MetricType(CanonicalPath.fromString("/t;t/mt;c"), "a", null, null, MetricUnit.BYTES, COUNTER, new HashMap<String, Object>() {{ put("a", "b"); }}, 0L); String ser = serialize(mt); JsonNode json = mapper.readTree(ser); Assert.assertTrue(json.isObject()); Assert.assertTrue(json.has("type")); Assert.assertTrue(json.has("metricDataType")); Assert.assertEquals(COUNTER.name(), json.get("type").textValue()); Assert.assertEquals(COUNTER.getDisplayName(), json.get("metricDataType").textValue()); } @Test @Deprecated public void testMetricTypeBlueprintIncludesDeprecatedFields() throws Exception { MetricType.Blueprint mt = MetricType.Blueprint.builder(COUNTER).withId("c").withInterval(0L) .withUnit(MetricUnit.BYTES).withProperties(new HashMap<String, Object>() {{ put("a", "b"); }}).build(); String ser = serialize(mt); JsonNode json = mapper.readTree(ser); Assert.assertTrue(json.isObject()); Assert.assertTrue(json.has("type")); Assert.assertTrue(json.has("metricDataType")); Assert.assertEquals(COUNTER.name(), json.get("type").textValue()); Assert.assertEquals(COUNTER.getDisplayName(), json.get("metricDataType").textValue()); } @Test public void testDetypedMetricType() throws Exception { DetypedPathDeserializer.setCurrentCanonicalOrigin(CanonicalPath.fromString("/t;t")); MetricType mt = new MetricType(CanonicalPath.fromString("/t;t/mt;c"), null, null, null, MetricUnit.BYTES, GAUGE, new HashMap<String, Object>() {{ put("a", "b"); }}, 0L); testDetyped(mt, "{\"path\":\"/t;t/mt;c\",\"properties\":{\"a\":\"b\"},\"unit\":\"BYTES\", " + "\"collectionInterval\":\"0\"}"); testDetyped(mt, "{\"path\":\"/t;t/c\",\"properties\":{\"a\":\"b\"},\"unit\":\"BYTES\", " + "\"collectionInterval\":\"0\"}"); testDetyped(mt, "{\"path\":\"/c\",\"properties\":{\"a\":\"b\"},\"unit\":\"BYTES\"," + " \"collectionInterval\":\"0\"}"); } @Test public void testFeed() throws Exception { Feed f = new Feed(CanonicalPath.fromString("/t;t/f;c"), null, null, null, new HashMap<String, Object>() {{ put("a", "b"); }}); test(f); } @Test public void testDetypedFeed() throws Exception { DetypedPathDeserializer.setCurrentCanonicalOrigin(CanonicalPath.fromString("/t;t")); Feed f = new Feed(CanonicalPath.fromString("/t;t/f;c"), null, null, null, new HashMap<String, Object>() {{ put("a", "b"); }}); testDetyped(f, "{\"path\":\"/t;t/f;c\",\"properties\":{\"a\":\"b\"}}"); testDetyped(f, "{\"path\":\"/t;t/c\",\"properties\":{\"a\":\"b\"}}"); testDetyped(f, "{\"path\":\"/c\",\"properties\":{\"a\":\"b\"}}"); } @Test public void testResourceInEnvironment() throws Exception { Resource r = new Resource(CanonicalPath.fromString("/t;t/e;e/r;c"), null, null, null, new ResourceType( CanonicalPath.fromString("/t;t/rt;k"), null, null, null), new HashMap<String, Object>() {{ put("a", "b"); }}); test(r); } @Test public void testDetypedResourceInEvironment() throws Exception { DetypedPathDeserializer.setCurrentCanonicalOrigin(CanonicalPath.fromString("/t;t")); Resource r = new Resource(CanonicalPath.fromString("/t;t/e;e/r;c"), null, null, null, new ResourceType( CanonicalPath.fromString("/t;t/rt;k"), null, null, null), new HashMap<String, Object>() {{ put("a", "b"); }}); testDetyped(r, "{\"path\":\"/t;t/e;e/r;c\",\"properties\":{\"a\":\"b\"}}"); testDetyped(r, "{\"path\":\"/e/c\",\"properties\":{\"a\":\"b\"}}"); } @Test public void testMetricInEnvironment() throws Exception { Metric m = new Metric(CanonicalPath.fromString("/t;t/e;e/m;c"), null, null, null, new MetricType( CanonicalPath.fromString("/t;t/mt;k"), null, null, null), new HashMap<String, Object>() {{ put("a", "b"); }}); test(m); } @Test public void testDetypedMetricInEnvironment() throws Exception { DetypedPathDeserializer.setCurrentCanonicalOrigin(CanonicalPath.fromString("/t;t")); Metric m = new Metric(CanonicalPath.fromString("/t;t/e;e/m;c"), null, null, null, new MetricType( CanonicalPath.fromString("/t;t/mt;k"), null, null, null), new HashMap<String, Object>() {{ put("a", "b"); }}); testDetyped(m, "{\"path\":\"/t;t/e;e/m;c\",\"properties\":{\"a\":\"b\"}}"); testDetyped(m, "{\"path\":\"/e/c\",\"properties\":{\"a\":\"b\"}}"); } @Test public void testResourceInFeed() throws Exception { Resource r = new Resource(CanonicalPath.fromString("/t;t/f;f/r;c"), null, null, null, new ResourceType( CanonicalPath.fromString("/t;t/rt;k"), null, null, null), new HashMap<String, Object>() {{ put("a", "b"); }}); test(r); } @Test public void testDetypedResourceInFeed() throws Exception { DetypedPathDeserializer.setCurrentCanonicalOrigin(CanonicalPath.fromString("/t;t")); Resource r = new Resource(CanonicalPath.fromString("/t;t/f;f/r;c"), null, null, null, new ResourceType( CanonicalPath.fromString("/t;t/rt;k"), null, null, null), new HashMap<String, Object>() {{ put("a", "b"); }}); testDetyped(r, "{\"path\":\"/t;t/f;f/r;c\",\"properties\":{\"a\":\"b\"}}"); testDetyped(r, "{\"path\":\"/f/c\",\"properties\":{\"a\":\"b\"}}"); } @Test public void testMetricInFeed() throws Exception { Metric m = new Metric(CanonicalPath.fromString("/t;t/f;f/m;c"), null, null, null, new MetricType( CanonicalPath.fromString("/t;t/mt;k"), null, null, null), new HashMap<String, Object>() {{ put("a", "b"); }}); test(m); } @Test public void testDetypedMetricInFeed() throws Exception { DetypedPathDeserializer.setCurrentCanonicalOrigin(CanonicalPath.fromString("/t;t")); Metric m = new Metric(CanonicalPath.fromString("/t;t/f;f/m;c"), null, null, null, new MetricType( CanonicalPath.fromString("/t;t/mt;k"), null, null, null), new HashMap<String, Object>() {{ put("a", "b"); }}); testDetyped(m, "{\"path\":\"/t;t/f;f/m;c\",\"properties\":{\"a\":\"b\"}}"); testDetyped(m, "{\"path\":\"/f/c\",\"properties\":{\"a\":\"b\"}}"); } @Test public void testOperationType() throws Exception { OperationType ot = new OperationType(CanonicalPath.fromString("/t;t/rt;rt/ot;ot"), null, null, null); test(ot); } @Test public void testStructuredData() throws Exception { test(StructuredData.get().bool(true)); test(StructuredData.get().integral(42L)); test(StructuredData.get().floatingPoint(1.D)); test(StructuredData.get().string("answer")); test(StructuredData.get().list().addBool(true).build()); test(StructuredData.get().list().addList().addBool(true).addIntegral(2L).closeList().build()); test(StructuredData.get().list().addMap().putIntegral("answer", 42L).closeMap().build()); test(StructuredData.get().map().putBool("yes", true).build()); test(StructuredData.get().map().putList("answer-list").addIntegral(42L).closeList().build()); } @Test public void testDataEntity() throws Exception { test(new DataEntity(CanonicalPath.of().tenant("t").environment("e").resource("r").get(), DataRole.Resource.connectionConfiguration, StructuredData.get().list().addIntegral(1).addIntegral(2).build(), null, null, null)); } @Test public void testEntityBlueprint() throws Exception { Map<String, Object> properties = new HashMap<String, Object>() {{ put("key", "value"); }}; Map<String, Set<CanonicalPath>> incoming = new HashMap<String, Set<CanonicalPath>>() {{ put("duck", Collections.singleton(CanonicalPath.of().tenant("t").get())); }}; Map<String, Set<CanonicalPath>> outgoing = new HashMap<String, Set<CanonicalPath>>() {{ put("kachna", Collections.singleton(CanonicalPath.of().tenant("t").get())); }}; testBlueprint(DataEntity.Blueprint.builder().withRole(DataRole.ResourceType.configurationSchema).withName("nd") .withIncomingRelationships(incoming).withOutgoingRelationships(outgoing).withProperties(properties) .build(), (bl, dbl) -> { Assert.assertEquals(bl.getRole(), dbl.getRole()); Assert.assertEquals(bl.getValue(), dbl.getValue()); }); testBlueprint(Environment.Blueprint.builder().withId("e").withName("ne").withIncomingRelationships(incoming) .withOutgoingRelationships(outgoing).withProperties(properties).build(), null); testBlueprint(Feed.Blueprint.builder().withId("f").withName("nf").withIncomingRelationships(incoming) .withOutgoingRelationships(outgoing).withProperties(properties).build(), null); testBlueprint(Metric.Blueprint.builder().withId("m").withName("nm").withIncomingRelationships(incoming) .withOutgoingRelationships(outgoing).withProperties(properties).withMetricTypePath("kachna") .build(), null); testBlueprint(MetricType.Blueprint.builder(GAUGE).withId("mt").withName("nmt").withUnit(MetricUnit.NONE) .withIncomingRelationships(incoming).withOutgoingRelationships(outgoing).withProperties(properties) .build(), null); testBlueprint(OperationType.Blueprint.builder().withId("ot").withName("not") .withIncomingRelationships(incoming).withOutgoingRelationships(outgoing).withProperties(properties) .build(), null); testBlueprint(Resource.Blueprint.builder().withId("r").withName("nr") .withIncomingRelationships(incoming).withOutgoingRelationships(outgoing).withProperties(properties) .withResourceTypePath("kachna").build(), null); testBlueprint(ResourceType.Blueprint.builder().withId("rt").withName("nrt") .withIncomingRelationships(incoming).withOutgoingRelationships(outgoing).withProperties(properties) .build(), null); testBlueprint(Tenant.Blueprint.builder().withId("t").withName("nt").withIncomingRelationships(incoming) .withOutgoingRelationships(outgoing).withProperties(properties).build(), null); } @Test public void testEntityUpdate() throws Exception { Map<String, Object> properties = new HashMap<String, Object>() {{ put("key", "value"); }}; testUpdate(DataEntity.Update.builder().withName("nd").withProperties(properties) .withValue(null).build(), (bl, dbl) -> { Assert.assertEquals(bl.getValue(), dbl.getValue()); }); testUpdate(Environment.Update.builder().withName("ne").withProperties(properties).build(), null); testUpdate(Feed.Update.builder().withName("nf").withProperties(properties).build(), null); testUpdate(Metric.Update.builder().withName("nm").withProperties(properties).build(), null); testUpdate(MetricType.Update.builder().withName("nmt").withProperties(properties).build(), null); testUpdate(OperationType.Update.builder().withName("not").withProperties(properties).build(), null); testUpdate(Resource.Update.builder().withName("nr").withProperties(properties).build(), null); testUpdate(ResourceType.Update.builder().withName("nrt").withProperties(properties).build(), null); testUpdate(Tenant.Update.builder().withName("nt").withProperties(properties).build(), null); } @Test public void testQuery() throws Exception { Query query = Query.to(CanonicalPath.fromString("/t;fooTenant")); test(query); query = new Query.Builder().with(new FilterFragment(new With.Ids("a"))).build(); test(query); query = Query.path().with(type(Tenant.class)).with(id("t")).filter().with(by(contains)).with(type (Environment.class)).with(id("e")).with(by(contains)).with(type(Tenant.class)).with(id("t2")).get(); test(query); query = Query.path().with(type(Tenant.class)).with(id("t")).with(by(contains)).with(type(Environment.class)) .with(id("e")).with(by(contains)).with(type(Resource.class)).with(id("r")).get(); test(query); query = Query.path().with(RelationWith.Ids.pathTo(CanonicalPath.fromString("/t;fooTenant"))).get(); test(query); // RelationFilter query = Query.filter().with(RelationWith.name("__inPrediction"), RelationWith.ids("id1", "id2"), RelationWith.id("id4"), RelationWith.property("prop"), RelationWith.propertyValue("prop2", "value")) .with(RelationWith.sourceOfType(Tenant.class)) .with(RelationWith.targetsOfTypes(Metric.class, MetricType.class)) .with(RelationWith.SourceOrTargetOfType.pathTo(CanonicalPath.fromString("/t;tenant"))) .with(RelationWith.SourceOrTargetOfType.by(RelationWith.name("name")).get()).get(); test(query); // Related query = Query.filter().with(Related.asTargetWith(CanonicalPath.fromString("/t;tenant"), "relation"), Incorporated.by("/t;tenant"), Contained.in(CanonicalPath.fromString("/t;tenant")), Defined.by("/t;tenant")).get(); test(query); // With.DataAt query = Query.path().with(new With.DataAt(RelativePath.fromString("../e;e/../t;t"))).get(); test(query); // With.RelativePaths query = Query.to(CanonicalPath.fromString("/t;tenant")).extend().with( With.RelativePaths.pathTo(CanonicalPath.fromString("/t;tenant"))).get(); test(query); // With.DataOfTypes query = Query.path().with(With.type(Tenant.class)) .withExact(Query.filter().with(With.DataOfTypes.pathTo(CanonicalPath.fromString("/t;tenant"))).get()) .get(); test(query); // With.Types query = Query.filter().with(With.Types.by(With.Types.pathTo(CanonicalPath.fromString("/t;tenant"))).get()) .get(); test(query); // With.PropertyValues query = Query.path().with(RelationWith.PropertyValues.pathTo(CanonicalPath.fromString("/t;tenant"))).get(); test(query); // With.CanonicalPaths query = Query.filter().with(With.CanonicalPaths.pathTo(CanonicalPath.fromString("/t;tenant"))).get(); test(query); // With.Ids query = Query.filter().with(new With.Ids("id1", "id2")).get(); test(query); // With.DataValued query = Query.path().with(new With.DataValued(new Double(3))).get(); test(query); // Marker query = Query.filter().with(new Marker()).get(); test(query); // SwitchElementType query = Query.filter().with(SwitchElementType.incomingRelationships(), SwitchElementType.incomingRelationships(), SwitchElementType.sourceEntities(), SwitchElementType.targetEntities()) .get(); test(query); // RecurseFilter query = Query.filter().with(RecurseFilter.pathTo(CanonicalPath.fromString("/t;tenant"))).get(); test(query); // NoopFilter query = Query.filter().with(NoopFilter.INSTANCE).get(); test(query); } @Test public void testPager() throws Exception { Pager pager = Pager.builder().orderBy(Order.by("name", Order.Direction.ASCENDING)).withPageSize(2) .withStartPage(1).build(); String json = serialize(pager); Pager fromJson = deserialize(json, Pager.class); String expected = "{\"pageNumber\":1,\"pageSize\":2," + "\"order\":[{\"field\":\"name\",\"direction\":\"ASCENDING\"}]}"; assertThat(json, is(equalTo(expected))); assertThat(fromJson.getOrder().equals(pager.getOrder()), is(Boolean.TRUE)); } @Test public void testInventoryStructure() throws Exception { InventoryStructure<?> s = InventoryStructure.Offline.of(Feed.Blueprint.builder().withId("feed").build()) .addChild(ResourceType.Blueprint.builder().withId("resourceType").build()) .addChild(MetricType.Blueprint.builder(GAUGE).withId("metricType").withUnit(MetricUnit.NONE) .withInterval(0L).build()) .addChild(Metric.Blueprint.builder().withId("metrics").withMetricTypePath("metricType").withInterval (0L).build()) .startChild( Resource.Blueprint.builder().withId("resource").withResourceTypePath("resourceType").build()) .addChild(Resource.Blueprint.builder().withId("childResource").withResourceTypePath("../.resourceType") .build()) .end() .build(); test(s); } @Test public void testIdentityHashTree() throws Exception { InventoryStructure<?> s = InventoryStructure.Offline.of(Feed.Blueprint.builder().withId("feed").build()) .addChild(ResourceType.Blueprint.builder().withId("resourceType").build()) .addChild(MetricType.Blueprint.builder(GAUGE).withId("metricType").withUnit(MetricUnit.NONE) .withInterval(0L).build()) .addChild(Metric.Blueprint.builder().withId("metrics").withMetricTypePath("metricType").withInterval (0L).build()) .startChild( Resource.Blueprint.builder().withId("resource").withResourceTypePath("resourceType").build()) .addChild(Resource.Blueprint.builder().withId("childResource").withResourceTypePath("../.resourceType") .build()) .end() .build(); IdentityHash.Tree t = IdentityHash.treeOf(s); test(t); } @Test public void testSyncHashTree() throws Exception { InventoryStructure<?> s = InventoryStructure.Offline.of(Feed.Blueprint.builder().withId("feed").build()) .addChild(ResourceType.Blueprint.builder().withId("resourceType").build()) .addChild(MetricType.Blueprint.builder(GAUGE).withId("metricType").withUnit(MetricUnit.NONE) .withInterval(0L).build()) .addChild(Metric.Blueprint.builder().withId("metrics").withMetricTypePath("metricType").withInterval (0L).build()) .startChild( Resource.Blueprint.builder().withId("resource").withResourceTypePath("resourceType").build()) .addChild(Resource.Blueprint.builder().withId("childResource").withResourceTypePath("../.resourceType") .build()) .end() .build(); SyncHash.Tree t = SyncHash.treeOf(s, CanonicalPath.of().tenant("tnt").feed("feed").get()); test(t); } @Test public void testChange() throws Exception { Tenant t = new Tenant("tenant", CanonicalPath.of().tenant("tnt").get(), "contentHash"); Change<Tenant> c = new Change<>(Instant.now(), Action.created(), t); test(c); c = new Change<Tenant>(Instant.now(), Action.updated(), new Action.Update<>(t, Tenant.Update.builder().withName("kachna").build())); test(c); c = new Change<>(Instant.now(), Action.deleted(), t); test(c); } private void testDetyped(Entity<?, ?> orig, String serialized) throws Exception { DetypedPathDeserializer.setCurrentEntityType(orig.getClass()); mapper.addMixIn(CanonicalPath.class, TenantlessCanonicalPathMixin.class); Assert.assertEquals(orig, deserialize(serialized, orig.getClass())); } private <T extends Entity.Blueprint> void testBlueprint(T bl, BiConsumer<T, T> additionalTests) throws Exception { String ser = serialize(bl); @SuppressWarnings("unchecked") T dbl = (T) deserialize(ser, bl.getClass()); BeanInfo beanInfo = Introspector.getBeanInfo(bl.getClass()); for (PropertyDescriptor prop : beanInfo.getPropertyDescriptors()) { Object origValue = prop.getReadMethod().invoke(bl); Object newValue = prop.getReadMethod().invoke(dbl); Assert.assertTrue("Unexpected value of property '" + prop.getName() + "' on class " + bl.getClass(), isEqual(origValue, newValue)); } if (additionalTests != null) { additionalTests.accept(bl, dbl); } } private <T extends Entity.Update> void testUpdate(T bl, BiConsumer<T, T> additionalTests) throws Exception { String ser = serialize(bl); @SuppressWarnings("unchecked") T dbl = (T) deserialize(ser, bl.getClass()); Assert.assertEquals(bl.getName(), dbl.getName()); Assert.assertEquals(bl.getProperties(), dbl.getProperties()); if (additionalTests != null) { additionalTests.accept(bl, dbl); } } private void test(Object o) throws Exception { Class<?> cls = o.getClass(); Object o2 = deserialize(serialize(o), cls); Assert.assertEquals(o, o2); BeanInfo beanInfo = Introspector.getBeanInfo(cls); for (PropertyDescriptor prop : beanInfo.getPropertyDescriptors()) { Object origValue = prop.getReadMethod().invoke(o); Object newValue = prop.getReadMethod().invoke(o2); Assert.assertTrue("Unexpected value of property '" + prop.getName() + "' on class " + cls, isEqual(origValue, newValue)); } } private boolean isEqual(Object a, Object b) { if (a == null) { return b == null; } else if (a.getClass().isArray()) { if (b == null || !b.getClass().isArray()) { return false; } int aLen = Array.getLength(a); int bLen = Array.getLength(b); if (aLen != bLen) { return false; } for (int i = 0; i < aLen; ++i) { Object aVal = Array.get(a, i); Object bVal = Array.get(b, i); if (!isEqual(aVal, bVal)) { return false; } } return true; } else if (a instanceof Collection) { // do a piecewise comparison ourselves. Mainly because Collections.UnmodifiableCollection doesn't implement // this as expected. See {@link Collections#unmodifiableCollection(Collection)} Collection<?> as = (Collection<?>) a; Collection<?> bs = (Collection<?>) b; if (as.size() != bs.size()) { return false; } Iterator<?> ai = as.iterator(); Iterator<?> bi = bs.iterator(); while (ai.hasNext()) { Object ao = ai.next(); Object bo = bi.next(); if (!Objects.equals(ao, bo)) { return false; } } return true; } else { return a.equals(b); } } private String serialize(Object object) throws IOException { StringWriter out = new StringWriter(); JsonGenerator gen = mapper.getFactory().createGenerator(out); gen.writeObject(object); gen.close(); out.close(); return out.toString(); } private <T> T deserialize(String json, Class<T> type) throws Exception { JsonParser parser = mapper.getFactory().createParser(json); return parser.readValueAs(type); } }