Package org.kie.services.client.api.command

Source Code of org.kie.services.client.api.command.AbstractRemoteCommandObject

package org.kie.services.client.api.command;

import static org.kie.services.client.serialization.SerializationConstants.DEPLOYMENT_ID_PROPERTY_NAME;
import static org.kie.services.client.serialization.SerializationConstants.SERIALIZATION_TYPE_PROPERTY_NAME;
import static org.kie.services.shared.ServicesVersion.VERSION;

import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.UUID;

import javax.jms.Connection;
import javax.jms.ConnectionFactory;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageConsumer;
import javax.jms.MessageProducer;
import javax.jms.Queue;
import javax.jms.Session;
import javax.jms.TextMessage;
import javax.ws.rs.core.MediaType;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;

import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.kie.api.command.Command;
import org.kie.api.task.model.Task;
import org.kie.remote.client.jaxb.AcceptedClientCommands;
import org.kie.remote.client.jaxb.JaxbCommandsRequest;
import org.kie.remote.client.jaxb.JaxbCommandsResponse;
import org.kie.remote.common.rest.KieRemoteHttpRequest;
import org.kie.remote.common.rest.KieRemoteHttpResponse;
import org.kie.remote.jaxb.gen.AddTaskCommand;
import org.kie.remote.jaxb.gen.AuditCommand;
import org.kie.remote.jaxb.gen.CompleteTaskCommand;
import org.kie.remote.jaxb.gen.CompleteWorkItemCommand;
import org.kie.remote.jaxb.gen.FailTaskCommand;
import org.kie.remote.jaxb.gen.InsertObjectCommand;
import org.kie.remote.jaxb.gen.JaxbStringObjectPair;
import org.kie.remote.jaxb.gen.JaxbStringObjectPairArray;
import org.kie.remote.jaxb.gen.SetGlobalCommand;
import org.kie.remote.jaxb.gen.SignalEventCommand;
import org.kie.remote.jaxb.gen.StartCorrelatedProcessCommand;
import org.kie.remote.jaxb.gen.StartProcessCommand;
import org.kie.remote.jaxb.gen.TaskCommand;
import org.kie.remote.jaxb.gen.UpdateCommand;
import org.kie.services.client.api.command.exception.MissingRequiredInfoException;
import org.kie.services.client.api.command.exception.RemoteApiException;
import org.kie.services.client.api.command.exception.RemoteCommunicationException;
import org.kie.services.client.api.command.exception.RemoteTaskException;
import org.kie.services.client.serialization.JaxbSerializationProvider;
import org.kie.services.client.serialization.SerializationException;
import org.kie.services.client.serialization.jaxb.impl.JaxbCommandResponse;
import org.kie.services.client.serialization.jaxb.rest.JaxbExceptionResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* This class contains the logic to interact with the REST or JMS api's. It is the basis for all of the remote interface instances.
*/
public abstract class AbstractRemoteCommandObject {

    protected static final Logger logger = LoggerFactory.getLogger(AbstractRemoteCommandObject.class);

    protected final RemoteConfiguration config;
    protected boolean isTaskService = false;

    AbstractRemoteCommandObject(RemoteConfiguration config) {
        this.config = config;
        if( config.isJms() && config.getResponseQueue() == null ) {
            throw new MissingRequiredInfoException("A Response queue is necessary in order to create a Remote JMS Client instance.");
        }
        this.config.initializeJaxbSerializationProvider();
    }

    protected void dispose() {
       
    }

    // Compatibility methods -----------------------------------------------------------------------------------------------------

    public void readExternal( ObjectInput arg0 ) throws IOException, ClassNotFoundException {
        String methodName = (new Throwable()).getStackTrace()[0].getMethodName();
        throw new UnsupportedOperationException(methodName + " is not supported on the JAXB " + Task.class.getSimpleName()
                + " implementation.");
    }

    public void writeExternal( ObjectOutput arg0 ) throws IOException {
        String methodName = (new Throwable()).getStackTrace()[0].getMethodName();
        throw new UnsupportedOperationException(methodName + " is not supported on the JAXB " + Task.class.getSimpleName()
                + " implementation.");
    }

    // Execute methods -----------------------------------------------------------------------------------------------------

