/* * 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.resources; import javax.annotation.Nullable; import javax.servlet.ServletContext; import javax.ws.rs.core.Context; import org.apache.brooklyn.api.entity.Entity; import org.apache.brooklyn.api.mgmt.ManagementContext; import org.apache.brooklyn.core.config.render.RendererHints; import org.apache.brooklyn.core.mgmt.ManagementContextInjectable; import org.apache.brooklyn.rest.util.BrooklynRestResourceUtils; import org.apache.brooklyn.rest.util.OsgiCompat; import org.apache.brooklyn.rest.util.WebResourceUtils; import org.apache.brooklyn.rest.util.json.BrooklynJacksonJsonProvider; import org.apache.brooklyn.util.core.task.Tasks; import org.apache.brooklyn.util.guava.Maybe; import org.apache.brooklyn.util.time.Duration; import org.codehaus.jackson.map.ObjectMapper; public abstract class AbstractBrooklynRestResource implements ManagementContextInjectable { // can be injected by jersey when ManagementContext in not injected manually // (seems there is no way to make this optional so note it _must_ be injected; // most of the time that happens for free, but with test framework it doesn't, // so we have set up a NullServletContextProvider in our tests) @Context ServletContext servletContext; private ManagementContext managementContext; private BrooklynRestResourceUtils brooklynRestResourceUtils; private ObjectMapper mapper; public ManagementContext mgmt() { return mgmtMaybe().get(); } protected synchronized Maybe<ManagementContext> mgmtMaybe() { if (managementContext!=null) return Maybe.of(managementContext); managementContext = OsgiCompat.getManagementContext(servletContext); if (managementContext!=null) return Maybe.of(managementContext); return Maybe.absent("ManagementContext not available for Brooklyn Jersey Resource "+this); } @Override public void setManagementContext(ManagementContext managementContext) { if (this.managementContext!=null) { if (this.managementContext.equals(managementContext)) return; throw new IllegalStateException("ManagementContext cannot be changed: specified twice for Brooklyn Jersey Resource "+this); } this.managementContext = managementContext; } public synchronized BrooklynRestResourceUtils brooklyn() { if (brooklynRestResourceUtils!=null) return brooklynRestResourceUtils; brooklynRestResourceUtils = new BrooklynRestResourceUtils(mgmt()); return brooklynRestResourceUtils; } protected ObjectMapper mapper() { if (mapper==null) mapper = BrooklynJacksonJsonProvider.findAnyObjectMapper(servletContext, managementContext); return mapper; } /** @deprecated since 0.7.0 use {@link #getValueForDisplay(Object, boolean, boolean, Boolean, EntityLocal, Duration)} */ @Deprecated protected Object getValueForDisplay(Object value, boolean preferJson, boolean isJerseyReturnValue) { return resolving(value).preferJson(preferJson).asJerseyOutermostReturnValue(isJerseyReturnValue).resolve(); } protected RestValueResolver resolving(Object v) { return new RestValueResolver(v).mapper(mapper()); } public static class RestValueResolver { final private Object valueToResolve; private @Nullable ObjectMapper mapper; private boolean preferJson; private boolean isJerseyReturnValue; private @Nullable Boolean raw; private @Nullable Entity entity; private @Nullable Duration timeout; private @Nullable Object rendererHintSource; public static RestValueResolver resolving(Object v) { return new RestValueResolver(v); } private RestValueResolver(Object v) { valueToResolve = v; } public RestValueResolver mapper(ObjectMapper mapper) { this.mapper = mapper; return this; } /** whether JSON is the ultimate product; * main effect here is to give null for null if true, else to give empty string * <p> * conversion to JSON for complex types is done subsequently (often by the framework) * <p> * default is true */ public RestValueResolver preferJson(boolean preferJson) { this.preferJson = preferJson; return this; } /** whether an outermost string must be wrapped in quotes, because a String return object is treated as * already JSON-encoded * <p> * default is false */ public RestValueResolver asJerseyOutermostReturnValue(boolean asJerseyReturnJson) { isJerseyReturnValue = asJerseyReturnJson; return this; } public RestValueResolver raw(Boolean raw) { this.raw = raw; return this; } public RestValueResolver context(Entity entity) { this.entity = entity; return this; } public RestValueResolver timeout(Duration timeout) { this.timeout = timeout; return this; } public RestValueResolver renderAs(Object rendererHintSource) { this.rendererHintSource = rendererHintSource; return this; } public Object resolve() { Object valueResult = getImmediateValue(valueToResolve, entity, timeout); if (valueResult==UNRESOLVED) valueResult = valueToResolve; if (rendererHintSource!=null && Boolean.FALSE.equals(raw)) { valueResult = RendererHints.applyDisplayValueHintUnchecked(rendererHintSource, valueResult); } return WebResourceUtils.getValueForDisplay(mapper, valueResult, preferJson, isJerseyReturnValue); } private static Object UNRESOLVED = "UNRESOLVED".toCharArray(); private static Object getImmediateValue(Object value, @Nullable Entity context, @Nullable Duration timeout) { return Tasks.resolving(value) .as(Object.class) .defaultValue(UNRESOLVED) .timeout(timeout) .context(context) .swallowExceptions() .get(); } } }