/************************************************************************* * (c) Copyright 2016 Hewlett Packard Enterprise Development Company LP * * 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/. ************************************************************************/ package com.eucalyptus.ws.handlers; import java.util.regex.Pattern; import org.apache.axiom.om.OMElement; import org.apache.axiom.om.OMNamespace; import org.apache.log4j.Logger; import com.eucalyptus.binding.Binding; import com.eucalyptus.binding.BindingException; import com.eucalyptus.binding.BindingManager; import com.eucalyptus.component.ComponentId; import com.eucalyptus.context.Context; import com.eucalyptus.context.Contexts; import com.eucalyptus.records.Logs; import com.eucalyptus.util.Exceptions; import com.eucalyptus.ws.IoMessage; import com.eucalyptus.ws.WebServicesException; import edu.ucsb.eucalyptus.msgs.BaseMessage; import edu.ucsb.eucalyptus.msgs.EucalyptusErrorMessageType; import edu.ucsb.eucalyptus.msgs.ExceptionResponseType; import io.netty.channel.ChannelDuplexHandler; import io.netty.channel.ChannelHandler; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelPromise; import io.netty.handler.codec.http.FullHttpMessage; import io.netty.handler.codec.http.HttpResponse; import io.netty.handler.codec.http.HttpResponseStatus; /** * */ @ChannelHandler.Sharable public class IoBindingHandler extends ChannelDuplexHandler { private static Logger LOG = Logger.getLogger( BindingHandler.class ); private final IoBindingHandler.BindingHandlerContext context; public static abstract class BindingHandlerContext { public boolean namespaceMismatch( final String namespace ) { return false; } public void updateBindingForNamespace( final String namespace ) { } public boolean updateForDefaultBinding( final String namespace ) { return false; } public String getNamespace( ) { return null; } public abstract Binding getBinding( ); } public static class StaticBindingHandlerContext extends IoBindingHandler.BindingHandlerContext { private final Binding binding; public StaticBindingHandlerContext( final Binding binding ) { this.binding = binding; } @Override public Binding getBinding( ) { return binding; } } public static class DynamicBindingHandlerContext extends IoBindingHandler.BindingHandlerContext { private Binding binding; private String namespace; private final Binding defaultBinding; private final Pattern namespacePattern; private final Class<? extends ComponentId> component; public DynamicBindingHandlerContext( final Binding binding, final Pattern namespacePattern, final Class<? extends ComponentId> component ) { this.binding = binding; this.defaultBinding = binding; this.namespacePattern = namespacePattern; this.component = component; } @Override public boolean namespaceMismatch( final String namespace ) { return namespacePattern != null && !namespacePattern.matcher( namespace ).matches( ); } @Override public void updateBindingForNamespace( final String namespace ) { this.binding = BindingManager.getBinding( namespace, component ); } @Override public boolean updateForDefaultBinding( final String namespace ) { if ( this.defaultBinding != null ) { this.namespace = namespace; this.binding = this.defaultBinding; return true; } return false; } @Override public String getNamespace( ) { return namespace; } @Override public Binding getBinding( ) { return binding; } } /** * Create a static context which will always use the given binding. * * <p>A BindingHandler created with such a context can safely be cached</p> */ public static IoBindingHandler.BindingHandlerContext context( final Binding binding ) { return new IoBindingHandler.StaticBindingHandlerContext( binding ); } /** * Create a dynamic context which will always use the given binding. * * <p>A BindingHandler created with such a context should only be used once * and should not be cached.</p> */ public static IoBindingHandler.BindingHandlerContext context( final Binding binding, final Pattern namespacePattern, final Class<? extends ComponentId> component ) { return new IoBindingHandler.DynamicBindingHandlerContext( binding, namespacePattern, component ); } public IoBindingHandler( final IoBindingHandler.BindingHandlerContext context ) { this.context = context; } @Override public void channelRead( final ChannelHandlerContext ctx, final Object msgObj ) throws Exception { if ( msgObj instanceof IoMessage ) { IoMessage ioMessage = ( IoMessage ) msgObj; BaseMessage msg = null; Class msgType = null; String namespace = null; try { OMElement elem = ioMessage.getOmMessage( ); OMNamespace omNs = elem.getNamespace( ); namespace = omNs.getNamespaceURI( ); if ( context.namespaceMismatch( namespace ) ) { throw new WebServicesException( "Invalid request" ); } context.updateBindingForNamespace( namespace ); msgType = this.context.getBinding( ).getElementClass( ioMessage.getOmMessage( ).getLocalName( ) ); } catch ( BindingException ex ) { if ( this.context.updateForDefaultBinding( namespace ) ) { try { msgType = this.context.getBinding( ).getElementClass( ioMessage.getOmMessage( ).getLocalName( ) ); } catch ( Exception ex1 ) { throw new WebServicesException( "Failed to find binding for namespace: " + namespace + " due to: " + ex.getMessage( ), ex ); } } } catch ( Exception e1 ) { LOG.error( e1.getMessage( ) + " while attempting to bind: " + ioMessage.getOmMessage( ) ); Logs.extreme( ).error( ioMessage.getSoapEnvelope( ).toString( ), e1 ); if ( this.context.getBinding( ) == null ) { throw new WebServicesException( e1 ); } else { throw new WebServicesException( "Failed to find binding for namespace: " + namespace + " due to: " + e1.getMessage( ), e1 ); } } try { if ( ioMessage.isRequest( ) ) { if ( msgType != null ) { msg = ( BaseMessage ) this.context.getBinding( ).fromOM( ioMessage.getOmMessage( ), msgType ); } else { msg = ( BaseMessage ) this.context.getBinding( ).fromOM( ioMessage.getOmMessage( ) ); } } else { msg = ( BaseMessage ) this.context.getBinding( ).fromOM( ioMessage.getOmMessage( ) ); } } catch ( Exception e1 ) { try { msg = ( BaseMessage ) this.context.getBinding( ).fromOM( ioMessage.getOmMessage( ), this.context.getNamespace( ) ); } catch ( Exception ex ) { throw new WebServicesException( e1 ); } } // in case the base message has request ID in its correlation ID prefix, // we should reset the correlation ID using the request ID if ( ioMessage.getCorrelationId() != null && msg.getCorrelationId()!=null && msg.hasRequestId()) { try{ final Context context = Contexts.lookup(ioMessage.getCorrelationId()); // reset correlation ID msg.regardingRequestId(msg.getCorrelationId()); ioMessage.setCorrelationId(msg.getCorrelationId()); Contexts.update(context, ioMessage.getCorrelationId()); }catch(final Exception ex){ ; } } msg.setCorrelationId( ioMessage.getCorrelationId( ) ); ioMessage.setMessage( msg ); } super.channelRead( ctx, msgObj ); } @Override public void write( final ChannelHandlerContext ctx, final Object msgObj, final ChannelPromise promise ) throws Exception { if ( msgObj instanceof IoMessage ) { IoMessage ioMessage = ( IoMessage ) msgObj; OMElement omElem; if ( ioMessage.getMessage( ) instanceof EucalyptusErrorMessageType || ioMessage.getMessage( ) == null ) { return; } else if ( ioMessage.getMessage( ) instanceof ExceptionResponseType ) { final FullHttpMessage httpMessage = ioMessage.getHttpMessage( ); ExceptionResponseType msg = ( ExceptionResponseType ) ioMessage.getMessage( ); String createFaultDetails = Logs.isExtrrreeeme( ) ? Exceptions.string( msg.getException( ) ) : msg.getException( ).getMessage( ); omElem = Binding.createFault( msg.getRequestType( ), msg.getMessage( ), createFaultDetails ); if ( httpMessage instanceof HttpResponse ) { ( ( HttpResponse ) httpMessage ).setStatus( HttpResponseStatus.valueOf( msg.getHttpStatusCode( ) ) ); } } else { try { omElem = this.context.getBinding( ).toOM( ioMessage.getMessage( ), this.context.getNamespace( ) ); } catch ( BindingException ex ) { omElem = BindingManager.getDefaultBinding( ).toOM( ioMessage.getMessage( ) ); } catch ( Exception ex ) { Logs.exhaust( ).debug( ex, ex ); throw ex; } } ioMessage.setOmMessage( omElem ); } super.write( ctx, msgObj, promise ); } }