    protected Object executeCommand( Command cmd ) {
        if( AcceptedClientCommands.isSendObjectParameterCommandClass(cmd.getClass()) ) {
            List<Object> extraClassInstanceList = new ArrayList<Object>();
            preprocessCommand(cmd, extraClassInstanceList);

            if( !extraClassInstanceList.isEmpty() ) {
                Set<Class<?>> extraJaxbClasses = new HashSet<Class<?>>();
                for( Object jaxbObject : extraClassInstanceList ) {
                    Class<?> jaxbClass = jaxbObject.getClass();
                    if( jaxbClass.isLocalClass() || jaxbClass.isAnonymousClass() ) {
                        throw new SerializationException(
                                "Only proper classes are allowed as parameters for the remote API: neither local nor anonymous classes are accepted: "
                                        + jaxbClass.getName());
                    }
                    extraJaxbClasses.add(jaxbClass);
                }
                if( config.addJaxbClasses(extraJaxbClasses) ) {
                    for( Class<?> extraClass : extraJaxbClasses ) {
                        logger.debug( "Adding {} to the JAXBContext instance in this client instance.", extraClass.getName() );
                    }
                    config.initializeJaxbSerializationProvider();
                }
            }
        }

        if( config.isRest() ) {
            return executeRestCommand(cmd);
        } else {
            return executeJmsCommand(cmd);
        }
    }

    void preprocessCommand( Object cmdObj, List<Object> extraClassInstanceList ) {
        if( cmdObj instanceof CompleteWorkItemCommand ) {
            addPossiblyNullObject(((CompleteWorkItemCommand) cmdObj).getResult(), extraClassInstanceList);
        } else if( cmdObj instanceof SignalEventCommand ) {
            addPossiblyNullObject(((SignalEventCommand) cmdObj).getEvent(), extraClassInstanceList);
        } else if( cmdObj instanceof StartCorrelatedProcessCommand ) {
            StartCorrelatedProcessCommand cmd = (StartCorrelatedProcessCommand) cmdObj;
            if( cmd.getData() != null ) {
                addPossiblyNullObject(cmd.getData().getDatas(), extraClassInstanceList);
            }
            addPossiblyNullObject(cmd.getParameter(), extraClassInstanceList);
        } else if( cmdObj instanceof StartProcessCommand ) {
            StartProcessCommand startProcCmd = (StartProcessCommand) cmdObj;
            if( startProcCmd.getData() != null ) {
                addPossiblyNullObject(startProcCmd.getData().getDatas(), extraClassInstanceList);
            }
            addPossiblyNullObject(((StartProcessCommand) cmdObj).getParameter(), extraClassInstanceList);
        } else if( cmdObj instanceof SetGlobalCommand ) {
            addPossiblyNullObject(((SetGlobalCommand) cmdObj).getObject(), extraClassInstanceList);
        } else if( cmdObj instanceof InsertObjectCommand ) {
            addPossiblyNullObject(((InsertObjectCommand) cmdObj).getObject(), extraClassInstanceList);
        } else if( cmdObj instanceof UpdateCommand ) {
            addPossiblyNullObject(((UpdateCommand) cmdObj).getObject(), extraClassInstanceList);
        } else if( cmdObj instanceof AddTaskCommand ) {
            addPossiblyNullObject(((AddTaskCommand) cmdObj).getParameter(), extraClassInstanceList);
        } else if( cmdObj instanceof CompleteTaskCommand ) {
            addPossiblyNullObject(((CompleteTaskCommand) cmdObj).getData(), extraClassInstanceList);
        } else if( cmdObj instanceof FailTaskCommand ) {
            addPossiblyNullObject(((FailTaskCommand) cmdObj).getData(), extraClassInstanceList);
        }
    }

