Repository: brooklyn-server Updated Branches: refs/heads/master 92f1cb3ef -> 4f75b2185
Move jackson serialization configuration to core Needs to be used by both rest-resource & software-base. Project: http://git-wip-us.apache.org/repos/asf/brooklyn-server/repo Commit: http://git-wip-us.apache.org/repos/asf/brooklyn-server/commit/8aa2b75b Tree: http://git-wip-us.apache.org/repos/asf/brooklyn-server/tree/8aa2b75b Diff: http://git-wip-us.apache.org/repos/asf/brooklyn-server/diff/8aa2b75b Branch: refs/heads/master Commit: 8aa2b75b6f1ece763d1a3bf668950fa0861f4b69 Parents: 05283c8 Author: Svetoslav Neykov <svetoslav.ney...@cloudsoftcorp.com> Authored: Fri Jun 17 18:55:45 2016 +0300 Committer: Svetoslav Neykov <svetoslav.ney...@cloudsoftcorp.com> Committed: Mon Jun 20 15:50:22 2016 +0300 ---------------------------------------------------------------------- core/pom.xml | 16 ++ .../util/core/json/BidiSerialization.java | 173 +++++++++++++++++++ .../core/json/BrooklynObjectsJsonMapper.java | 43 +++++ .../json/ConfigurableSerializerProvider.java | 91 ++++++++++ .../ErrorAndToStringUnknownTypeSerializer.java | 123 +++++++++++++ .../util/core/json/MultimapSerializer.java | 64 +++++++ ...StrictPreferringFieldsVisibilityChecker.java | 108 ++++++++++++ .../resources/AbstractBrooklynRestResource.java | 1 + .../brooklyn/rest/util/WebResourceUtils.java | 1 - .../rest/util/json/BidiSerialization.java | 173 ------------------- .../util/json/BrooklynJacksonJsonProvider.java | 21 +-- .../json/ConfigurableSerializerProvider.java | 91 ---------- .../ErrorAndToStringUnknownTypeSerializer.java | 123 ------------- .../rest/util/json/MultimapSerializer.java | 64 ------- ...StrictPreferringFieldsVisibilityChecker.java | 108 ------------ .../json/BrooklynJacksonSerializerTest.java | 1 + 16 files changed, 622 insertions(+), 579 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/8aa2b75b/core/pom.xml ---------------------------------------------------------------------- diff --git a/core/pom.xml b/core/pom.xml index ef3e850..c2be055 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -150,6 +150,22 @@ <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> </dependency> + <dependency> + <groupId>com.fasterxml.jackson.core</groupId> + <artifactId>jackson-annotations</artifactId> + </dependency> + <dependency> + <groupId>com.fasterxml.jackson.core</groupId> + <artifactId>jackson-core</artifactId> + </dependency> + <dependency> + <groupId>com.fasterxml.jackson.core</groupId> + <artifactId>jackson-databind</artifactId> + </dependency> + <dependency> + <groupId>com.fasterxml.jackson.jaxrs</groupId> + <artifactId>jackson-jaxrs-json-provider</artifactId> + </dependency> <dependency> <groupId>org.testng</groupId> http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/8aa2b75b/core/src/main/java/org/apache/brooklyn/util/core/json/BidiSerialization.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/brooklyn/util/core/json/BidiSerialization.java b/core/src/main/java/org/apache/brooklyn/util/core/json/BidiSerialization.java new file mode 100644 index 0000000..08f3bc1 --- /dev/null +++ b/core/src/main/java/org/apache/brooklyn/util/core/json/BidiSerialization.java @@ -0,0 +1,173 @@ +/* + * 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.util.core.json; + +import java.io.IOException; +import java.util.Map; + +import org.apache.brooklyn.api.entity.Entity; +import org.apache.brooklyn.api.location.Location; +import org.apache.brooklyn.api.mgmt.ManagementContext; +import org.apache.brooklyn.api.objs.BrooklynObject; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.module.SimpleModule; + +public class BidiSerialization { + + protected final static ThreadLocal<Boolean> STRICT_SERIALIZATION = new ThreadLocal<Boolean>(); + + /** + * Sets strict serialization on, or off (the default), for the current thread. + * Recommended to be used in a <code>try { ... } finally { ... }</code> block + * with {@link #clearStrictSerialization()} at the end. + * <p> + * With strict serialization, classes must have public fields or annotated fields, else they will not be serialized. + */ + public static void setStrictSerialization(Boolean value) { + STRICT_SERIALIZATION.set(value); + } + + public static void clearStrictSerialization() { + STRICT_SERIALIZATION.remove(); + } + + public static boolean isStrictSerialization() { + Boolean result = STRICT_SERIALIZATION.get(); + if (result!=null) return result; + return false; + } + + + public abstract static class AbstractWithManagementContextSerialization<T> { + + protected class Serializer extends JsonSerializer<T> { + @Override + public void serialize(T value, JsonGenerator jgen, SerializerProvider provider) throws IOException { + AbstractWithManagementContextSerialization.this.serialize(value, jgen, provider); + } + } + + protected class Deserializer extends JsonDeserializer<T> { + @Override + public T deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException { + return AbstractWithManagementContextSerialization.this.deserialize(jp, ctxt); + } + } + + protected final Serializer serializer = new Serializer(); + protected final Deserializer deserializer = new Deserializer(); + protected final Class<T> type; + protected final ManagementContext mgmt; + + public AbstractWithManagementContextSerialization(Class<T> type, ManagementContext mgmt) { + this.type = type; + this.mgmt = mgmt; + } + + public JsonSerializer<T> getSerializer() { + return serializer; + } + + public JsonDeserializer<T> getDeserializer() { + return deserializer; + } + + public void serialize(T value, JsonGenerator jgen, SerializerProvider provider) throws IOException { + jgen.writeStartObject(); + writeBody(value, jgen, provider); + jgen.writeEndObject(); + } + + protected void writeBody(T value, JsonGenerator jgen, SerializerProvider provider) throws IOException { + jgen.writeStringField("type", value.getClass().getCanonicalName()); + customWriteBody(value, jgen, provider); + } + + public abstract void customWriteBody(T value, JsonGenerator jgen, SerializerProvider provider) throws IOException; + + public T deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException { + @SuppressWarnings("unchecked") + Map<Object,Object> values = jp.readValueAs(Map.class); + String type = (String) values.get("type"); + return customReadBody(type, values, jp, ctxt); + } + + protected abstract T customReadBody(String type, Map<Object, Object> values, JsonParser jp, DeserializationContext ctxt) throws IOException; + + public void install(SimpleModule module) { + module.addSerializer(type, serializer); + module.addDeserializer(type, deserializer); + } + } + + public static class ManagementContextSerialization extends AbstractWithManagementContextSerialization<ManagementContext> { + public ManagementContextSerialization(ManagementContext mgmt) { super(ManagementContext.class, mgmt); } + @Override + public void customWriteBody(ManagementContext value, JsonGenerator jgen, SerializerProvider provider) throws IOException {} + @Override + protected ManagementContext customReadBody(String type, Map<Object, Object> values, JsonParser jp, DeserializationContext ctxt) throws IOException { + return mgmt; + } + } + + public abstract static class AbstractBrooklynObjectSerialization<T extends BrooklynObject> extends AbstractWithManagementContextSerialization<T> { + public AbstractBrooklynObjectSerialization(Class<T> type, ManagementContext mgmt) { + super(type, mgmt); + } + @Override + protected void writeBody(T value, JsonGenerator jgen, SerializerProvider provider) throws IOException { + jgen.writeStringField("type", type.getCanonicalName()); + customWriteBody(value, jgen, provider); + } + @Override + public void customWriteBody(T value, JsonGenerator jgen, SerializerProvider provider) throws IOException { + jgen.writeStringField("id", value.getId()); + } + @Override + protected T customReadBody(String type, Map<Object, Object> values, JsonParser jp, DeserializationContext ctxt) throws IOException { + return getInstanceFromId((String) values.get("id")); + } + protected abstract T getInstanceFromId(String id); + } + + public static class EntitySerialization extends AbstractBrooklynObjectSerialization<Entity> { + public EntitySerialization(ManagementContext mgmt) { super(Entity.class, mgmt); } + @Override protected Entity getInstanceFromId(String id) { return mgmt.getEntityManager().getEntity(id); } + } + public static class LocationSerialization extends AbstractBrooklynObjectSerialization<Location> { + public LocationSerialization(ManagementContext mgmt) { super(Location.class, mgmt); } + @Override protected Location getInstanceFromId(String id) { return mgmt.getLocationManager().getLocation(id); } + } + // TODO how to look up policies and enrichers? (not essential...) +// public static class PolicySerialization extends AbstractBrooklynObjectSerialization<Policy> { +// public EntitySerialization(ManagementContext mgmt) { super(Policy.class, mgmt); } +// @Override protected Policy getKind(String id) { return mgmt.getEntityManager().getEntity(id); } +// } +// public static class EnricherSerialization extends AbstractBrooklynObjectSerialization<Enricher> { +// public EntitySerialization(ManagementContext mgmt) { super(Entity.class, mgmt); } +// @Override protected Enricher getKind(String id) { return mgmt.getEntityManager().getEntity(id); } +// } + +} http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/8aa2b75b/core/src/main/java/org/apache/brooklyn/util/core/json/BrooklynObjectsJsonMapper.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/brooklyn/util/core/json/BrooklynObjectsJsonMapper.java b/core/src/main/java/org/apache/brooklyn/util/core/json/BrooklynObjectsJsonMapper.java new file mode 100644 index 0000000..1a2972e --- /dev/null +++ b/core/src/main/java/org/apache/brooklyn/util/core/json/BrooklynObjectsJsonMapper.java @@ -0,0 +1,43 @@ +/* + * Copyright 2016 The Apache Software Foundation. + * + * 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.apache.brooklyn.util.core.json; + +import org.apache.brooklyn.api.mgmt.ManagementContext; + +import com.fasterxml.jackson.core.Version; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.module.SimpleModule; + +public class BrooklynObjectsJsonMapper { + public static ObjectMapper newMapper(ManagementContext mgmt) { + ConfigurableSerializerProvider sp = new ConfigurableSerializerProvider(); + sp.setUnknownTypeSerializer(new ErrorAndToStringUnknownTypeSerializer()); + + ObjectMapper mapper = new ObjectMapper(); + mapper.setSerializerProvider(sp); + mapper.setVisibilityChecker(new PossiblyStrictPreferringFieldsVisibilityChecker()); + + SimpleModule mapperModule = new SimpleModule("Brooklyn", new Version(0, 0, 0, "ignored", null, null)); + + new BidiSerialization.ManagementContextSerialization(mgmt).install(mapperModule); + new BidiSerialization.EntitySerialization(mgmt).install(mapperModule); + new BidiSerialization.LocationSerialization(mgmt).install(mapperModule); + + mapperModule.addSerializer(new MultimapSerializer()); + mapper.registerModule(mapperModule); + return mapper; + } +} http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/8aa2b75b/core/src/main/java/org/apache/brooklyn/util/core/json/ConfigurableSerializerProvider.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/brooklyn/util/core/json/ConfigurableSerializerProvider.java b/core/src/main/java/org/apache/brooklyn/util/core/json/ConfigurableSerializerProvider.java new file mode 100644 index 0000000..055f289 --- /dev/null +++ b/core/src/main/java/org/apache/brooklyn/util/core/json/ConfigurableSerializerProvider.java @@ -0,0 +1,91 @@ +/* + * 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.util.core.json; + +import java.io.IOException; + +import org.apache.brooklyn.util.exceptions.Exceptions; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonStreamContext; +import com.fasterxml.jackson.databind.JavaType; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializationConfig; +import com.fasterxml.jackson.databind.ser.DefaultSerializerProvider; +import com.fasterxml.jackson.databind.ser.SerializerFactory; + +/** allows the serializer-of-last-resort to be customized, ie used for unknown-types */ +final class ConfigurableSerializerProvider extends DefaultSerializerProvider { + + private static final long serialVersionUID = 6094990395562170217L; + protected JsonSerializer<Object> unknownTypeSerializer; + + public ConfigurableSerializerProvider() {} + + @Override + public DefaultSerializerProvider createInstance(SerializationConfig config, SerializerFactory jsf) { + return new ConfigurableSerializerProvider(config, this, jsf); + } + + public ConfigurableSerializerProvider(SerializationConfig config, ConfigurableSerializerProvider src, SerializerFactory jsf) { + super(src, config, jsf); + unknownTypeSerializer = src.unknownTypeSerializer; + } + + @Override + public JsonSerializer<Object> getUnknownTypeSerializer(Class<?> unknownType) { + if (unknownTypeSerializer!=null) return unknownTypeSerializer; + return super.getUnknownTypeSerializer(unknownType); + } + + public void setUnknownTypeSerializer(JsonSerializer<Object> unknownTypeSerializer) { + this.unknownTypeSerializer = unknownTypeSerializer; + } + + @Override + public void serializeValue(JsonGenerator jgen, Object value) throws IOException { + JsonStreamContext ctxt = jgen.getOutputContext(); + try { + super.serializeValue(jgen, value); + } catch (Exception e) { + onSerializationException(ctxt, jgen, value, e); + } + } + + @Override + public void serializeValue(JsonGenerator jgen, Object value, JavaType rootType) throws IOException { + JsonStreamContext ctxt = jgen.getOutputContext(); + try { + super.serializeValue(jgen, value, rootType); + } catch (Exception e) { + onSerializationException(ctxt, jgen, value, e); + } + } + + protected void onSerializationException(JsonStreamContext ctxt, JsonGenerator jgen, Object value, Exception e) throws IOException { + Exceptions.propagateIfFatal(e); + + JsonSerializer<Object> unknownTypeSerializer = getUnknownTypeSerializer(value.getClass()); + if (unknownTypeSerializer instanceof ErrorAndToStringUnknownTypeSerializer) { + ((ErrorAndToStringUnknownTypeSerializer)unknownTypeSerializer).serializeFromError(ctxt, e, value, jgen, this); + } else { + unknownTypeSerializer.serialize(value, jgen, this); + } + } +} http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/8aa2b75b/core/src/main/java/org/apache/brooklyn/util/core/json/ErrorAndToStringUnknownTypeSerializer.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/brooklyn/util/core/json/ErrorAndToStringUnknownTypeSerializer.java b/core/src/main/java/org/apache/brooklyn/util/core/json/ErrorAndToStringUnknownTypeSerializer.java new file mode 100644 index 0000000..613c55f --- /dev/null +++ b/core/src/main/java/org/apache/brooklyn/util/core/json/ErrorAndToStringUnknownTypeSerializer.java @@ -0,0 +1,123 @@ +/* + * 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.util.core.json; + +import java.io.IOException; +import java.io.NotSerializableException; +import java.util.Collections; +import java.util.Set; + +import javax.annotation.Nullable; + +import org.apache.brooklyn.util.collections.MutableSet; +import org.apache.brooklyn.util.javalang.Reflections; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonStreamContext; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.fasterxml.jackson.databind.ser.impl.UnknownSerializer; + +/** + * for non-json-serializable classes (quite a lot of them!) simply provide a sensible error message and a toString. + * TODO maybe we want to attempt to serialize fields instead? (but being careful not to be self-referential!) + */ +public class ErrorAndToStringUnknownTypeSerializer extends UnknownSerializer { + + private static final Logger log = LoggerFactory.getLogger(ErrorAndToStringUnknownTypeSerializer.class); + private static Set<String> WARNED_CLASSES = Collections.synchronizedSet(MutableSet.<String>of()); + + @Override + public void serialize(Object value, JsonGenerator jgen, SerializerProvider provider) throws IOException { + if (BidiSerialization.isStrictSerialization()) + throw new JsonMappingException("Cannot serialize object containing "+value.getClass().getName()+" when strict serialization requested"); + + serializeFromError(jgen.getOutputContext(), null, value, jgen, provider); + } + + public void serializeFromError(JsonStreamContext ctxt, @Nullable Exception error, Object value, JsonGenerator jgen, SerializerProvider configurableSerializerProvider) throws IOException { + if (log.isDebugEnabled()) + log.debug("Recovering from json serialization error, serializing "+value+": "+error); + + if (BidiSerialization.isStrictSerialization()) + throw new JsonMappingException("Cannot serialize " + + (ctxt!=null && !ctxt.inRoot() ? "object containing " : "") + + value.getClass().getName()+" when strict serialization requested"); + + if (WARNED_CLASSES.add(value.getClass().getCanonicalName())) { + log.warn("Standard serialization not possible for "+value.getClass()+" ("+value+")", error); + } + JsonStreamContext newCtxt = jgen.getOutputContext(); + + // very odd, but flush seems necessary when working with large objects; presumably a buffer which is allowed to clear itself? + // without this, when serializing the large (1.5M) Server json object from BrooklynJacksonSerializerTest creates invalid json, + // containing: "foo":false,"{"error":true,... + jgen.flush(); + + boolean createObject = !newCtxt.inObject() || newCtxt.getCurrentName()!=null; + if (createObject) { + jgen.writeStartObject(); + } + + if (allowEmpty(value.getClass())) { + // write nothing + } else { + + jgen.writeFieldName("error"); + jgen.writeBoolean(true); + + jgen.writeFieldName("errorType"); + jgen.writeString(NotSerializableException.class.getCanonicalName()); + + jgen.writeFieldName("type"); + jgen.writeString(value.getClass().getCanonicalName()); + + jgen.writeFieldName("toString"); + jgen.writeString(value.toString()); + + if (error!=null) { + jgen.writeFieldName("causedByError"); + jgen.writeString(error.toString()); + } + + } + + if (createObject) { + jgen.writeEndObject(); + } + + while (newCtxt!=null && !newCtxt.equals(ctxt)) { + if (jgen.getOutputContext().inArray()) { jgen.writeEndArray(); continue; } + if (jgen.getOutputContext().inObject()) { jgen.writeEndObject(); continue; } + break; + } + + } + + protected boolean allowEmpty(Class<? extends Object> clazz) { + if (clazz.getAnnotation(JsonSerialize.class)!=null && Reflections.hasNoNonObjectFields(clazz)) { + return true; + } else { + return false; + } + } +} http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/8aa2b75b/core/src/main/java/org/apache/brooklyn/util/core/json/MultimapSerializer.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/brooklyn/util/core/json/MultimapSerializer.java b/core/src/main/java/org/apache/brooklyn/util/core/json/MultimapSerializer.java new file mode 100644 index 0000000..d825e3e --- /dev/null +++ b/core/src/main/java/org/apache/brooklyn/util/core/json/MultimapSerializer.java @@ -0,0 +1,64 @@ +/* + * 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.util.core.json; + +import java.io.IOException; +import java.util.Collection; +import java.util.Map; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.ser.std.StdSerializer; + +import com.google.common.annotations.Beta; +import com.google.common.collect.Lists; +import com.google.common.collect.Multimap; + +/** + * Provides a serializer for {@link Multimap} instances. + * <p> + * When Brooklyn's Jackson dependency is updated from org.codehaus.jackson:1.9.13 to + * com.fasterxml.jackson:2.3+ then this class should be replaced with a dependency on + * jackson-datatype-guava and a GuavaModule registered with Brooklyn's ObjectMapper. + * Check the guava version when doing the switch as it could be incompatible with the + * version used by Brooklyn. + */ +@Beta +public class MultimapSerializer extends StdSerializer<Multimap<?, ?>> { + + @SuppressWarnings({ "unchecked", "rawtypes" }) + protected MultimapSerializer() { + super((Class<Multimap<?, ?>>) (Class) Multimap.class); + } + + @Override + public void serialize(Multimap<?, ?> value, JsonGenerator jgen, SerializerProvider provider) throws IOException { + jgen.writeStartObject(); + writeEntries(value, jgen, provider); + jgen.writeEndObject(); + } + + private void writeEntries(Multimap<?, ?> value, JsonGenerator jgen, SerializerProvider provider) throws IOException { + for (Map.Entry<?, ? extends Collection<?>> entry : value.asMap().entrySet()) { + provider.findKeySerializer(provider.constructType(String.class), null) + .serialize(entry.getKey(), jgen, provider); + provider.defaultSerializeValue(Lists.newArrayList(entry.getValue()), jgen); + } + } +} http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/8aa2b75b/core/src/main/java/org/apache/brooklyn/util/core/json/PossiblyStrictPreferringFieldsVisibilityChecker.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/brooklyn/util/core/json/PossiblyStrictPreferringFieldsVisibilityChecker.java b/core/src/main/java/org/apache/brooklyn/util/core/json/PossiblyStrictPreferringFieldsVisibilityChecker.java new file mode 100644 index 0000000..afe5a7f --- /dev/null +++ b/core/src/main/java/org/apache/brooklyn/util/core/json/PossiblyStrictPreferringFieldsVisibilityChecker.java @@ -0,0 +1,108 @@ +/* + * 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.util.core.json; + +import java.lang.reflect.Field; +import java.lang.reflect.Member; +import java.lang.reflect.Method; + +import com.fasterxml.jackson.annotation.JsonAutoDetect; +import com.fasterxml.jackson.annotation.PropertyAccessor; +import com.fasterxml.jackson.databind.introspect.AnnotatedField; +import com.fasterxml.jackson.databind.introspect.AnnotatedMember; +import com.fasterxml.jackson.databind.introspect.AnnotatedMethod; +import com.fasterxml.jackson.databind.introspect.VisibilityChecker; + +import static com.fasterxml.jackson.annotation.JsonAutoDetect.*; + +/** a visibility checker which disables getters, but allows private access, + * unless {@link BidiSerialization#isStrictSerialization()} is enabled in which case public fields or annotations must be used. + * <p> + * the reason for this change to visibility + * is that getters might generate a copy, resulting in infinite loops, whereas field access should never do so. + * (see e.g. test in {@link BrooklynJacksonSerializerTest} which uses a sensor+config object whose getTypeToken + * causes infinite recursion) + **/ +public class PossiblyStrictPreferringFieldsVisibilityChecker implements VisibilityChecker<PossiblyStrictPreferringFieldsVisibilityChecker> { + VisibilityChecker<?> + vizDefault = new VisibilityChecker.Std(Visibility.NONE, Visibility.NONE, Visibility.NONE, Visibility.ANY, Visibility.ANY), + vizStrict = new VisibilityChecker.Std(Visibility.NONE, Visibility.NONE, Visibility.NONE, Visibility.PUBLIC_ONLY, Visibility.PUBLIC_ONLY); + + @Override public PossiblyStrictPreferringFieldsVisibilityChecker with(JsonAutoDetect ann) { throw new UnsupportedOperationException(); } + @Override public PossiblyStrictPreferringFieldsVisibilityChecker with(Visibility v) { throw new UnsupportedOperationException(); } + @Override public PossiblyStrictPreferringFieldsVisibilityChecker withVisibility(PropertyAccessor method, Visibility v) { throw new UnsupportedOperationException(); } + @Override public PossiblyStrictPreferringFieldsVisibilityChecker withGetterVisibility(Visibility v) { throw new UnsupportedOperationException(); } + @Override public PossiblyStrictPreferringFieldsVisibilityChecker withIsGetterVisibility(Visibility v) { throw new UnsupportedOperationException(); } + @Override public PossiblyStrictPreferringFieldsVisibilityChecker withSetterVisibility(Visibility v) { throw new UnsupportedOperationException(); } + @Override public PossiblyStrictPreferringFieldsVisibilityChecker withCreatorVisibility(Visibility v) { throw new UnsupportedOperationException(); } + @Override public PossiblyStrictPreferringFieldsVisibilityChecker withFieldVisibility(Visibility v) { throw new UnsupportedOperationException(); } + + protected VisibilityChecker<?> viz() { + return BidiSerialization.isStrictSerialization() ? vizStrict : vizDefault; + } + + @Override public boolean isGetterVisible(Method m) { + return viz().isGetterVisible(m); + } + + @Override + public boolean isGetterVisible(AnnotatedMethod m) { + return isGetterVisible(m.getAnnotated()); + } + + @Override + public boolean isIsGetterVisible(Method m) { + return viz().isIsGetterVisible(m); + } + + @Override + public boolean isIsGetterVisible(AnnotatedMethod m) { + return isIsGetterVisible(m.getAnnotated()); + } + + @Override + public boolean isSetterVisible(Method m) { + return viz().isSetterVisible(m); + } + + @Override + public boolean isSetterVisible(AnnotatedMethod m) { + return isSetterVisible(m.getAnnotated()); + } + + @Override + public boolean isCreatorVisible(Member m) { + return viz().isCreatorVisible(m); + } + + @Override + public boolean isCreatorVisible(AnnotatedMember m) { + return isCreatorVisible(m.getMember()); + } + + @Override + public boolean isFieldVisible(Field f) { + return viz().isFieldVisible(f); + } + + @Override + public boolean isFieldVisible(AnnotatedField f) { + return isFieldVisible(f.getAnnotated()); + } +} http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/8aa2b75b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/AbstractBrooklynRestResource.java ---------------------------------------------------------------------- diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/AbstractBrooklynRestResource.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/AbstractBrooklynRestResource.java index adf50dd..2c47866 100644 --- a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/AbstractBrooklynRestResource.java +++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/AbstractBrooklynRestResource.java @@ -24,6 +24,7 @@ import javax.ws.rs.core.UriInfo; import javax.ws.rs.ext.ContextResolver; import org.apache.brooklyn.api.entity.Entity; +import org.apache.brooklyn.api.entity.EntityLocal; import org.apache.brooklyn.api.mgmt.ManagementContext; import org.apache.brooklyn.core.config.render.RendererHints; import org.apache.brooklyn.core.mgmt.internal.ManagementContextInternal; http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/8aa2b75b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/util/WebResourceUtils.java ---------------------------------------------------------------------- diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/util/WebResourceUtils.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/util/WebResourceUtils.java index f3723e6..5e8b7f9 100644 --- a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/util/WebResourceUtils.java +++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/util/WebResourceUtils.java @@ -32,7 +32,6 @@ import org.apache.brooklyn.api.mgmt.ManagementContext; import org.apache.brooklyn.core.catalog.internal.CatalogUtils; import org.apache.brooklyn.rest.domain.ApiError; import org.apache.brooklyn.rest.util.json.BrooklynJacksonJsonProvider; -import org.apache.brooklyn.util.core.osgi.Compat; import org.apache.brooklyn.util.exceptions.Exceptions; import org.apache.brooklyn.util.exceptions.PropagatedRuntimeException; import org.apache.brooklyn.util.net.Urls; http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/8aa2b75b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/util/json/BidiSerialization.java ---------------------------------------------------------------------- diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/util/json/BidiSerialization.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/util/json/BidiSerialization.java deleted file mode 100644 index 93cae3f..0000000 --- a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/util/json/BidiSerialization.java +++ /dev/null @@ -1,173 +0,0 @@ -/* - * 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.rest.util.json; - -import java.io.IOException; -import java.util.Map; - -import org.apache.brooklyn.api.entity.Entity; -import org.apache.brooklyn.api.location.Location; -import org.apache.brooklyn.api.mgmt.ManagementContext; -import org.apache.brooklyn.api.objs.BrooklynObject; - -import com.fasterxml.jackson.core.JsonGenerator; -import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.JsonDeserializer; -import com.fasterxml.jackson.databind.JsonSerializer; -import com.fasterxml.jackson.databind.SerializerProvider; -import com.fasterxml.jackson.databind.module.SimpleModule; - -public class BidiSerialization { - - protected final static ThreadLocal<Boolean> STRICT_SERIALIZATION = new ThreadLocal<Boolean>(); - - /** - * Sets strict serialization on, or off (the default), for the current thread. - * Recommended to be used in a <code>try { ... } finally { ... }</code> block - * with {@link #clearStrictSerialization()} at the end. - * <p> - * With strict serialization, classes must have public fields or annotated fields, else they will not be serialized. - */ - public static void setStrictSerialization(Boolean value) { - STRICT_SERIALIZATION.set(value); - } - - public static void clearStrictSerialization() { - STRICT_SERIALIZATION.remove(); - } - - public static boolean isStrictSerialization() { - Boolean result = STRICT_SERIALIZATION.get(); - if (result!=null) return result; - return false; - } - - - public abstract static class AbstractWithManagementContextSerialization<T> { - - protected class Serializer extends JsonSerializer<T> { - @Override - public void serialize(T value, JsonGenerator jgen, SerializerProvider provider) throws IOException { - AbstractWithManagementContextSerialization.this.serialize(value, jgen, provider); - } - } - - protected class Deserializer extends JsonDeserializer<T> { - @Override - public T deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException { - return AbstractWithManagementContextSerialization.this.deserialize(jp, ctxt); - } - } - - protected final Serializer serializer = new Serializer(); - protected final Deserializer deserializer = new Deserializer(); - protected final Class<T> type; - protected final ManagementContext mgmt; - - public AbstractWithManagementContextSerialization(Class<T> type, ManagementContext mgmt) { - this.type = type; - this.mgmt = mgmt; - } - - public JsonSerializer<T> getSerializer() { - return serializer; - } - - public JsonDeserializer<T> getDeserializer() { - return deserializer; - } - - public void serialize(T value, JsonGenerator jgen, SerializerProvider provider) throws IOException { - jgen.writeStartObject(); - writeBody(value, jgen, provider); - jgen.writeEndObject(); - } - - protected void writeBody(T value, JsonGenerator jgen, SerializerProvider provider) throws IOException { - jgen.writeStringField("type", value.getClass().getCanonicalName()); - customWriteBody(value, jgen, provider); - } - - public abstract void customWriteBody(T value, JsonGenerator jgen, SerializerProvider provider) throws IOException; - - public T deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException { - @SuppressWarnings("unchecked") - Map<Object,Object> values = jp.readValueAs(Map.class); - String type = (String) values.get("type"); - return customReadBody(type, values, jp, ctxt); - } - - protected abstract T customReadBody(String type, Map<Object, Object> values, JsonParser jp, DeserializationContext ctxt) throws IOException; - - public void install(SimpleModule module) { - module.addSerializer(type, serializer); - module.addDeserializer(type, deserializer); - } - } - - public static class ManagementContextSerialization extends AbstractWithManagementContextSerialization<ManagementContext> { - public ManagementContextSerialization(ManagementContext mgmt) { super(ManagementContext.class, mgmt); } - @Override - public void customWriteBody(ManagementContext value, JsonGenerator jgen, SerializerProvider provider) throws IOException {} - @Override - protected ManagementContext customReadBody(String type, Map<Object, Object> values, JsonParser jp, DeserializationContext ctxt) throws IOException { - return mgmt; - } - } - - public abstract static class AbstractBrooklynObjectSerialization<T extends BrooklynObject> extends AbstractWithManagementContextSerialization<T> { - public AbstractBrooklynObjectSerialization(Class<T> type, ManagementContext mgmt) { - super(type, mgmt); - } - @Override - protected void writeBody(T value, JsonGenerator jgen, SerializerProvider provider) throws IOException { - jgen.writeStringField("type", type.getCanonicalName()); - customWriteBody(value, jgen, provider); - } - @Override - public void customWriteBody(T value, JsonGenerator jgen, SerializerProvider provider) throws IOException { - jgen.writeStringField("id", value.getId()); - } - @Override - protected T customReadBody(String type, Map<Object, Object> values, JsonParser jp, DeserializationContext ctxt) throws IOException { - return getInstanceFromId((String) values.get("id")); - } - protected abstract T getInstanceFromId(String id); - } - - public static class EntitySerialization extends AbstractBrooklynObjectSerialization<Entity> { - public EntitySerialization(ManagementContext mgmt) { super(Entity.class, mgmt); } - @Override protected Entity getInstanceFromId(String id) { return mgmt.getEntityManager().getEntity(id); } - } - public static class LocationSerialization extends AbstractBrooklynObjectSerialization<Location> { - public LocationSerialization(ManagementContext mgmt) { super(Location.class, mgmt); } - @Override protected Location getInstanceFromId(String id) { return mgmt.getLocationManager().getLocation(id); } - } - // TODO how to look up policies and enrichers? (not essential...) -// public static class PolicySerialization extends AbstractBrooklynObjectSerialization<Policy> { -// public EntitySerialization(ManagementContext mgmt) { super(Policy.class, mgmt); } -// @Override protected Policy getKind(String id) { return mgmt.getEntityManager().getEntity(id); } -// } -// public static class EnricherSerialization extends AbstractBrooklynObjectSerialization<Enricher> { -// public EntitySerialization(ManagementContext mgmt) { super(Entity.class, mgmt); } -// @Override protected Enricher getKind(String id) { return mgmt.getEntityManager().getEntity(id); } -// } - -} http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/8aa2b75b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/util/json/BrooklynJacksonJsonProvider.java ---------------------------------------------------------------------- diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/util/json/BrooklynJacksonJsonProvider.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/util/json/BrooklynJacksonJsonProvider.java index 83106ab..c099cf2 100644 --- a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/util/json/BrooklynJacksonJsonProvider.java +++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/util/json/BrooklynJacksonJsonProvider.java @@ -34,12 +34,11 @@ import org.apache.brooklyn.core.config.ConfigKeys; import org.apache.brooklyn.core.internal.BrooklynProperties; import org.apache.brooklyn.core.server.BrooklynServiceAttributes; import org.apache.brooklyn.rest.util.OsgiCompat; +import org.apache.brooklyn.util.core.json.BrooklynObjectsJsonMapper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.fasterxml.jackson.core.Version; import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.module.SimpleModule; import com.fasterxml.jackson.jaxrs.json.JacksonJsonProvider; @Provider @@ -153,23 +152,7 @@ public class BrooklynJacksonJsonProvider extends JacksonJsonProvider implements throw new IllegalStateException("No management context available for creating ObjectMapper"); } - ConfigurableSerializerProvider sp = new ConfigurableSerializerProvider(); - sp.setUnknownTypeSerializer(new ErrorAndToStringUnknownTypeSerializer()); - - ObjectMapper mapper = new ObjectMapper(); - mapper.setSerializerProvider(sp); - mapper.setVisibilityChecker(new PossiblyStrictPreferringFieldsVisibilityChecker()); - - SimpleModule mapperModule = new SimpleModule("Brooklyn", new Version(0, 0, 0, "ignored", null, null)); - - new BidiSerialization.ManagementContextSerialization(mgmt).install(mapperModule); - new BidiSerialization.EntitySerialization(mgmt).install(mapperModule); - new BidiSerialization.LocationSerialization(mgmt).install(mapperModule); - - mapperModule.addSerializer(new MultimapSerializer()); - mapper.registerModule(mapperModule); - - return mapper; + return BrooklynObjectsJsonMapper.newMapper(mgmt); } } http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/8aa2b75b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/util/json/ConfigurableSerializerProvider.java ---------------------------------------------------------------------- diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/util/json/ConfigurableSerializerProvider.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/util/json/ConfigurableSerializerProvider.java deleted file mode 100644 index b77202f..0000000 --- a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/util/json/ConfigurableSerializerProvider.java +++ /dev/null @@ -1,91 +0,0 @@ -/* - * 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.rest.util.json; - -import java.io.IOException; - -import org.apache.brooklyn.util.exceptions.Exceptions; - -import com.fasterxml.jackson.core.JsonGenerator; -import com.fasterxml.jackson.core.JsonStreamContext; -import com.fasterxml.jackson.databind.JavaType; -import com.fasterxml.jackson.databind.JsonSerializer; -import com.fasterxml.jackson.databind.SerializationConfig; -import com.fasterxml.jackson.databind.ser.DefaultSerializerProvider; -import com.fasterxml.jackson.databind.ser.SerializerFactory; - -/** allows the serializer-of-last-resort to be customized, ie used for unknown-types */ -final class ConfigurableSerializerProvider extends DefaultSerializerProvider { - - private static final long serialVersionUID = 6094990395562170217L; - protected JsonSerializer<Object> unknownTypeSerializer; - - public ConfigurableSerializerProvider() {} - - @Override - public DefaultSerializerProvider createInstance(SerializationConfig config, SerializerFactory jsf) { - return new ConfigurableSerializerProvider(config, this, jsf); - } - - public ConfigurableSerializerProvider(SerializationConfig config, ConfigurableSerializerProvider src, SerializerFactory jsf) { - super(src, config, jsf); - unknownTypeSerializer = src.unknownTypeSerializer; - } - - @Override - public JsonSerializer<Object> getUnknownTypeSerializer(Class<?> unknownType) { - if (unknownTypeSerializer!=null) return unknownTypeSerializer; - return super.getUnknownTypeSerializer(unknownType); - } - - public void setUnknownTypeSerializer(JsonSerializer<Object> unknownTypeSerializer) { - this.unknownTypeSerializer = unknownTypeSerializer; - } - - @Override - public void serializeValue(JsonGenerator jgen, Object value) throws IOException { - JsonStreamContext ctxt = jgen.getOutputContext(); - try { - super.serializeValue(jgen, value); - } catch (Exception e) { - onSerializationException(ctxt, jgen, value, e); - } - } - - @Override - public void serializeValue(JsonGenerator jgen, Object value, JavaType rootType) throws IOException { - JsonStreamContext ctxt = jgen.getOutputContext(); - try { - super.serializeValue(jgen, value, rootType); - } catch (Exception e) { - onSerializationException(ctxt, jgen, value, e); - } - } - - protected void onSerializationException(JsonStreamContext ctxt, JsonGenerator jgen, Object value, Exception e) throws IOException { - Exceptions.propagateIfFatal(e); - - JsonSerializer<Object> unknownTypeSerializer = getUnknownTypeSerializer(value.getClass()); - if (unknownTypeSerializer instanceof ErrorAndToStringUnknownTypeSerializer) { - ((ErrorAndToStringUnknownTypeSerializer)unknownTypeSerializer).serializeFromError(ctxt, e, value, jgen, this); - } else { - unknownTypeSerializer.serialize(value, jgen, this); - } - } -} http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/8aa2b75b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/util/json/ErrorAndToStringUnknownTypeSerializer.java ---------------------------------------------------------------------- diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/util/json/ErrorAndToStringUnknownTypeSerializer.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/util/json/ErrorAndToStringUnknownTypeSerializer.java deleted file mode 100644 index e529ec9..0000000 --- a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/util/json/ErrorAndToStringUnknownTypeSerializer.java +++ /dev/null @@ -1,123 +0,0 @@ -/* - * 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.rest.util.json; - -import java.io.IOException; -import java.io.NotSerializableException; -import java.util.Collections; -import java.util.Set; - -import javax.annotation.Nullable; - -import org.apache.brooklyn.util.collections.MutableSet; -import org.apache.brooklyn.util.javalang.Reflections; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.fasterxml.jackson.core.JsonGenerator; -import com.fasterxml.jackson.core.JsonStreamContext; -import com.fasterxml.jackson.databind.JsonMappingException; -import com.fasterxml.jackson.databind.SerializerProvider; -import com.fasterxml.jackson.databind.annotation.JsonSerialize; -import com.fasterxml.jackson.databind.ser.impl.UnknownSerializer; - -/** - * for non-json-serializable classes (quite a lot of them!) simply provide a sensible error message and a toString. - * TODO maybe we want to attempt to serialize fields instead? (but being careful not to be self-referential!) - */ -public class ErrorAndToStringUnknownTypeSerializer extends UnknownSerializer { - - private static final Logger log = LoggerFactory.getLogger(ErrorAndToStringUnknownTypeSerializer.class); - private static Set<String> WARNED_CLASSES = Collections.synchronizedSet(MutableSet.<String>of()); - - @Override - public void serialize(Object value, JsonGenerator jgen, SerializerProvider provider) throws IOException { - if (BidiSerialization.isStrictSerialization()) - throw new JsonMappingException("Cannot serialize object containing "+value.getClass().getName()+" when strict serialization requested"); - - serializeFromError(jgen.getOutputContext(), null, value, jgen, provider); - } - - public void serializeFromError(JsonStreamContext ctxt, @Nullable Exception error, Object value, JsonGenerator jgen, SerializerProvider configurableSerializerProvider) throws IOException { - if (log.isDebugEnabled()) - log.debug("Recovering from json serialization error, serializing "+value+": "+error); - - if (BidiSerialization.isStrictSerialization()) - throw new JsonMappingException("Cannot serialize " - + (ctxt!=null && !ctxt.inRoot() ? "object containing " : "") - + value.getClass().getName()+" when strict serialization requested"); - - if (WARNED_CLASSES.add(value.getClass().getCanonicalName())) { - log.warn("Standard serialization not possible for "+value.getClass()+" ("+value+")", error); - } - JsonStreamContext newCtxt = jgen.getOutputContext(); - - // very odd, but flush seems necessary when working with large objects; presumably a buffer which is allowed to clear itself? - // without this, when serializing the large (1.5M) Server json object from BrooklynJacksonSerializerTest creates invalid json, - // containing: "foo":false,"{"error":true,... - jgen.flush(); - - boolean createObject = !newCtxt.inObject() || newCtxt.getCurrentName()!=null; - if (createObject) { - jgen.writeStartObject(); - } - - if (allowEmpty(value.getClass())) { - // write nothing - } else { - - jgen.writeFieldName("error"); - jgen.writeBoolean(true); - - jgen.writeFieldName("errorType"); - jgen.writeString(NotSerializableException.class.getCanonicalName()); - - jgen.writeFieldName("type"); - jgen.writeString(value.getClass().getCanonicalName()); - - jgen.writeFieldName("toString"); - jgen.writeString(value.toString()); - - if (error!=null) { - jgen.writeFieldName("causedByError"); - jgen.writeString(error.toString()); - } - - } - - if (createObject) { - jgen.writeEndObject(); - } - - while (newCtxt!=null && !newCtxt.equals(ctxt)) { - if (jgen.getOutputContext().inArray()) { jgen.writeEndArray(); continue; } - if (jgen.getOutputContext().inObject()) { jgen.writeEndObject(); continue; } - break; - } - - } - - protected boolean allowEmpty(Class<? extends Object> clazz) { - if (clazz.getAnnotation(JsonSerialize.class)!=null && Reflections.hasNoNonObjectFields(clazz)) { - return true; - } else { - return false; - } - } -} http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/8aa2b75b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/util/json/MultimapSerializer.java ---------------------------------------------------------------------- diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/util/json/MultimapSerializer.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/util/json/MultimapSerializer.java deleted file mode 100644 index f750e86..0000000 --- a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/util/json/MultimapSerializer.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * 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.rest.util.json; - -import java.io.IOException; -import java.util.Collection; -import java.util.Map; - -import com.fasterxml.jackson.core.JsonGenerator; -import com.fasterxml.jackson.databind.SerializerProvider; -import com.fasterxml.jackson.databind.ser.std.StdSerializer; - -import com.google.common.annotations.Beta; -import com.google.common.collect.Lists; -import com.google.common.collect.Multimap; - -/** - * Provides a serializer for {@link Multimap} instances. - * <p> - * When Brooklyn's Jackson dependency is updated from org.codehaus.jackson:1.9.13 to - * com.fasterxml.jackson:2.3+ then this class should be replaced with a dependency on - * jackson-datatype-guava and a GuavaModule registered with Brooklyn's ObjectMapper. - * Check the guava version when doing the switch as it could be incompatible with the - * version used by Brooklyn. - */ -@Beta -public class MultimapSerializer extends StdSerializer<Multimap<?, ?>> { - - @SuppressWarnings({ "unchecked", "rawtypes" }) - protected MultimapSerializer() { - super((Class<Multimap<?, ?>>) (Class) Multimap.class); - } - - @Override - public void serialize(Multimap<?, ?> value, JsonGenerator jgen, SerializerProvider provider) throws IOException { - jgen.writeStartObject(); - writeEntries(value, jgen, provider); - jgen.writeEndObject(); - } - - private void writeEntries(Multimap<?, ?> value, JsonGenerator jgen, SerializerProvider provider) throws IOException { - for (Map.Entry<?, ? extends Collection<?>> entry : value.asMap().entrySet()) { - provider.findKeySerializer(provider.constructType(String.class), null) - .serialize(entry.getKey(), jgen, provider); - provider.defaultSerializeValue(Lists.newArrayList(entry.getValue()), jgen); - } - } -} http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/8aa2b75b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/util/json/PossiblyStrictPreferringFieldsVisibilityChecker.java ---------------------------------------------------------------------- diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/util/json/PossiblyStrictPreferringFieldsVisibilityChecker.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/util/json/PossiblyStrictPreferringFieldsVisibilityChecker.java deleted file mode 100644 index e474467..0000000 --- a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/util/json/PossiblyStrictPreferringFieldsVisibilityChecker.java +++ /dev/null @@ -1,108 +0,0 @@ -/* - * 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.rest.util.json; - -import java.lang.reflect.Field; -import java.lang.reflect.Member; -import java.lang.reflect.Method; - -import com.fasterxml.jackson.annotation.JsonAutoDetect; -import com.fasterxml.jackson.annotation.PropertyAccessor; -import com.fasterxml.jackson.databind.introspect.AnnotatedField; -import com.fasterxml.jackson.databind.introspect.AnnotatedMember; -import com.fasterxml.jackson.databind.introspect.AnnotatedMethod; -import com.fasterxml.jackson.databind.introspect.VisibilityChecker; - -import static com.fasterxml.jackson.annotation.JsonAutoDetect.*; - -/** a visibility checker which disables getters, but allows private access, - * unless {@link BidiSerialization#isStrictSerialization()} is enabled in which case public fields or annotations must be used. - * <p> - * the reason for this change to visibility - * is that getters might generate a copy, resulting in infinite loops, whereas field access should never do so. - * (see e.g. test in {@link BrooklynJacksonSerializerTest} which uses a sensor+config object whose getTypeToken - * causes infinite recursion) - **/ -public class PossiblyStrictPreferringFieldsVisibilityChecker implements VisibilityChecker<PossiblyStrictPreferringFieldsVisibilityChecker> { - VisibilityChecker<?> - vizDefault = new VisibilityChecker.Std(Visibility.NONE, Visibility.NONE, Visibility.NONE, Visibility.ANY, Visibility.ANY), - vizStrict = new VisibilityChecker.Std(Visibility.NONE, Visibility.NONE, Visibility.NONE, Visibility.PUBLIC_ONLY, Visibility.PUBLIC_ONLY); - - @Override public PossiblyStrictPreferringFieldsVisibilityChecker with(JsonAutoDetect ann) { throw new UnsupportedOperationException(); } - @Override public PossiblyStrictPreferringFieldsVisibilityChecker with(Visibility v) { throw new UnsupportedOperationException(); } - @Override public PossiblyStrictPreferringFieldsVisibilityChecker withVisibility(PropertyAccessor method, Visibility v) { throw new UnsupportedOperationException(); } - @Override public PossiblyStrictPreferringFieldsVisibilityChecker withGetterVisibility(Visibility v) { throw new UnsupportedOperationException(); } - @Override public PossiblyStrictPreferringFieldsVisibilityChecker withIsGetterVisibility(Visibility v) { throw new UnsupportedOperationException(); } - @Override public PossiblyStrictPreferringFieldsVisibilityChecker withSetterVisibility(Visibility v) { throw new UnsupportedOperationException(); } - @Override public PossiblyStrictPreferringFieldsVisibilityChecker withCreatorVisibility(Visibility v) { throw new UnsupportedOperationException(); } - @Override public PossiblyStrictPreferringFieldsVisibilityChecker withFieldVisibility(Visibility v) { throw new UnsupportedOperationException(); } - - protected VisibilityChecker<?> viz() { - return BidiSerialization.isStrictSerialization() ? vizStrict : vizDefault; - } - - @Override public boolean isGetterVisible(Method m) { - return viz().isGetterVisible(m); - } - - @Override - public boolean isGetterVisible(AnnotatedMethod m) { - return isGetterVisible(m.getAnnotated()); - } - - @Override - public boolean isIsGetterVisible(Method m) { - return viz().isIsGetterVisible(m); - } - - @Override - public boolean isIsGetterVisible(AnnotatedMethod m) { - return isIsGetterVisible(m.getAnnotated()); - } - - @Override - public boolean isSetterVisible(Method m) { - return viz().isSetterVisible(m); - } - - @Override - public boolean isSetterVisible(AnnotatedMethod m) { - return isSetterVisible(m.getAnnotated()); - } - - @Override - public boolean isCreatorVisible(Member m) { - return viz().isCreatorVisible(m); - } - - @Override - public boolean isCreatorVisible(AnnotatedMember m) { - return isCreatorVisible(m.getMember()); - } - - @Override - public boolean isFieldVisible(Field f) { - return viz().isFieldVisible(f); - } - - @Override - public boolean isFieldVisible(AnnotatedField f) { - return isFieldVisible(f.getAnnotated()); - } -} http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/8aa2b75b/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/util/json/BrooklynJacksonSerializerTest.java ---------------------------------------------------------------------- diff --git a/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/util/json/BrooklynJacksonSerializerTest.java b/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/util/json/BrooklynJacksonSerializerTest.java index 2dd5ae1..eb91f0a 100644 --- a/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/util/json/BrooklynJacksonSerializerTest.java +++ b/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/util/json/BrooklynJacksonSerializerTest.java @@ -30,6 +30,7 @@ import org.apache.brooklyn.core.mgmt.BrooklynTaskTags; import org.apache.brooklyn.core.test.entity.LocalManagementContextForTests; import org.apache.brooklyn.util.collections.MutableList; import org.apache.brooklyn.util.collections.MutableMap; +import org.apache.brooklyn.util.core.json.BidiSerialization; import org.apache.brooklyn.util.exceptions.Exceptions; import org.apache.brooklyn.util.stream.Streams; import org.apache.brooklyn.util.text.Strings;