/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you 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.apache.brooklyn.core.mgmt.persist; import static org.testng.Assert.assertEquals; import java.net.InetAddress; import java.util.Arrays; import java.util.Collection; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.NoSuchElementException; import java.util.Set; import org.apache.brooklyn.api.catalog.CatalogItem; import org.apache.brooklyn.api.entity.Entity; import org.apache.brooklyn.api.entity.EntitySpec; import org.apache.brooklyn.api.location.Location; import org.apache.brooklyn.api.location.LocationSpec; import org.apache.brooklyn.api.mgmt.ManagementContext; import org.apache.brooklyn.api.mgmt.rebind.mementos.BrooklynMementoPersister.LookupContext; import org.apache.brooklyn.api.objs.BrooklynObject; import org.apache.brooklyn.api.objs.BrooklynObjectType; import org.apache.brooklyn.api.policy.Policy; import org.apache.brooklyn.api.sensor.Enricher; import org.apache.brooklyn.api.sensor.Feed; import org.apache.brooklyn.api.typereg.RegisteredType; import org.apache.brooklyn.core.catalog.internal.CatalogItemBuilder; import org.apache.brooklyn.core.catalog.internal.CatalogItemDtoAbstract; import org.apache.brooklyn.core.entity.Entities; import org.apache.brooklyn.core.location.SimulatedLocation; import org.apache.brooklyn.core.mgmt.osgi.OsgiVersionMoreEntityTest; import org.apache.brooklyn.core.test.entity.LocalManagementContextForTests; import org.apache.brooklyn.core.test.entity.TestApplication; import org.apache.brooklyn.core.test.entity.TestEntity; import org.apache.brooklyn.entity.group.DynamicCluster; import org.apache.brooklyn.test.support.TestResourceUnavailableException; import org.apache.brooklyn.util.collections.MutableList; import org.apache.brooklyn.util.collections.MutableMap; import org.apache.brooklyn.util.collections.MutableSet; import org.apache.brooklyn.util.net.Networking; import org.apache.brooklyn.util.net.UserAndHostAndPort; import org.apache.brooklyn.util.osgi.OsgiTestResources; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; import com.google.api.client.repackaged.com.google.common.base.Joiner; import com.google.common.base.Objects; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Maps; public class XmlMementoSerializerTest { private static final Logger LOG = LoggerFactory.getLogger(XmlMementoSerializerTest.class); private XmlMementoSerializer<Object> serializer; @BeforeMethod(alwaysRun=true) public void setUp() throws Exception { serializer = new XmlMementoSerializer<Object>(XmlMementoSerializerTest.class.getClassLoader()); } @Test public void testRenamedClass() throws Exception { serializer = new XmlMementoSerializer<Object>(XmlMementoSerializerTest.class.getClassLoader(), ImmutableMap.of("old.package.name.UserAndHostAndPort", UserAndHostAndPort.class.getName())); String serializedForm = Joiner.on("\n").join( "<org.apache.brooklyn.util.net.UserAndHostAndPort>", "<user>myuser</user>", "<hostAndPort>", "<host>myhost</host>", "<port>1234</port>", "<hasBracketlessColons>false</hasBracketlessColons>", "</hostAndPort>", "</org.apache.brooklyn.util.net.UserAndHostAndPort>"); UserAndHostAndPort obj = UserAndHostAndPort.fromParts("myuser", "myhost", 1234); runRenamed(serializedForm, obj, ImmutableMap.<String, String>of( UserAndHostAndPort.class.getName(), "old.package.name.UserAndHostAndPort")); } @Test public void testRenamedStaticInner() throws Exception { serializer = new XmlMementoSerializer<Object>(XmlMementoSerializerTest.class.getClassLoader(), ImmutableMap.of("old.package.name.XmlMementoSerializerTest", XmlMementoSerializerTest.class.getName())); String serializedForm = Joiner.on("\n").join( "<org.apache.brooklyn.core.mgmt.persist.XmlMementoSerializerTest_-MyStaticInner>", "<myStaticInnerField>myStaticInnerVal</myStaticInnerField>", "</org.apache.brooklyn.core.mgmt.persist.XmlMementoSerializerTest_-MyStaticInner>"); MyStaticInner obj = new MyStaticInner("myStaticInnerVal"); runRenamed(serializedForm, obj, ImmutableMap.<String, String>of( XmlMementoSerializerTest.class.getName(), "old.package.name.XmlMementoSerializerTest")); } @Test public void testRenamedNonStaticInner() throws Exception { serializer = new XmlMementoSerializer<Object>(XmlMementoSerializerTest.class.getClassLoader(), ImmutableMap.of("old.package.name.XmlMementoSerializerTest", XmlMementoSerializerTest.class.getName())); String serializedForm = Joiner.on("\n").join( "<org.apache.brooklyn.core.mgmt.persist.XmlMementoSerializerTest_-MyStaticInner_-MyNonStaticInner>", "<myNonStaticInnerField>myNonStaticInnerVal</myNonStaticInnerField>", "<this_-1>", "<myStaticInnerField>myStaticInnerVal</myStaticInnerField>", "</this_-1>", "</org.apache.brooklyn.core.mgmt.persist.XmlMementoSerializerTest_-MyStaticInner_-MyNonStaticInner>"); MyStaticInner outer = new MyStaticInner("myStaticInnerVal"); MyStaticInner.MyNonStaticInner obj = outer.new MyNonStaticInner("myNonStaticInnerVal"); runRenamed(serializedForm, obj, ImmutableMap.<String, String>of( XmlMementoSerializerTest.class.getName(), "old.package.name.XmlMementoSerializerTest")); } @Test public void testRenamedAnonymousInner() throws Exception { serializer = new XmlMementoSerializer<Object>(XmlMementoSerializerTest.class.getClassLoader(), ImmutableMap.of("old.package.name.XmlMementoSerializerTest", XmlMementoSerializerTest.class.getName())); String serializedForm = Joiner.on("\n").join( "<org.apache.brooklyn.core.mgmt.persist.XmlMementoSerializerTest_-MySuper_-1>", "<mySuperField>mySuperVal</mySuperField>", "<myAnonymousInnerField>mySubVal</myAnonymousInnerField>", "</org.apache.brooklyn.core.mgmt.persist.XmlMementoSerializerTest_-MySuper_-1>"); MySuper obj = MySuper.newAnonymousInner("mySuperVal", "mySubVal"); runRenamed(serializedForm, obj, ImmutableMap.<String, String>of( XmlMementoSerializerTest.class.getName(), "old.package.name.XmlMementoSerializerTest")); } protected void runRenamed(String serializedForm, Object obj, Map<String, String> transforms) throws Exception { assertSerializeAndDeserialize(obj); assertEquals(serializer.fromString(serializedForm), obj, "serializedForm="+serializedForm); String transformedForm = serializedForm; for (Map.Entry<String, String> entry : transforms.entrySet()) { transformedForm = transformedForm.replaceAll(entry.getKey(), entry.getValue()); } assertEquals(serializer.fromString(transformedForm), obj, "serializedForm="+transformedForm); } @Test public void testInetAddress() throws Exception { InetAddress obj = Networking.getInetAddressWithFixedName("1.2.3.4"); assertSerializeAndDeserialize(obj); } @Test public void testMutableSet() throws Exception { Set<?> obj = MutableSet.of("123"); assertSerializeAndDeserialize(obj); } @Test public void testLinkedHashSet() throws Exception { Set<String> obj = new LinkedHashSet<String>(); obj.add("123"); assertSerializeAndDeserialize(obj); } @Test public void testImmutableSet() throws Exception { Set<String> obj = ImmutableSet.of("123"); assertSerializeAndDeserialize(obj); } @Test public void testMutableList() throws Exception { List<?> obj = MutableList.of("123"); assertSerializeAndDeserialize(obj); } @Test public void testLinkedList() throws Exception { List<String> obj = new LinkedList<String>(); obj.add("123"); assertSerializeAndDeserialize(obj); } @Test public void testArraysAsList() throws Exception { // For some reason Arrays.asList used in the catalog's libraries can't be deserialized correctly, // but here works perfectly - the generated catalog xml contains // <libraries class="list"> // <a ...> // <bundle....> // which is deserialized as an ArrayList with a single member array of bundles. // The cause is the class="list" type which should be java.util.Arrays$ArrayList instead. Collection<String> obj = Arrays.asList("a", "b"); assertSerializeAndDeserialize(obj); } @Test public void testImmutableList() throws Exception { List<String> obj = ImmutableList.of("123"); assertSerializeAndDeserialize(obj); } @Test public void testMutableMap() throws Exception { Map<?,?> obj = MutableMap.of("mykey", "myval"); assertSerializeAndDeserialize(obj); } @Test public void testLinkedHashMap() throws Exception { Map<String,String> obj = new LinkedHashMap<String,String>(); obj.put("mykey", "myval"); assertSerializeAndDeserialize(obj); } @Test public void testImmutableMap() throws Exception { Map<?,?> obj = ImmutableMap.of("mykey", "myval"); assertSerializeAndDeserialize(obj); } @Test public void testClass() throws Exception { Class<?> t = XmlMementoSerializer.class; assertSerializeAndDeserialize(t); } @Test public void testEntity() throws Exception { final TestApplication app = TestApplication.Factory.newManagedInstanceForTests(); ManagementContext managementContext = app.getManagementContext(); try { serializer.setLookupContext(new LookupContextImpl(managementContext, ImmutableList.of(app), ImmutableList.<Location>of(), ImmutableList.<Policy>of(), ImmutableList.<Enricher>of(), ImmutableList.<Feed>of(), ImmutableList.<CatalogItem<?, ?>>of(), true)); assertSerializeAndDeserialize(app); } finally { Entities.destroyAll(managementContext); } } @Test public void testLocation() throws Exception { final TestApplication app = TestApplication.Factory.newManagedInstanceForTests(); ManagementContext managementContext = app.getManagementContext(); try { final Location loc = managementContext.getLocationManager().createLocation(LocationSpec.create(SimulatedLocation.class)); serializer.setLookupContext(new LookupContextImpl(managementContext, ImmutableList.<Entity>of(), ImmutableList.of(loc), ImmutableList.<Policy>of(), ImmutableList.<Enricher>of(), ImmutableList.<Feed>of(), ImmutableList.<CatalogItem<?, ?>>of(), true)); assertSerializeAndDeserialize(loc); } finally { Entities.destroyAll(managementContext); } } @Test public void testCatalogItem() throws Exception { final TestApplication app = TestApplication.Factory.newManagedInstanceForTests(); ManagementContext managementContext = app.getManagementContext(); try { CatalogItem<?, ?> catalogItem = CatalogItemBuilder.newEntity("symbolicName", "0.0.1") .displayName("test catalog item") .description("description") .plan("yaml plan") .iconUrl("iconUrl") .libraries(CatalogItemDtoAbstract.parseLibraries(ImmutableList.of("library-url"))) .build(); serializer.setLookupContext(new LookupContextImpl(managementContext, ImmutableList.<Entity>of(), ImmutableList.<Location>of(), ImmutableList.<Policy>of(), ImmutableList.<Enricher>of(), ImmutableList.<Feed>of(), ImmutableList.of(catalogItem), true)); assertSerializeAndDeserialize(catalogItem); } finally { Entities.destroyAll(managementContext); } } @Test public void testEntitySpec() throws Exception { EntitySpec<?> obj = EntitySpec.create(TestEntity.class); assertSerializeAndDeserialize(obj); } @Test public void testEntitySpecFromOsgi() throws Exception { TestResourceUnavailableException.throwIfResourceUnavailable(getClass(), OsgiTestResources.BROOKLYN_TEST_MORE_ENTITIES_V1_PATH); ManagementContext mgmt = LocalManagementContextForTests.builder(true).disableOsgi(false).build(); try { RegisteredType ci = OsgiVersionMoreEntityTest.addMoreEntityV1(mgmt, "1.0"); EntitySpec<DynamicCluster> spec = EntitySpec.create(DynamicCluster.class) .configure(DynamicCluster.INITIAL_SIZE, 1) .configure(DynamicCluster.MEMBER_SPEC, mgmt.getTypeRegistry().createSpec(ci, null, EntitySpec.class)); serializer.setLookupContext(new LookupContextImpl(mgmt, ImmutableList.<Entity>of(), ImmutableList.<Location>of(), ImmutableList.<Policy>of(), ImmutableList.<Enricher>of(), ImmutableList.<Feed>of(), ImmutableList.<CatalogItem<?,?>>of(), true)); assertSerializeAndDeserialize(spec); } finally { Entities.destroyAllCatching(mgmt); } } @Test public void testImmutableCollectionsWithDanglingEntityRef() throws Exception { // If there's a dangling entity in an ImmutableList etc, then discard it entirely. // If we try to insert null then it fails, breaking the deserialization of that entire file. final TestApplication app = TestApplication.Factory.newManagedInstanceForTests(); final TestEntity entity = app.createAndManageChild(EntitySpec.create(TestEntity.class)); ManagementContext managementContext = app.getManagementContext(); try { serializer.setLookupContext(new LookupContextImpl(managementContext, ImmutableList.of(app), ImmutableList.<Location>of(), ImmutableList.<Policy>of(), ImmutableList.<Enricher>of(), ImmutableList.<Feed>of(), ImmutableList.<CatalogItem<?, ?>>of(), false)); List<?> resultList = serializeAndDeserialize(ImmutableList.of(app, entity)); assertEquals(resultList, ImmutableList.of(app)); Set<?> resultSet = serializeAndDeserialize(ImmutableSet.of(app, entity)); assertEquals(resultSet, ImmutableSet.of(app)); Map<?, ?> resultMap = serializeAndDeserialize(ImmutableMap.of(app, "appval", "appkey", app, entity, "entityval", "entityKey", entity)); assertEquals(resultMap, ImmutableMap.of(app, "appval", "appkey", app)); } finally { Entities.destroyAll(managementContext); } } @Test public void testFieldReffingEntity() throws Exception { final TestApplication app = TestApplication.Factory.newManagedInstanceForTests(); ReffingEntity reffer = new ReffingEntity(app); ManagementContext managementContext = app.getManagementContext(); try { serializer.setLookupContext(new LookupContextImpl(managementContext, ImmutableList.of(app), ImmutableList.<Location>of(), ImmutableList.<Policy>of(), ImmutableList.<Enricher>of(), ImmutableList.<Feed>of(), ImmutableList.<CatalogItem<?, ?>>of(), true)); ReffingEntity reffer2 = assertSerializeAndDeserialize(reffer); assertEquals(reffer2.entity, app); } finally { Entities.destroyAll(managementContext); } } @Test public void testUntypedFieldReffingEntity() throws Exception { final TestApplication app = TestApplication.Factory.newManagedInstanceForTests(); ReffingEntity reffer = new ReffingEntity((Object)app); ManagementContext managementContext = app.getManagementContext(); try { serializer.setLookupContext(new LookupContextImpl(managementContext, ImmutableList.of(app), ImmutableList.<Location>of(), ImmutableList.<Policy>of(), ImmutableList.<Enricher>of(), ImmutableList.<Feed>of(), ImmutableList.<CatalogItem<?, ?>>of(), true)); ReffingEntity reffer2 = assertSerializeAndDeserialize(reffer); assertEquals(reffer2.obj, app); } finally { Entities.destroyAll(managementContext); } } public static class ReffingEntity { public Entity entity; public Object obj; public ReffingEntity(Entity entity) { this.entity = entity; } public ReffingEntity(Object obj) { this.obj = obj; } @Override public boolean equals(Object o) { return (o instanceof ReffingEntity) && Objects.equal(entity, ((ReffingEntity)o).entity) && Objects.equal(obj, ((ReffingEntity)o).obj); } @Override public int hashCode() { return Objects.hashCode(entity, obj); } } @SuppressWarnings("unchecked") private <T> T assertSerializeAndDeserialize(T obj) throws Exception { String serializedForm = serializer.toString(obj); LOG.info("serializedForm=" + serializedForm); Object deserialized = serializer.fromString(serializedForm); assertEquals(deserialized, obj, "serializedForm="+serializedForm); return (T) deserialized; } @SuppressWarnings("unchecked") private <T> T serializeAndDeserialize(T obj) throws Exception { String serializedForm = serializer.toString(obj); LOG.info("serializedForm=" + serializedForm); return (T) serializer.fromString(serializedForm); } static class LookupContextImpl implements LookupContext { private final ManagementContext mgmt; private final Map<String, Entity> entities; private final Map<String, Location> locations; private final Map<String, Policy> policies; private final Map<String, Enricher> enrichers; private final Map<String, Feed> feeds; private final Map<String, CatalogItem<?, ?>> catalogItems; private final boolean failOnDangling; LookupContextImpl(ManagementContext mgmt, Iterable<? extends Entity> entities, Iterable<? extends Location> locations, Iterable<? extends Policy> policies, Iterable<? extends Enricher> enrichers, Iterable<? extends Feed> feeds, Iterable<? extends CatalogItem<?, ?>> catalogItems, boolean failOnDangling) { this.mgmt = mgmt; this.entities = Maps.newLinkedHashMap(); this.locations = Maps.newLinkedHashMap(); this.policies = Maps.newLinkedHashMap(); this.enrichers = Maps.newLinkedHashMap(); this.feeds = Maps.newLinkedHashMap(); this.catalogItems = Maps.newLinkedHashMap(); for (Entity entity : entities) this.entities.put(entity.getId(), entity); for (Location location : locations) this.locations.put(location.getId(), location); for (Policy policy : policies) this.policies.put(policy.getId(), policy); for (Enricher enricher : enrichers) this.enrichers.put(enricher.getId(), enricher); for (Feed feed : feeds) this.feeds.put(feed.getId(), feed); for (CatalogItem<?, ?> catalogItem : catalogItems) this.catalogItems.put(catalogItem.getId(), catalogItem); this.failOnDangling = failOnDangling; } LookupContextImpl(ManagementContext mgmt, Map<String,? extends Entity> entities, Map<String,? extends Location> locations, Map<String,? extends Policy> policies, Map<String,? extends Enricher> enrichers, Map<String,? extends Feed> feeds, Map<String, ? extends CatalogItem<?, ?>> catalogItems, boolean failOnDangling) { this.mgmt = mgmt; this.entities = ImmutableMap.copyOf(entities); this.locations = ImmutableMap.copyOf(locations); this.policies = ImmutableMap.copyOf(policies); this.enrichers = ImmutableMap.copyOf(enrichers); this.feeds = ImmutableMap.copyOf(feeds); this.catalogItems = ImmutableMap.copyOf(catalogItems); this.failOnDangling = failOnDangling; } @Override public ManagementContext lookupManagementContext() { return mgmt; } @Override public Entity lookupEntity(String id) { if (entities.containsKey(id)) { return entities.get(id); } if (failOnDangling) { throw new NoSuchElementException("no entity with id "+id+"; contenders are "+entities.keySet()); } return null; } @Override public Location lookupLocation(String id) { if (locations.containsKey(id)) { return locations.get(id); } if (failOnDangling) { throw new NoSuchElementException("no location with id "+id+"; contenders are "+locations.keySet()); } return null; } @Override public Policy lookupPolicy(String id) { if (policies.containsKey(id)) { return policies.get(id); } if (failOnDangling) { throw new NoSuchElementException("no policy with id "+id+"; contenders are "+policies.keySet()); } return null; } @Override public Enricher lookupEnricher(String id) { if (enrichers.containsKey(id)) { return enrichers.get(id); } if (failOnDangling) { throw new NoSuchElementException("no enricher with id "+id+"; contenders are "+enrichers.keySet()); } return null; } @Override public Feed lookupFeed(String id) { if (feeds.containsKey(id)) { return feeds.get(id); } if (failOnDangling) { throw new NoSuchElementException("no feed with id "+id+"; contenders are "+feeds.keySet()); } return null; } @Override public CatalogItem<?, ?> lookupCatalogItem(String id) { if (catalogItems.containsKey(id)) { return catalogItems.get(id); } if (failOnDangling) { throw new NoSuchElementException("no catalog item with id "+id+"; contenders are "+catalogItems.keySet()); } return null; } @Override public BrooklynObject lookup(BrooklynObjectType type, String id) { if (type==null) { BrooklynObject result = peek(null, id); if (result==null) { if (failOnDangling) { throw new NoSuchElementException("no brooklyn object with id "+id+"; type not specified"); } } type = BrooklynObjectType.of(result); } switch (type) { case CATALOG_ITEM: return lookupCatalogItem(id); case ENRICHER: return lookupEnricher(id); case ENTITY: return lookupEntity(id); case FEED: return lookupFeed(id); case LOCATION: return lookupLocation(id); case POLICY: return lookupPolicy(id); case UNKNOWN: return null; } throw new IllegalStateException("Unexpected type "+type+" / id "+id); } @Override public BrooklynObject peek(BrooklynObjectType type, String id) { if (type==null) { for (BrooklynObjectType typeX: BrooklynObjectType.values()) { BrooklynObject result = peek(typeX, id); if (result!=null) return result; } return null; } switch (type) { case CATALOG_ITEM: return catalogItems.get(id); case ENRICHER: return enrichers.get(id); case ENTITY: return entities.get(id); case FEED: return feeds.get(id); case LOCATION: return locations.get(id); case POLICY: return policies.get(id); case UNKNOWN: return null; } throw new IllegalStateException("Unexpected type "+type+" / id "+id); } }; public static class MySuper { public String mySuperField; public static MySuper newAnonymousInner(final String superVal, final String subVal) { MySuper result = new MySuper() { public String myAnonymousInnerField = subVal; @Override public boolean equals(Object obj) { return (obj != null) && (obj.getClass() == getClass()) && super.equals(obj); } @Override public int hashCode() { return Objects.hashCode(super.hashCode(), myAnonymousInnerField); } }; result.mySuperField = superVal; return result; } @Override public boolean equals(Object obj) { return (obj instanceof MySuper) && mySuperField.equals(((MySuper)obj).mySuperField); } @Override public int hashCode() { return Objects.hashCode(mySuperField); } } public static class MyStaticInner { public class MyNonStaticInner { public String myNonStaticInnerField; public MyNonStaticInner(String val) { this.myNonStaticInnerField = val; } @Override public boolean equals(Object obj) { return (obj instanceof MyNonStaticInner) && myNonStaticInnerField.equals(((MyNonStaticInner)obj).myNonStaticInnerField); } @Override public int hashCode() { return Objects.hashCode(myNonStaticInnerField); } } public String myStaticInnerField; public MyStaticInner(String val) { this.myStaticInnerField = val; } @Override public boolean equals(Object obj) { return (obj instanceof MyStaticInner) && myStaticInnerField.equals(((MyStaticInner)obj).myStaticInnerField); } @Override public int hashCode() { return Objects.hashCode(myStaticInnerField); } } }