    void addPossiblyNullObject( Object inputObject, List<Object> objectList ) {
        if( inputObject != null ) {
            if( inputObject instanceof List )  {
                objectList.addAll((List) inputObject);
            } else if( inputObject instanceof JaxbStringObjectPairArray ) {
                for( JaxbStringObjectPair stringObjectPair : ((JaxbStringObjectPairArray) inputObject).getItems() ) {
                    objectList.add(stringObjectPair.getValue());
                }
            } else
                objectList.add(inputObject);
            }
        }
    }

    private JaxbCommandsRequest prepareCommandRequest( Command command ) {
        if( config.getDeploymentId() == null && !(command instanceof TaskCommand || command instanceof AuditCommand) ) {
            throw new MissingRequiredInfoException("A deployment id is required when sending commands involving the KieSession.");
        }
        JaxbCommandsRequest req;
        if( command instanceof AuditCommand ) {
            req = new JaxbCommandsRequest(command);
        } else {
            req = new JaxbCommandsRequest(config.getDeploymentId(), command);
        }

        Long processInstanceId = findProcessInstanceId(command);
        if( processInstanceId == null ) {
            processInstanceId = config.getProcessInstanceId();
        }
        req.setProcessInstanceId(processInstanceId);
        req.setUser(config.getUserName());
        req.setVersion(VERSION);

        return req;
    }

    /**
     * Method to communicate with the backend via JMS.
     *
     * @param command The {@link Command} object to be executed.
     * @return The result of the {@link Command} object execution.
     */
    private Object executeJmsCommand( Command command ) {
        JaxbCommandsRequest req = prepareCommandRequest(command);
        String deploymentId = config.getDeploymentId();

        ConnectionFactory factory = config.getConnectionFactory();
        Queue sendQueue;

        boolean isTaskCommand = (command instanceof TaskCommand);
        if( isTaskCommand ) {
            sendQueue = config.getTaskQueue();
            if( ! config.getUseUssl() && ! config.getDoNotUseSsl() ) {
                throw new SecurityException("Task operation requests can only be sent via JMS if SSL is used.");
            }
        } else {
            sendQueue = config.getKsessionQueue();
        }
        Queue responseQueue = config.getResponseQueue();

        Connection connection = null;
        Session session = null;
        JaxbCommandsResponse cmdResponse = null;
        String corrId = UUID.randomUUID().toString();
        String selector = "JMSCorrelationID = '" + corrId + "'";
        try {

            // setup
            MessageProducer producer;
            MessageConsumer consumer;
            try {
                if( config.getPassword() != null ) {
                    connection = factory.createConnection(config.getUserName(), config.getPassword());
                } else {
                    connection = factory.createConnection();
                }
                session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);

                producer = session.createProducer(sendQueue);
                consumer = session.createConsumer(responseQueue, selector);

                connection.start();
            } catch( JMSException jmse ) {
                throw new RemoteCommunicationException("Unable to setup a JMS connection.", jmse);
            }

            // Create msg
            TextMessage textMsg;
            JaxbSerializationProvider serializationProvider;
            try {

                // serialize request
                serializationProvider = config.getJaxbSerializationProvider();
                String xmlStr = serializationProvider.serialize(req);
                textMsg = session.createTextMessage(xmlStr);
               
                // set properties
                // 1. corr id
                textMsg.setJMSCorrelationID(corrId);
                // 2. serialization info
                textMsg.setIntProperty(SERIALIZATION_TYPE_PROPERTY_NAME, config.getSerializationType());
                Set<Class<?>> extraJaxbClasses = config.getExtraJaxbClasses();
                if( !extraJaxbClasses.isEmpty() ) {
                    if( deploymentId == null ) {
                        throw new MissingRequiredInfoException(
                                "Deserialization of parameter classes requires a deployment id, which has not been configured.");
                    }
                    textMsg.setStringProperty(DEPLOYMENT_ID_PROPERTY_NAME, deploymentId);
                }
                // 3. user/pass for task operations
                String userName = config.getUserName();
                String password = config.getPassword();
                if( isTaskCommand ) {
                    if( userName == null ) {
                        throw new RemoteCommunicationException(
                                "A user name is required when sending task operation requests via JMS");
                    }
                    if( password == null ) {
                        throw new RemoteCommunicationException(
                                "A password is required when sending task operation requests via JMS");
                    }
                    textMsg.setStringProperty("username", userName);
                    textMsg.setStringProperty("password", password);
                }
                // 4. process instance id
            } catch( JMSException jmse ) {
                throw new RemoteCommunicationException("Unable to create and fill a JMS message.", jmse);
            } catch( SerializationException se ) {
                throw new RemoteCommunicationException("Unable to deserialze JMS message.", se.getCause());
            }

            // send
            try {
                producer.send(textMsg);
            } catch( JMSException jmse ) {
                throw new RemoteCommunicationException("Unable to send a JMS message.", jmse);
            }

            // receive
            Message response;
            try {
                response = consumer.receive(config.getTimeout() * 1000);
            } catch( JMSException jmse ) {
                throw new RemoteCommunicationException("Unable to receive or retrieve the JMS response.", jmse);
            }

            if( response == null ) {
                logger.warn("Response is empty");
                return null;
            }
            // extract response
            assert response != null: "Response is empty.";
            try {
                String xmlStr = ((TextMessage) response).getText();
                cmdResponse = (JaxbCommandsResponse) serializationProvider.deserialize(xmlStr);
            } catch( JMSException jmse ) {
                throw new RemoteCommunicationException("Unable to extract " + JaxbCommandsResponse.class.getSimpleName()
                        + " instance from JMS response.", jmse);
            } catch( SerializationException se ) {
                throw new RemoteCommunicationException("Unable to extract " + JaxbCommandsResponse.class.getSimpleName()
                        + " instance from JMS response.", se.getCause());
            }
            assert cmdResponse != null: "Jaxb Cmd Response was null!";
        } finally {
            if( connection != null ) {
                try {
                    connection.close();
                    if( session != null ) {
                        session.close();
                    }
                } catch( JMSException jmse ) {
                    logger.warn("Unable to close connection or session!", jmse);
                }
            }
        }
        String version = cmdResponse.getVersion();
        if( version == null ) {
            version = "pre-6.0.3";
        }
        if( !version.equals(VERSION) ) {
            logger.info("Response received from server version [{}] while client is version [{}]! This may cause problems.",
                    version, VERSION);
        }
        List<JaxbCommandResponse<?>> responses = cmdResponse.getResponses();
        if( responses.size() > 0 ) {
            JaxbCommandResponse<?> response = responses.get(0);
            if( response instanceof JaxbExceptionResponse ) {
                JaxbExceptionResponse exceptionResponse = (JaxbExceptionResponse) response;
                throw new RemoteApiException(exceptionResponse.getMessage());
            } else {
                return response.getResult();
            }
        } else {
            assert responses.size() == 0: "There should only be 1 response, " + "not " + responses.size()
                    + ", returned by a command!";
            return null;
        }
    }

    /**
     * Method to communicate with the backend via REST.
     *
     * @param command The {@link Command} object to be executed.
     * @return The result of the {@link Command} object execution.
     */
    private <T> T executeRestCommand( Command command ) {
        JaxbCommandsRequest jaxbRequest = prepareCommandRequest(command);
        KieRemoteHttpRequest httpRequest = config.createHttpRequest().relativeRequest("/execute");
       
        // necessary for deserialization
        httpRequest.header(JaxbSerializationProvider.EXECUTE_DEPLOYMENT_ID_HEADER, config.getDeploymentId());

        String jaxbRequestString = config.getJaxbSerializationProvider().serialize(jaxbRequest);
        if( logger.isTraceEnabled() ) {
            try {
                logger.trace("Sending {} via POST to {}", command.getClass().getSimpleName(), httpRequest.getUri());
            } catch( Exception e ) {
                // do nothing because this should never happen..
            }
            logger.trace("Serialized JaxbCommandsRequest:\n {}", jaxbRequestString);
        }

        KieRemoteHttpResponse httpResponse = null;
        try {
            logger.debug("Sending POST request with " + command.getClass().getSimpleName() + " to " + httpRequest.getUri());
            httpRequest.contentType(MediaType.APPLICATION_XML).body(jaxbRequestString);
            httpRequest.post();
            httpResponse = httpRequest.response();
        } catch( Exception e ) {
            httpRequest.disconnect();
            throw new RemoteCommunicationException("Unable to post request: " + e.getMessage(), e);
        }

        // Get response
        JaxbExceptionResponse exceptionResponse = null;
        JaxbCommandsResponse commandResponse = null;
        int responseStatus = httpResponse.code();
        try {
            String content = httpResponse.body();
            if( responseStatus < 300 ) {
                commandResponse = deserializeResponseContent(content, JaxbCommandsResponse.class);
            } else {
                String contentType = httpResponse.contentType();
                if( contentType.equals(MediaType.APPLICATION_XML) ) {
                    exceptionResponse = deserializeResponseContent(content, JaxbExceptionResponse.class);
                } else if( contentType.startsWith(MediaType.TEXT_HTML) ) {
                    exceptionResponse = new JaxbExceptionResponse();
                    Document doc = Jsoup.parse(content);
                    String body = doc.body().text();
                    exceptionResponse.setMessage(body);
                    exceptionResponse.setUrl(httpRequest.getUri().toString());
                    exceptionResponse.setStackTrace("");
                }
                else {
                    throw new RemoteCommunicationException("Unable to deserialize response with content type '" + contentType + "'");
                }
            }
        } catch( Exception e ) {
            logger.error("Unable to retrieve response content from request with status {}: {}", e.getMessage(), e);
            throw new RemoteCommunicationException("Unable to retrieve content from response!", e);
        } finally {
            httpRequest.disconnect();
        }
       
        if( commandResponse != null ) {
            List<JaxbCommandResponse<?>> responses = commandResponse.getResponses();
            if( responses.size() == 0 ) {
                return null;
            } else if( responses.size() == 1 ) {
                JaxbCommandResponse<?> responseObject = responses.get(0);
                if( responseObject instanceof JaxbExceptionResponse ) {
                    exceptionResponse = (JaxbExceptionResponse) responseObject;
                } else {
                    return (T) responseObject.getResult();
                }
            } else {
                throw new RemoteCommunicationException("Unexpected number of results from " + command.getClass().getSimpleName()
                        + ":" + responses.size() + " results instead of only 1");
            }
        }

        logger.error("Response with status {} returned.", responseStatus);
        // Process exception response
        switch ( responseStatus ) {
        case 409:
            throw new RemoteTaskException(exceptionResponse.getMessage() + ":\n" + exceptionResponse.getStackTrace());
        default:
            if( exceptionResponse != null ) {
                throw new RemoteApiException(exceptionResponse.getMessage() + ":\n" + exceptionResponse.getStackTrace());
            } else {
                throw new RemoteCommunicationException("Unable to communicate with remote API via URL "
                        + "'" + httpRequest.getUri().toString() + "'");
            }
        }
    }
   
    private <T> T deserializeResponseContent(String responseBody, Class<T> entityClass) {
       JaxbSerializationProvider jaxbSerializationProvider = config.getJaxbSerializationProvider();
       T responseEntity = null;
       try {
           responseEntity = (T) jaxbSerializationProvider.deserialize(responseBody);
       } catch( ClassCastException cce ) {
           throw new RemoteApiException("Unexpected entity in response body, expected " + entityClass.getName() + " instance.", cce);
       }
       return responseEntity;
    }

    // TODO: https://issues.jboss.org/browse/JBPM-4296
    private Long findProcessInstanceId( Object command ) {
        if( command instanceof AuditCommand ) {
            return null;
        }
        try {
            Field[] fields = command.getClass().getDeclaredFields();

            for( Field field : fields ) {
                field.setAccessible(true);
                if( field.isAnnotationPresent(XmlAttribute.class) ) {
                    String attributeName = field.getAnnotation(XmlAttribute.class).name();
                    if( "process-instance-id".equalsIgnoreCase(attributeName) ) {
                        return (Long) field.get(command);
                    }
                } else if( field.isAnnotationPresent(XmlElement.class) ) {
                    String elementName = field.getAnnotation(XmlElement.class).name();
                    if( "process-instance-id".equalsIgnoreCase(elementName) ) {
                        return (Long) field.get(command);
                    }
                } else if( field.getName().equals("processInstanceId") ) {
                    return (Long) field.get(command);
                }
            }
        } catch( Exception e ) {
            logger.debug("Unable to find process instance id on command {} due to {}", command, e.getMessage());
        }

        return null;
    }

    // Command Object helper methods --

    protected static <T> T getField( String fieldName, Class objClass, Object obj, Class<T> fieldClass ) throws Exception {
        Field field = objClass.getDeclaredField(fieldName);
        field.setAccessible(true);
        return (T) field.get(obj);
    }

    public static <T> T unsupported( Class<?> realClass, Class<T> returnClass ) {
        String methodName = (new Throwable()).getStackTrace()[1].getMethodName();
        throw new UnsupportedOperationException("The " + realClass.getSimpleName() + "." + methodName + "(..) method is not supported on the Remote Client instance.");
    }

}
TOP

Related Classes of org.kie.services.client.api.command.AbstractRemoteCommandObject

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.