org.pepstock.jem.node.https.SubmitHandler.java Source code

Java tutorial

Introduction

Here is the source code for org.pepstock.jem.node.https.SubmitHandler.java

Source

/**
JEM, the BEE - Job Entry Manager, the Batch Execution Environment
Copyright (C) 2012-2015   Andrea "Stock" Stocchero
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, either version 3 of the License, or
any later version.
    
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 org.pepstock.jem.node.https;

import java.io.IOException;
import java.security.KeyException;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.Signature;
import java.security.SignatureException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;

import org.apache.commons.codec.DecoderException;
import org.apache.commons.codec.binary.Hex;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpEntity;
import org.apache.http.HttpEntityEnclosingRequest;
import org.apache.http.HttpException;
import org.apache.http.HttpRequest;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.MethodNotSupportedException;
import org.apache.http.NameValuePair;
import org.apache.http.ProtocolException;
import org.apache.http.client.utils.URLEncodedUtils;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.protocol.HttpContext;
import org.apache.http.protocol.HttpRequestHandler;
import org.apache.http.util.EntityUtils;
import org.pepstock.jem.Job;
import org.pepstock.jem.PreJob;
import org.pepstock.jem.commands.SubmitMessage;
import org.pepstock.jem.commands.SubmitParameters;
import org.pepstock.jem.commands.util.Factory;
import org.pepstock.jem.log.LogAppl;
import org.pepstock.jem.log.MessageException;
import org.pepstock.jem.node.Main;
import org.pepstock.jem.node.NodeMessage;
import org.pepstock.jem.node.Queues;
import org.pepstock.jem.node.security.keystore.KeyStoreUtil;
import org.pepstock.jem.node.security.keystore.KeyStoresInfo;
import org.pepstock.jem.node.security.keystore.KeysUtil;
import org.pepstock.jem.util.CharSet;
import org.pepstock.jem.util.Parser;

import com.hazelcast.core.IQueue;
import com.hazelcast.core.IdGenerator;

/**
 * Is the HTTP request handler when you use NO java clients to submit jobs.
 * It takes care about the information sent from the client.<br>
 * Accepts ONLY POST requests where in the body needs:<br>
 * <br>
 * FirstRow (delimiter is 'n'): <code>wait=wait&jcl=jcl&user=user&type=type&env=env&password=password&callback=localPort&printOutput=printOutput&signature=signature</code><br>
 * Rest of the body: JCL content to submit<br>
 * <br>
 * @author Andrea "Stock" Stocchero
 * @version 2.2
 */
public class SubmitHandler implements HttpRequestHandler {

    /**
     * URL action to use to submit job
     */
    public static final String DEFAULT_ACTION = "/submit";

    static final String JOB_SUBMIT_IP_ADDRESS_KEY = "jem-submit-client-address";

    private static final String JOB_SUBMIT_CALLBACK_PORT_KEY = "callback";

    private static final String USER_SIGNATURE_KEY = "signature";

    private static final String POST = "POST";

    private static final String RESPONSE_MIME_TYPE = "text/plain";

    private static final String DELIMITER = "\n";

