/* * 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.io.IOException; import java.util.List; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.http.HttpInputMessage; import org.springframework.http.HttpOutputMessage; import org.springframework.http.MediaType; import org.springframework.http.converter.AbstractHttpMessageConverter; import org.springframework.http.converter.HttpMessageNotReadableException; import org.springframework.http.converter.HttpMessageNotWritableException; import flex.messaging.FlexContext; import flex.messaging.MessageException; import flex.messaging.io.MessageIOConstants; import flex.messaging.io.SerializationContext; import flex.messaging.io.SerializationException; import flex.messaging.io.amf.ActionContext; import flex.messaging.io.amf.ActionMessage; import flex.messaging.io.amf.Amf3Input; import flex.messaging.io.amf.Amf3Output; import flex.messaging.io.amf.AmfMessageDeserializer; import flex.messaging.io.amf.AmfMessageSerializer; import flex.messaging.io.amf.AmfTrace; /** * Implementation of {@link org.springframework.http.converter.HttpMessageConverter HttpMessageConverter} * that can read and write AMF using BlazeDS's AMF serialization/deserialization APIs. * * <p>This converter can be used to bind to typed beans, or untyped {@link java.util.HashMap HashMap} instances. * * <p>By default, this converter supports {@code application/x-amf}. This can be overridden by setting the * {@link #setSupportedMediaTypes(List) supportedMediaTypes} property. * * @author Jeremy Grelle */ public class AmfHttpMessageConverter extends AbstractHttpMessageConverter<Object> { private static final String AMF_ERROR = "Could not read input message body as AMF"; private static final String ACTION_MSG_ERROR = "Could not read input message body as "+ActionMessage.class.getName(); private static final Log log = LogFactory.getLog(AmfHttpMessageConverter.class); public AmfHttpMessageConverter() { super(MediaType.parseMediaType(MessageIOConstants.AMF_CONTENT_TYPE)); } /** * {@inheritDoc} */ @Override protected boolean supports(Class<?> clazz) { return true; } /** * {@inheritDoc} */ @Override protected Object readInternal(Class<? extends Object> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException { try { AmfTrace trace = null; if (log.isDebugEnabled()) { trace = new AmfTrace(); } Object result = null; if (clazz.equals(ActionMessage.class)) { result = readActionMessage(inputMessage, trace); } else { result = readObject(inputMessage, trace); } if (log.isDebugEnabled()) { log.debug("Read AMF message:\n" + trace); } return result; } finally { FlexContext.clearThreadLocalObjects(); SerializationContext.clearThreadLocalObjects(); } } /** * {@inheritDoc} */ @Override protected void writeInternal(Object data, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException { try { AmfTrace trace = null; if (log.isDebugEnabled()) { trace = new AmfTrace(); } outputMessage.getHeaders().setPragma("no-cache"); outputMessage.getHeaders().setCacheControl("no-cache, no-store, max-age=0"); outputMessage.getHeaders().setExpires(1L); if (data instanceof ActionMessage) { writeActionMessage((ActionMessage) data, outputMessage, trace); } else { writeObject(data, outputMessage, trace); } if (log.isDebugEnabled()) { log.debug("Wrote AMF message:\n" + trace); } } finally { FlexContext.clearThreadLocalObjects(); SerializationContext.clearThreadLocalObjects(); } } private Object readObject(HttpInputMessage inputMessage, AmfTrace trace) throws IOException { Amf3Input deserializer = new Amf3Input(new SerializationContext()); deserializer.setInputStream(inputMessage.getBody()); deserializer.setDebugTrace(trace); try { return deserializer.readObject(); } catch (ClassNotFoundException cnfe) { throw new HttpMessageNotReadableException(AMF_ERROR, cnfe); } catch (MessageException se) { throw new HttpMessageNotReadableException(AMF_ERROR, se); } } private ActionMessage readActionMessage(HttpInputMessage inputMessage, AmfTrace trace) throws IOException { AmfMessageDeserializer deserializer = new AmfMessageDeserializer(); deserializer.initialize(new SerializationContext(), inputMessage.getBody(), trace); try { ActionContext context = new ActionContext(); ActionMessage message = new ActionMessage(); context.setRequestMessage(message); deserializer.readMessage(message, context); return message; } catch (ClassNotFoundException cnfe) { throw new HttpMessageNotReadableException(ACTION_MSG_ERROR, cnfe); } catch (MessageException me) { throw new HttpMessageNotReadableException(ACTION_MSG_ERROR, me); } } private void writeActionMessage(ActionMessage message, HttpOutputMessage outputMessage, AmfTrace trace) throws IOException { AmfMessageSerializer serializer = new AmfMessageSerializer(); ByteArrayOutputStream outBuffer = new ByteArrayOutputStream(); serializer.setVersion(message.getVersion()); serializer.initialize(new SerializationContext(), outBuffer, trace); try { ActionContext context = new ActionContext(); context.setVersion(message.getVersion()); context.setResponseMessage(message); serializer.writeMessage(message); outBuffer.flush(); outBuffer.close(); outputMessage.getHeaders().setContentLength(outBuffer.size()); outBuffer.writeTo(outputMessage.getBody()); } catch (SerializationException se) { throw new HttpMessageNotWritableException("Could not write "+message+" as AMF message.", se); } } private void writeObject(Object data, HttpOutputMessage outputMessage, AmfTrace trace) throws IOException { ByteArrayOutputStream outBuffer = new ByteArrayOutputStream(); Amf3Output serializer = new Amf3Output(new SerializationContext()); serializer.setOutputStream(outBuffer); serializer.setDebugTrace(trace); try { serializer.writeObject(data); outBuffer.flush(); outBuffer.close(); outputMessage.getHeaders().setContentLength(outBuffer.size()); outBuffer.writeTo(outputMessage.getBody()); } catch (SerializationException se) { throw new HttpMessageNotWritableException("Could not write "+data+" as AMF message.", se); } } }