/*
* 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 javax.servlet.ServletContext;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import org.apache.brooklyn.api.mgmt.ManagementContext;
import org.apache.brooklyn.config.ConfigKey;
import org.apache.brooklyn.core.config.ConfigKeys;
import org.apache.brooklyn.core.internal.BrooklynProperties;
import org.apache.brooklyn.core.mgmt.ManagementContextInjectable;
import org.apache.brooklyn.core.mgmt.internal.ManagementContextInternal;
import org.apache.brooklyn.core.server.BrooklynServiceAttributes;
import org.apache.brooklyn.rest.util.OsgiCompat;
import org.codehaus.jackson.Version;
import org.codehaus.jackson.jaxrs.JacksonJsonProvider;
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.map.SerializationConfig;
import org.codehaus.jackson.map.module.SimpleModule;
import org.codehaus.jackson.map.type.TypeFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class BrooklynJacksonJsonProvider extends JacksonJsonProvider implements ManagementContextInjectable {
private static final Logger log = LoggerFactory.getLogger(BrooklynJacksonJsonProvider.class);
public static final String BROOKLYN_REST_OBJECT_MAPPER = BrooklynServiceAttributes.BROOKLYN_REST_OBJECT_MAPPER;
@Context protected ServletContext servletContext;
protected ObjectMapper ourMapper;
protected boolean notFound = false;
private ManagementContext mgmt;
public ObjectMapper locateMapper(Class<?> type, MediaType mediaType) {
if (ourMapper != null)
return ourMapper;
findSharedMapper();
if (ourMapper != null)
return ourMapper;
if (!notFound) {
log.warn("Management context not available; using default ObjectMapper in "+this);
notFound = true;
}
return super.locateMapper(Object.class, MediaType.APPLICATION_JSON_TYPE);
}
protected synchronized ObjectMapper findSharedMapper() {
if (ourMapper != null || notFound)
return ourMapper;
ourMapper = findSharedObjectMapper(servletContext, mgmt);
if (ourMapper == null) return null;
if (notFound) {
notFound = false;
}
log.debug("Found mapper "+ourMapper+" for "+this+", creating custom Brooklyn mapper");
return ourMapper;
}
/**
* Finds a shared {@link ObjectMapper} or makes a new one, stored against the servlet context;
* returns null if a shared instance cannot be created.
*/
public static ObjectMapper findSharedObjectMapper(ServletContext servletContext, ManagementContext mgmt) {
if (servletContext != null) {
synchronized (servletContext) {
ObjectMapper mapper = (ObjectMapper) servletContext.getAttribute(BROOKLYN_REST_OBJECT_MAPPER);
if (mapper != null) return mapper;
mapper = newPrivateObjectMapper(getManagementContext(servletContext));
servletContext.setAttribute(BROOKLYN_REST_OBJECT_MAPPER, mapper);
return mapper;
}
}
if (mgmt != null) {
synchronized (mgmt) {
ConfigKey<ObjectMapper> key = ConfigKeys.newConfigKey(ObjectMapper.class, BROOKLYN_REST_OBJECT_MAPPER);
ObjectMapper mapper = mgmt.getConfig().getConfig(key);
if (mapper != null) return mapper;
mapper = newPrivateObjectMapper(mgmt);
log.debug("Storing new ObjectMapper against "+mgmt+" because no ServletContext available: "+mapper);
((ManagementContextInternal)mgmt).getBrooklynProperties().put(key, mapper);
return mapper;
}
}
return null;
}
/**
* Like {@link #findSharedObjectMapper(ServletContext, ManagementContext)} but will create a private
* ObjectMapper if it can, from the servlet context and/or the management context, or else fail
*/
public static ObjectMapper findAnyObjectMapper(ServletContext servletContext, ManagementContext mgmt) {
ObjectMapper mapper = findSharedObjectMapper(servletContext, mgmt);
if (mapper != null) return mapper;
if (mgmt == null && servletContext != null) {
mgmt = getManagementContext(servletContext);
}
return newPrivateObjectMapper(mgmt);
}
/**
* @return A new Brooklyn-specific ObjectMapper.
* Normally {@link #findSharedObjectMapper(ServletContext, ManagementContext)} is preferred
*/
public static ObjectMapper newPrivateObjectMapper(ManagementContext mgmt) {
if (mgmt == null) {
throw new IllegalStateException("No management context available for creating ObjectMapper");
}
SerializationConfig defaultConfig = new ObjectMapper().getSerializationConfig();
SerializationConfig sc = new SerializationConfig(
defaultConfig.getClassIntrospector() /* ObjectMapper.DEFAULT_INTROSPECTOR */,
defaultConfig.getAnnotationIntrospector() /* ObjectMapper.DEFAULT_ANNOTATION_INTROSPECTOR */,
new PossiblyStrictPreferringFieldsVisibilityChecker(),
null, null, TypeFactory.defaultInstance(), null);
ConfigurableSerializerProvider sp = new ConfigurableSerializerProvider();
sp.setUnknownTypeSerializer(new ErrorAndToStringUnknownTypeSerializer());
ObjectMapper mapper = new ObjectMapper(null, sp, null, sc, null);
SimpleModule mapperModule = new SimpleModule("Brooklyn", new Version(0, 0, 0, "ignored"));
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;
}
public static ManagementContext getManagementContext(ServletContext servletContext) {
return OsgiCompat.getManagementContext(servletContext);
}
@Override
public void setManagementContext(ManagementContext mgmt) {
this.mgmt = mgmt;
}
}