    /*
     * (non-Javadoc)
     * 
     * @see org.apache.http.protocol.HttpRequestHandler#handle(org.apache.http.
     * HttpRequest, org.apache.http.HttpResponse,
     * org.apache.http.protocol.HttpContext)
     */
    @Override
    public void handle(final HttpRequest request, final HttpResponse response, final HttpContext context)
            throws HttpException, IOException {
        // extracts the host
        String host = context.getAttribute(JOB_SUBMIT_IP_ADDRESS_KEY).toString();
        // gets HTTP method (uses locale ENGLISH to be sure to have POST)
        String method = request.getRequestLine().getMethod().toUpperCase(Locale.ENGLISH);
        // if NOT post, exception!
        if (!method.equals(POST)) {
            LogAppl.getInstance().emit(NodeMessage.JEMC284W, method, host);
            throw new MethodNotSupportedException(
                    NodeMessage.JEMC284W.toMessage().getFormattedMessage(method, host));
        }
        // gets the URI or the request
        String target = request.getRequestLine().getUri();
        // if is not the same , accepts, exception!
        if (!target.equalsIgnoreCase(DEFAULT_ACTION)) {
            LogAppl.getInstance().emit(NodeMessage.JEMC285W, target, host);
            throw new MethodNotSupportedException(
                    NodeMessage.JEMC285W.toMessage().getFormattedMessage(target, host));
        }
        // checks the HTTP request
        if (request instanceof HttpEntityEnclosingRequest) {
            // gets the entity of the request
            HttpEntity entity = ((HttpEntityEnclosingRequest) request).getEntity();
            // gets the body in string format
            String result = EntityUtils.toString(entity, CharSet.DEFAULT);
            // reads the first line,
            // with all URL encoded variables  
            String vars = StringUtils.substringBefore(result, DELIMITER);
            // loads a map with all parms
            Map<String, String> parms = loadParametersMap(URLEncodedUtils.parse(vars, CharSet.DEFAULT));
            // gets the USER
            String user = parms.get(SubmitParameters.USER.getName());
            // if JEM is configured to have the Socket Interceptor on HC
            // the client MUST provide a SIGNATURE (using own private key) with
            // the user crypted inside
            if (Main.getHazelcastConfig().getNetworkConfig().getSocketInterceptorConfig().isEnabled()) {
                // checks if there is the signature
                if (parms.containsKey(USER_SIGNATURE_KEY)) {
                    // gets the signature in HEX format
                    String cryptedUserString = parms.get(USER_SIGNATURE_KEY);
                    // gets keys stores
                    KeyStoresInfo keyStoresInfo = KeyStoreUtil.getKeyStoresInfo();
                    try {
                        // extracts from the USER key store the PUBLIC KEY (upload by UI) for the user  
                        PublicKey publicKey = KeysUtil.getPublicKeyByAlias(keyStoresInfo.getUserKeystoreInfo(),
                                user);
                        // creates tne SIGNATURE verifying steps
                        Signature signature = Signature.getInstance("SHA256withRSA");
                        // sets public key
                        signature.initVerify(publicKey);
                        // sets content to check. It uses USER
                        signature.update(user.getBytes(CharSet.DEFAULT_CHARSET_NAME));
                        // checks if is verified
                        if (!signature.verify(Hex.decodeHex(cryptedUserString.toCharArray()))) {
                            // if not, log and EXCEPTION
                            LogAppl.getInstance().emit(NodeMessage.JEMC286W, user, host);
                            throw new HttpException(
                                    NodeMessage.JEMC286W.toMessage().getFormattedMessage(user, host));
                        }
                    } catch (MessageException e) {
                        LogAppl.getInstance().emit(NodeMessage.JEMC286W, user, host);
                        throw new ProtocolException(e.getMessage(), e);
                    } catch (KeyException e) {
                        throw new ProtocolException(e.getMessage(), e);
                    } catch (DecoderException e) {
                        throw new ProtocolException(e.getMessage(), e);
                    } catch (NoSuchAlgorithmException e) {
                        throw new ProtocolException(e.getMessage(), e);
                    } catch (SignatureException e) {
                        throw new ProtocolException(e.getMessage(), e);
                    }
                } else {
                    LogAppl.getInstance().emit(NodeMessage.JEMC287W, user, host);
                    // if here, the signature is missing
                    throw new HttpException(NodeMessage.JEMC287W.toMessage().getFormattedMessage(user, host));
                }
            }
            // gets JEM environemnt name and its passwrod
            String env = parms.get(SubmitParameters.ENV.getName());
            String password = parms.get(SubmitParameters.PASSWORD.getName());
            // checks if password and env are same, 
            // comparing with the HC configuration
            if (!Main.getHazelcastConfig().getGroupConfig().getName().equalsIgnoreCase(env)
                    || !Main.getHazelcastConfig().getGroupConfig().getPassword().equalsIgnoreCase(password)) {
                // if not equals, exception
                LogAppl.getInstance().emit(NodeMessage.JEMC288W, host);
                throw new HttpException(NodeMessage.JEMC288W.toMessage().getFormattedMessage(host));
            }

            // reads teh second row of the body, with the JCL
            String jcl = StringUtils.substringAfter(result, DELIMITER);

            // sets the entity to send back, submitting the job.
            // it returns the JOBID
            StringEntity resultEntity = new StringEntity(submit(jcl, user, host, parms),
                    ContentType.create(RESPONSE_MIME_TYPE, CharSet.DEFAULT_CHARSET_NAME));
            // sets STATUS code and entity 
            response.setStatusCode(HttpStatus.SC_OK);
            response.setEntity(resultEntity);
        } else {
            // if here, the request is not correct
            LogAppl.getInstance().emit(NodeMessage.JEMC284W, method, host);
            throw new MethodNotSupportedException(
                    NodeMessage.JEMC284W.toMessage().getFormattedMessage(method, host));
        }
    }

