/*************************************************************************
* Copyright 2009-2015 Eucalyptus Systems, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see http://www.gnu.org/licenses/.
*
* Please contact Eucalyptus Systems, Inc., 6755 Hollister Ave., Goleta
* CA 93117, USA or visit http://www.eucalyptus.com/licenses/ if you need
* additional information or have any questions.
************************************************************************/
package com.eucalyptus.cloudformation.ws;
import com.eucalyptus.binding.Binding;
import com.eucalyptus.binding.HoldMe;
import com.eucalyptus.cloudformation.CloudFormationErrorResponse;
import com.eucalyptus.context.Context;
import com.eucalyptus.context.Contexts;
import com.eucalyptus.http.MappingHttpResponse;
import com.eucalyptus.records.Logs;
import com.eucalyptus.util.UnsafeByteArrayOutputStream;
import com.eucalyptus.ws.EucalyptusWebServiceException;
import com.eucalyptus.ws.protocol.BaseQueryBinding;
import com.eucalyptus.ws.protocol.OperationParameter;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectWriter;
import com.fasterxml.jackson.databind.PropertyNamingStrategy;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.module.SimpleSerializers;
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
import com.google.common.base.Strings;
import com.google.common.collect.Lists;
import edu.ucsb.eucalyptus.msgs.EucalyptusErrorMessageType;
import edu.ucsb.eucalyptus.msgs.ExceptionResponseType;
import org.apache.log4j.Logger;
import org.jboss.netty.buffer.ChannelBuffer;
import org.jboss.netty.buffer.ChannelBuffers;
import org.jboss.netty.channel.ChannelHandlerContext;
import org.jboss.netty.channel.MessageEvent;
import org.jboss.netty.handler.codec.http.HttpHeaders;
import org.jboss.netty.handler.codec.http.HttpResponseStatus;
import java.io.IOException;
import java.util.Date;
public class CloudFormationQueryBinding extends BaseQueryBinding<OperationParameter> {
// TODO: This is a best guess
static final String CLOUDFORMATION_NAMESPACE_PATTERN = "http://cloudformation.amazonaws.com/doc/%s/";
static final String CLOUDFORMATION_DEFAULT_VERSION = "2010-05-15";
static final String CLOUDFORMATION_DEFAULT_NAMESPACE = String.format( CLOUDFORMATION_NAMESPACE_PATTERN, CLOUDFORMATION_DEFAULT_VERSION );
private static final Logger LOG = Logger.getLogger(CloudFormationQueryBinding.class);
public CloudFormationQueryBinding() {
super( CLOUDFORMATION_NAMESPACE_PATTERN, CLOUDFORMATION_DEFAULT_VERSION, OperationParameter.Action );
}
@Override
public void outgoingMessage( ChannelHandlerContext ctx, MessageEvent event ) throws Exception {
Context context = Contexts.lookup(ctx.getChannel());
if (context == null || context.getHttpRequest() == null ||
context.getHttpRequest().getParameters() == null ||
!"JSON".equals(context.getHttpRequest().getParameters().get("ContentType"))) {
super.outgoingMessage(ctx, event);
} else {
if ( event.getMessage( ) instanceof MappingHttpResponse) {
MappingHttpResponse httpResponse = ( MappingHttpResponse ) event.getMessage( );
UnsafeByteArrayOutputStream byteOut = new UnsafeByteArrayOutputStream( 8192 );
HoldMe.canHas.lock( );
try {
if ( httpResponse.getMessage( ) == null ) {
/** TODO:GRZE: doing nothing here may be needed for streaming? double check... **/
// String response = Binding.createRestFault( this.requestType.get( ctx.getChannel( ) ), "Recieved an response from the service which has no content.", "" );
// byteOut.write( response.getBytes( ) );
// httpResponse.setStatus( HttpResponseStatus.INTERNAL_SERVER_ERROR );
} else if (httpResponse.getMessage( ) instanceof CloudFormationErrorResponse) {
jsonWriter().writeValue(byteOut, httpResponse.getMessage());
} else if ( httpResponse.getMessage( ) instanceof EucalyptusErrorMessageType) {
EucalyptusErrorMessageType errMsg = ( EucalyptusErrorMessageType ) httpResponse.getMessage( );
byteOut.write( Binding.createRestFault(errMsg.getSource(), errMsg.getMessage(), errMsg.getCorrelationId()).getBytes( ) );
httpResponse.setStatus( HttpResponseStatus.BAD_REQUEST );
} else if ( httpResponse.getMessage( ) instanceof ExceptionResponseType) {//handle error case specially
ExceptionResponseType msg = ( ExceptionResponseType ) httpResponse.getMessage( );
String detail = msg.getError( );
if( msg.getException( ) != null ) {
Logs.extreme().debug( msg, msg.getException( ) );
}
if ( msg.getException() instanceof EucalyptusWebServiceException) {
detail = msg.getCorrelationId( );
}
String response = Binding.createRestFault( msg.getRequestType( ), msg.getMessage( ), detail );
byteOut.write( response.getBytes( ) );
httpResponse.setStatus( msg.getHttpStatus( ) );
} else {//actually try to bind response
try {
// hack, assume type
String className = httpResponse.getMessage().getClass().getName();
// just get the last part
className = className.substring(className.lastIndexOf(".") + 1);
String messageType = className.replace("ResponseType", "Response");
// seriously cheating here
byteOut.write("{".getBytes());
byteOut.write(("\"" + messageType + "\" : ").getBytes());
jsonWriter().writeValue(byteOut, httpResponse.getMessage());
byteOut.write("}".getBytes());
} catch ( Exception e ) {
LOG.debug( e );
Logs.exhaust( ).error( e, e );
throw e;
}
}
ChannelBuffer buffer = ChannelBuffers.wrappedBuffer( byteOut.getBuffer( ), 0, byteOut.getCount( ) );
httpResponse.addHeader( HttpHeaders.Names.CONTENT_LENGTH, String.valueOf( buffer.readableBytes( ) ) );
httpResponse.addHeader( HttpHeaders.Names.CONTENT_TYPE, "application/json; charset=UTF-8" );
httpResponse.setContent( buffer );
} finally {
HoldMe.canHas.unlock( );
}
}
}
}
public static ObjectWriter jsonWriter( ) {
final ObjectMapper mapper = new ObjectMapper( );
mapper.setPropertyNamingStrategy( PropertyNamingStrategy.PASCAL_CASE_TO_CAMEL_CASE );
mapper.setSerializerFactory( mapper.getSerializerFactory( ).withAdditionalSerializers(
new SimpleSerializers( Lists.<JsonSerializer<?>>newArrayList( new EpochSecondsDateSerializer( ) ) )
) );
mapper.setSerializationInclusion( JsonInclude.Include.NON_NULL );
return mapper.writer( ).without( SerializationFeature.FAIL_ON_EMPTY_BEANS );
}
private static final class EpochSecondsDateSerializer extends StdSerializer<Date> {
public EpochSecondsDateSerializer( ) {
super( Date.class );
}
@Override
public void serialize( final Date date,
final JsonGenerator jsonGenerator,
final SerializerProvider serializerProvider ) throws IOException {
jsonGenerator.writeRawValue( String.valueOf( date.getTime( ) / 1000 ) + "." + Strings.padStart( Long.toString( date.getTime() % 1000 ), 3, '0' ) );
}
}
}