/* * Copyright 2002-2011 the original author or authors. * * 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.springframework.flex.http; import java.io.ByteArrayOutputStream; import java.util.HashMap; import java.util.Map; import java.util.Set; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.util.CollectionUtils; import org.springframework.validation.BindingResult; import org.springframework.web.servlet.View; import org.springframework.web.servlet.view.AbstractView; import flex.messaging.FlexContext; import flex.messaging.io.SerializationContext; import flex.messaging.io.amf.Amf3Output; import flex.messaging.io.amf.AmfTrace; /** * Spring-MVC {@link View} that renders AMF content by serializing the model for the current request using * BlazeDS's AMF serialization/deserialization APIs. * * <p>By default, the entire contents of the model map (with the exception of framework-specific classes) will be * encoded as AMF. For cases where the contents of the map need to be filtered, users may specify a specific set of * model attributes to encode via the {@link #setRenderedAttributes(Set) renderedAttributes} property. * * @author Jeremy Grelle */ public class AmfView extends AbstractView { public static final String DEFAULT_CONTENT_TYPE = "application/x-amf"; private static final Log log = LogFactory.getLog(AmfView.class); private Set<String> renderedAttributes; private boolean disableCaching = true; public AmfView() { setContentType(DEFAULT_CONTENT_TYPE); } /** * Returns the attributes in the model that should be rendered by this view. */ public Set<String> getRenderedAttributes() { return renderedAttributes; } /** * Sets the attributes in the model that should be rendered by this view. When set, all other model attributes will be * ignored. */ public void setRenderedAttributes(Set<String> renderedAttributes) { this.renderedAttributes = renderedAttributes; } /** * Disables caching of the generated AMF response. * * <p>Default is {@code true}, which will prevent the client from caching the generated AMF response. */ public void setDisableCaching(boolean disableCaching) { this.disableCaching = disableCaching; } /** * {@inheritDoc} */ @Override protected void prepareResponse(HttpServletRequest request, HttpServletResponse response) { response.setContentType(getContentType()); response.setCharacterEncoding("UTF-8"); if (disableCaching) { response.addHeader("Pragma", "no-cache"); response.addHeader("Cache-Control", "no-cache, no-store, max-age=0"); response.addDateHeader("Expires", 1L); } } /** * {@inheritDoc} */ @Override protected void renderMergedOutputModel(Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception { Object value = filterModel(model); try { AmfTrace trace = null; if (log.isDebugEnabled()) { trace = new AmfTrace(); } ByteArrayOutputStream outBuffer = new ByteArrayOutputStream(); SerializationContext context = new SerializationContext(); Amf3Output out = new Amf3Output(context); if (trace != null) { out.setDebugTrace(trace); } out.setOutputStream(outBuffer); out.writeObject(value); out.flush(); outBuffer.flush(); response.setContentLength(outBuffer.size()); outBuffer.writeTo(response.getOutputStream()); if (log.isDebugEnabled()) { log.debug("Wrote AMF message:\n" + trace); } } finally { FlexContext.clearThreadLocalObjects(); SerializationContext.clearThreadLocalObjects(); } } /** * Filters out undesired attributes from the given model. The return value can be either another {@link Map}, or a * single value object. If only a single attribute is present in the model map, that value will be returned instead * of the full map. * * <p>Default implementation removes {@link BindingResult} instances and entries not included in the {@link * #setRenderedAttributes(Set) renderedAttributes} property. * * @param model the model, as passed on to {@link #renderMergedOutputModel} * @return the object to be rendered */ protected Object filterModel(Map<String, Object> model) { Map<String, Object> result = new HashMap<String, Object>(model.size()); Set<String> renderedAttributes = !CollectionUtils.isEmpty(this.renderedAttributes) ? this.renderedAttributes : model.keySet(); for (Map.Entry<String, Object> entry : model.entrySet()) { if (!(entry.getValue() instanceof BindingResult) && renderedAttributes.contains(entry.getKey())) { result.put(entry.getKey(), entry.getValue()); } } if (result.size() == 1) { return result.values().iterator().next(); } else { return result; } } }