    /**
     * Loads all parms inserted in the body with all necessary info to submit the job.
     * @param params list of pair values with all parms
     * @return a map, key-value with the parameters
     */
    private Map<String, String> loadParametersMap(List<NameValuePair> params) {
        // scans the pairs and creates a map
        Map<String, String> map = new HashMap<String, String>();
        for (NameValuePair param : params) {
            map.put(param.getName(), param.getValue());
        }
        return map;
    }

    /**
     * Submits the JCL as JOB inside the JEM.
     * It uses all information passed by client.
     * 
     * @param content JCL content
     * @param type JCL type (Optional)
     * @param user user who submit the job
     * @param isWait if the client is waiting for the result of job
     * @throws HttpException if any errors occurs 
     */
    private String submit(String content, String user, String host, Map<String, String> parms)
            throws HttpException {
        // gets JCL type, OPTIONAL
        String type = parms.get(SubmitParameters.TYPE.getName());
        // gets filename of JCL, used as JOB NAME when the JCL factory
        // is not able to parse the JCL
        String fileName = parms.get(SubmitParameters.JCL.getName());
        // gets the HTTP port of the HTTP server used by the client 
        // to wait the answer, OPTIONAL
        String callbackPort = parms.get(JOB_SUBMIT_CALLBACK_PORT_KEY);
        // gets if the client is waiting for the end of the job 
        boolean isWait = Parser.parseBoolean(parms.get(SubmitParameters.WAIT.getName()), false);
        // gets if the client wants to have back the output of the job
        String printOutput = parms.get(SubmitParameters.PRINT_OUTPUT.getName());

        // creates a pre job using the JCL
        PreJob preJob = new PreJob();
        preJob.setJclContent(content);
        // sets JCL type
        if (type.trim().length() > 0) {
            preJob.setJclType(type);
        }
        // creates a job
        Job job = new Job();
        // sets user and group
        job.setUser(user);
        job.setOrgUnit(user);

        // uses file name as job name, if file name exists
        if (fileName != null) {
            job.setName(fileName);
        }

        // creates a job ID asking to Hazelcast for a new long value
        IdGenerator generator = Main.getHazelcast().getIdGenerator(Queues.JOB_ID_GENERATOR);
        long id = generator.newId();
        // Pads the value with "0"
        String jobId = Factory.createJobId(job, id);
        job.setId(jobId);
        // loads all line arguments (the -D properties).
        // could be useful to factories, listeners and during job execution to
        // job itself

        job.setInputArguments(Arrays.asList(host, callbackPort, printOutput, JOB_SUBMIT_IP_ADDRESS_KEY));
        // set nowait
        job.setNowait(!isWait);
        // loads prejob with job
        preJob.setJob(job);

        // puts the pre job in a queue for validating and moving to right QUEUE
        // (input if is correct, output if is wrong)
        IQueue<PreJob> jclCheckingQueue = Main.getHazelcast().getQueue(Queues.JCL_CHECKING_QUEUE);
        try {
            jclCheckingQueue.put(preJob);
        } catch (InterruptedException e) {
            throw new HttpException(SubmitMessage.JEMW003E.toMessage().getFormattedMessage(), e);
        }
        // returns job id
        return jobId;
    }
}