/************************************************************************* * (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.Iterator; import java.util.List; import java.util.function.Supplier; import javax.annotation.Nullable; import javax.xml.soap.SOAPConstants; import org.apache.axiom.om.OMElement; import org.apache.axiom.soap.SOAPEnvelope; import org.apache.axiom.soap.SOAPFactory; import org.apache.axiom.soap.SOAPFault; import org.apache.axiom.soap.SOAPHeader; import org.apache.axiom.soap.SOAPHeaderBlock; import com.eucalyptus.binding.Binding; import com.eucalyptus.binding.HoldMe; import com.eucalyptus.records.Logs; import com.eucalyptus.util.Exceptions; import com.eucalyptus.util.Pair; import com.eucalyptus.ws.EucalyptusRemoteFault; import com.eucalyptus.ws.IoMessage; import com.google.common.collect.Lists; 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.HttpMessage; import io.netty.handler.codec.http.HttpResponse; import io.netty.handler.codec.http.HttpResponseStatus; import javaslang.control.Option; /** * SOAP object model handler */ @SuppressWarnings( "ThrowableResultOfMethodCallIgnored" ) @ChannelHandler.Sharable public class IoSoapHandler extends ChannelDuplexHandler { /** * Handle incoming soap model */ @Override public void channelRead( final ChannelHandlerContext ctx, final Object msg ) throws Exception { if ( msg instanceof IoMessage ) { final IoMessage ioMessage = IoMessage.class.cast( msg ); final SOAPEnvelope env = ioMessage.getSoapEnvelope( ); if ( env != null && !env.hasFault( ) ) { ioMessage.setOmMessage( env.getBody( ).getFirstElement( ) ); } else { final Supplier<Integer> statusCodeSupplier = () -> getStatus( ioMessage ); perhapsFault( env, statusCodeSupplier ); } } super.channelRead( ctx, msg ); } /** * Create outbound soap model */ @Override public void write( final ChannelHandlerContext ctx, final Object msg, final ChannelPromise promise ) throws Exception { if ( msg instanceof IoMessage ) { final IoMessage ioMessage = IoMessage.class.cast( msg ); final HttpMessage httpMessage = ioMessage.getHttpMessage( ); final Option<Pair<SOAPEnvelope,Integer>> soapEnvelopeOption = IoSoapHandler.perhapsBuildFault( ioMessage.getMessage( ) ); if ( soapEnvelopeOption.isDefined( ) ) { ioMessage.setSoapEnvelope( soapEnvelopeOption.get( ).getLeft( ) ); if ( httpMessage instanceof HttpResponse ) { ( ( HttpResponse ) httpMessage ).setStatus( HttpResponseStatus.valueOf( soapEnvelopeOption.get( ).getRight( ) ) ); } } else { ioMessage.setSoapEnvelope( buildSoapEnvelope( ioMessage.getOmMessage() ) ); } } super.write( ctx, msg, promise ); } @Nullable private static Integer getStatus( final IoMessage message ) { return message.getHttpMessage( ) instanceof HttpResponse ? ((HttpResponse) message.getHttpMessage( )).getStatus( ).code( ) : null; } static SOAPEnvelope buildSoapEnvelope( final OMElement body ) { final SOAPFactory factory = HoldMe.getOMSOAP11Factory( ); final SOAPEnvelope soapEnvelope = factory.createSOAPEnvelope( factory.createOMNamespace( SOAPConstants.URI_NS_SOAP_1_1_ENVELOPE, "" ) ); factory.createSOAPHeader( soapEnvelope ); factory.createSOAPBody( soapEnvelope ).addChild( body ); return soapEnvelope; } static Option<Pair<SOAPEnvelope,Integer>> perhapsBuildFault( final Object msg ) { Option<Pair<SOAPEnvelope,Integer>> soapEnvelopeOption = Option.none( ); if ( msg instanceof EucalyptusErrorMessageType ) { EucalyptusErrorMessageType errMsg = ( EucalyptusErrorMessageType ) msg; soapEnvelopeOption = Option.some( Pair.of( Binding.createFault( errMsg.getSource( ), errMsg.getMessage( ), errMsg.getStatusMessage( ) ), org.jboss.netty.handler.codec.http.HttpResponseStatus.BAD_REQUEST.getCode( ) ) ); } else if ( msg instanceof ExceptionResponseType ) { ExceptionResponseType errMsg = ( ExceptionResponseType ) msg; String createFaultDetails = Logs.isExtrrreeeme( ) ? Exceptions.string( errMsg.getException( ) ) : errMsg.getException( ).getMessage( ); soapEnvelopeOption = Option.some( Pair.of( Binding.createFault( errMsg.getRequestType( ), errMsg.getMessage( ), createFaultDetails ), errMsg.getHttpStatus( ).getCode( ) ) ); } return soapEnvelopeOption; } static void perhapsFault( final SOAPEnvelope env, final Supplier<Integer> statusCodeSupplier ) throws EucalyptusRemoteFault { final SOAPHeader header = env.getHeader( ); String action = "ProblemAction"; String relatesTo = "RelatesTo"; if ( header != null ) { final List<SOAPHeaderBlock> headers = Lists.newArrayList( header.examineAllHeaderBlocks( ) ); // :: try to get the fault info from the soap header -- hello there? ::// for ( final SOAPHeaderBlock headerBlock : headers ) { if ( action.equals( headerBlock.getLocalName( ) ) ) { action = headerBlock.getText( ); } else if ( relatesTo.equals( headerBlock.getLocalName( ) ) ) { relatesTo = headerBlock.getText( ); } } } //faults don't need to have a header. // :: process the real fault ::// final SOAPFault fault = env.getBody( ).getFault( ); if ( fault != null ) { String faultReason = ""; final Iterator children = fault.getChildElements( ); while ( children.hasNext( ) ) { final OMElement child = ( OMElement ) children.next( ); faultReason += child.getText( ); } final String faultCode = fault.getCode( ).getText( ); faultReason = faultReason.replaceAll( faultCode, "" ); final String faultDetail = fault.getDetail( ).getText( ); throw new EucalyptusRemoteFault( action, relatesTo, faultCode, faultReason, faultDetail, statusCodeSupplier.get( ) ); } } }