vitkin.sfdc.mojo.wsdl.WsdlDownloadlMojo.java Source code

Java tutorial

Introduction

Here is the source code for vitkin.sfdc.mojo.wsdl.WsdlDownloadlMojo.java

Source

/*
 * #%L
 * SFDC WSDL Maven Plugin
 * %%
 * Copyright (C) 2013 Victor Itkin
 * %%
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * #L%
 */
package vitkin.sfdc.mojo.wsdl;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.PrintWriter;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.List;

import com.thoughtworks.xstream.XStream;

import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.logging.Log;

import org.apache.maven.plugins.annotations.InstantiationStrategy;
import org.apache.maven.plugins.annotations.LifecyclePhase;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;

import org.apache.http.Consts;
import org.apache.http.Header;
import org.apache.http.HeaderElement;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.ParseException;
import org.apache.http.client.CookieStore;
import org.apache.http.client.HttpClient;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.params.CookiePolicy;
import org.apache.http.client.params.HttpClientParams;
import org.apache.http.cookie.Cookie;
import org.apache.http.impl.client.BasicCookieStore;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.impl.cookie.BasicClientCookie;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.params.HttpConnectionParams;
import org.apache.http.params.HttpParams;

/**
 * Goal which downloads a WSDL.<br/>
 * That goals by default binds to the 'initialize' lifecycle phase.
 *
 * @author Victor Itkin
 */
@Mojo(name = "download", defaultPhase = LifecyclePhase.INITIALIZE, instantiationStrategy = InstantiationStrategy.POOLABLE, requiresOnline = true)
public class WsdlDownloadlMojo extends AbstractMojo {
    /**
     * Suffix for cookie store files.
     */
    private static final String COOKIES_SUFFIX = "-cookies.xml";

    /**
     * Development/Production Authorization server URL.
     */
    private static final String DEV_PROD_AUTHORIZATION_SERVER = "https://login.salesforce.com";

    /**
     * Sandbox Authorization server URL.
     */
    private static final String SANDBOX_AUTHORIZATION_SERVER = "https://test.salesforce.com";

    private static final String DEFAULT_OUTPUT_DIRECTORY_VALUE = "${basedir}/src/main/wsdl";

    private static final File DEFAULT_OUTPUT_DIRECTORY = new File(DEFAULT_OUTPUT_DIRECTORY_VALUE);

    private static final String DEFAULT_COOKIES_DIRECTORY_VALUE = "${basedir}/cookies";

    private static final File DEFAULT_COOKIES_DIRECTORY = new File(DEFAULT_COOKIES_DIRECTORY_VALUE);

    private static final String DEFAULT_WSDL_URI_VALUE = "soap/wsdl.jsp";

    private static final URI DEFAULT_WSDL_URI;

    static {
        URI uri = null;

        try {
            uri = new URI(DEFAULT_WSDL_URI_VALUE);
        } catch (URISyntaxException ex) {
        }

        DEFAULT_WSDL_URI = uri;
    }

    /**
     * Location of the file.<br/>
     * The default location is '${basedir}/src/main/wsdl'.
     */
    @Parameter(property = "sfdc.wsdl.outputDirectory", defaultValue = DEFAULT_OUTPUT_DIRECTORY_VALUE, required = true)
    private File outputDirectory;

    /**
     * Name of the file to override the default one provided by the resource
     * server by the time of the download. (e.g. 'partner.wsdl')
     */
    @Parameter(property = "sfdc.wsdl.filename")
    private String filename;

    /**
     * Path to the directory where the cookies are stored.<br/>
     * The default path is '${basedir}/cookies'.
     */
    @Parameter(property = "sfdc.cookiesDirectory", defaultValue = DEFAULT_COOKIES_DIRECTORY_VALUE, required = true)
    private File cookiesDirectory;

    /**
     * Set to true if the authorization server to use is the sandbox one
     * (https://test.salesforce.com).<br/>
     * Default to false for production/development authorization server
     * (https://login.salesforce.com).
     */
    @Parameter(property = "sfdc.useSandbox")
    private boolean useSandbox;

    /**
     * Relative URI of the WSDL.<br/>
     * The default URI is 'soap/wsdl.jsp'.<br/>
     * For an Apex class based Web Service it can be
     * 'services/wsdl/class/MyApexClassName'.
     */
    @Parameter(property = "sfdc.wsdl.uri", defaultValue = DEFAULT_WSDL_URI_VALUE, required = true)
    private URI wsdlUri;

    /**
     * Email address based username to connect to the salesforce.com organization.
     */
    @Parameter(property = "sfdc.username", required = true)
    private String username;

    /**
     * Password (without the security token) to connect to the salesforce.com
     * organization.
     */
    @Parameter(property = "sfdc.password", required = true)
    private String password;

    /**
     * Used for saving/loading cookie stores as XML.
     */
    private final XStream xstream = new XStream();

    /**
     * Map of HTTP clients used by the instance.
     */
    private final HashMap<String, InnerHttpClient> clients = new HashMap<String, InnerHttpClient>();

    /**
     * Default constructor.
     */
    public WsdlDownloadlMojo() {
        xstream.alias("store", BasicCookieStore.class);
        xstream.alias("cookie", BasicClientCookie.class);
    }

    /**
     * Execute the 'download' goal.
     *
     * @throws MojoExecutionException
     */
    @Override
    public void execute() throws MojoExecutionException {
        final InnerHttpClient client = getHttpClient();

        String resourceServer = getResourceServer(client);

        try {
            if (resourceServer == null) {
                final String redirectUrl = logIn(client);
                resourceServer = redirect(client, redirectUrl);
            }

            downloadWsdl(client, resourceServer);
        } finally {
            client.saveCookieStore();
            reset();
        }
    }

    /**
     * Download the WSDL.
     *
     * @param client         HTTP client.
     * @param resourceServer URL of the resource server from where to download.
     *
     * @throws MojoExecutionException
     */
    private void downloadWsdl(final HttpClient client, final String resourceServer) throws MojoExecutionException {
        final Log logger = getLog();
        final String baseUrl = resourceServer + '/' + wsdlUri;

        logger.info("Getting WSDL from " + baseUrl);

        final HttpGet wsdlRequest = new HttpGet(baseUrl);

        try {
            final HttpResponse response = client.execute(wsdlRequest);

            final int code = response.getStatusLine().getStatusCode();

            if (code != HttpStatus.SC_OK) {
                throw new MojoExecutionException("Failed getting the WSDL! Got HTTP Code " + code);
            }

            PrintWriter pw = null;
            BufferedReader br = null;

            try {
                if (filename == null) {
                    logger.info("No filename defined. Using default one from server...");

                    final Header header = response.getFirstHeader("Content-Disposition");

                    if (header == null) {
                        if (logger.isDebugEnabled()) {
                            debugResponse(response);
                        }

                        throw new MojoExecutionException("Couldn't get filename from server!");
                    }

                    final HeaderElement[] elements = header.getElements();

                    filename = elements[0].getParameterByName("filename").getValue();
                }

                if (!outputDirectory.exists()) {
                    outputDirectory.mkdirs();
                }

                final File wsdlFile = new File(outputDirectory, filename);

                logger.info("Saving WSDL to '" + wsdlFile + "'...");

                br = new BufferedReader(new InputStreamReader(response.getEntity().getContent()));

                pw = new PrintWriter(wsdlFile);

                for (String line = br.readLine(); line != null; line = br.readLine()) {
                    pw.println(line);
                }
            } catch (IOException ex) {
                throw new MojoExecutionException("Failed saving the WSDL!", ex);
            } catch (IllegalStateException ex) {
                throw new MojoExecutionException("Failed saving the WSDL!", ex);
            } catch (ParseException ex) {
                throw new MojoExecutionException("Failed saving the WSDL!", ex);
            } finally {
                try {
                    if (br != null) {
                        br.close();
                    }

                    if (pw != null) {
                        pw.close();
                    }
                } catch (IOException ex) {
                    logger.warn(ex.getMessage(), ex);
                }
            }
        } catch (IOException ex) {
            throw new MojoExecutionException("Cannot get WSDL!", ex);
        }
    }

    /**
     * Get an existing HTTP client for the current instance or create a new
     * one.<br/>
     * When creating a new HTTP client, try to load previously saved cookies.
     *
     * @return HTTP client for the current environment and username.
     */
    private InnerHttpClient getHttpClient() {
        final String env = useSandbox ? "sanbox" : "dev-prod";
        final String clientId = env + '/' + username;

        InnerHttpClient client = clients.get(clientId);

        if (client == null) {
            client = new InnerHttpClient(env);

            final HttpParams params = client.getParams();

            HttpClientParams.setCookiePolicy(params, CookiePolicy.NETSCAPE);
            params.setParameter(HttpConnectionParams.CONNECTION_TIMEOUT, 30000);

            clients.put(clientId, client);
        }

        return client;
    }

    /**
     * Tell if a session is older than an hour.
     *
     * @param client HTTP client for which to attempt deducing if the session has
     *               expired.
     *
     * @return String The resource server for the session if the session hasn't
     *         expired. Null otherwise.
     */
    private String getResourceServer(InnerHttpClient client) {
        Log logger = getLog();

        // Cookie 'oid' expiry date is supposed to be in 2 years in the future from
        // the date of creation of the cookie.
        final Calendar cal = GregorianCalendar.getInstance();
        cal.add(Calendar.YEAR, 2);
        cal.add(Calendar.HOUR, -1);

        final Date futureDate = cal.getTime();

        if (logger.isDebugEnabled()) {
            logger.debug("Future date: " + futureDate);
        }

        String resourceServer = null;

        for (Cookie cookie : client.getCookieStore().getCookies()) {
            final String name = cookie.getName();

            if ("oid".equals(name)) {
                final Date expiryDate = cookie.getExpiryDate();

                if (logger.isDebugEnabled()) {
                    logger.debug("Expiry date: " + expiryDate);
                }

                if (futureDate.before(expiryDate)) {
                    resourceServer = "https://" + cookie.getDomain();
                }

                break;
            }
        }

        return resourceServer;
    }

    /**
     * Log in to a Salesforce authorization server.
     *
     * @param client HTTP client.
     *
     * @return Redirection URL if successful.
     *
     * @throws MojoExecutionException
     */
    private String logIn(final DefaultHttpClient client) throws MojoExecutionException {
        final Log logger = getLog();

        final String authorizationServer = useSandbox ? SANDBOX_AUTHORIZATION_SERVER
                : DEV_PROD_AUTHORIZATION_SERVER;

        logger.info("Logging in as " + username + " at authorization server at " + authorizationServer + "...");

        // Send a post request to the login URI.
        final HttpPost loginRequest = new HttpPost(authorizationServer);

        // The request body must contain these 2 values.
        final List<BasicNameValuePair> parametersBody = new ArrayList<BasicNameValuePair>();

        parametersBody.add(new BasicNameValuePair("un", username));
        parametersBody.add(new BasicNameValuePair("pw", password));

        loginRequest.setEntity(new UrlEncodedFormEntity(parametersBody, Consts.UTF_8));

        final String location;

        try {
            final HttpResponse response = client.execute(loginRequest);

            if (logger.isErrorEnabled()) {
                debugResponse(response);
            } else {
                // Low level resources should be released before initiating a new request
                HttpEntity entity = response.getEntity();

                if (entity != null) {
                    // Do not need the rest
                    loginRequest.abort();
                }
            }

            final int code = response.getStatusLine().getStatusCode();

            if (code != HttpStatus.SC_MOVED_TEMPORARILY) {
                // No redirection. That means we're not logged in.
                throw new MojoExecutionException(
                        "Cannot log in! Wrong credentials or need for activation for the current IP.");
            }

            location = response.getFirstHeader("Location").getValue();

        } catch (IOException ex) {
            throw new MojoExecutionException("Cannot log in!", ex);
        }

        return location;
    }

    /**
     * Load cookies from the previous instance execution.
     *
     * @param env Salesforce environment. Either 'sandbox' or 'dev-prod'.
     *
     * @return The loaded cookie store or null if it can't load it.
     */
    private CookieStore loadCookies(final String env) {
        final Log logger = getLog();
        final File cookieStorePath = new File(cookiesDirectory, env);

        CookieStore cookieStore = null;

        if (cookieStorePath.exists()) {
            final File cookieStoreFile = new File(cookieStorePath, username + COOKIES_SUFFIX);

            if (cookieStoreFile.exists()) {
                logger.info("Found cookies from previous execution. Loading from '" + cookieStoreFile + "'...");

                ObjectInputStream ois = null;

                try {
                    ois = xstream
                            .createObjectInputStream(new BufferedInputStream(new FileInputStream(cookieStoreFile)));

                    cookieStore = (BasicCookieStore) ois.readObject();
                } catch (IOException ex) {
                    logger.warn("Failed reading cookies from previous run!", ex);
                } catch (ClassNotFoundException ex) {
                    logger.warn("Failed loading cookies from previous run!", ex);
                } finally {
                    if (ois != null) {
                        try {
                            ois.close();
                        } catch (IOException ex) {
                            logger.warn(ex.getMessage(), ex);
                        }
                    }
                }
            }
        }

        return cookieStore;
    }

    /**
     * Display the content of the HTTP response.<br/>
     * <b>CAUTION:</b> Calling this method will consume the response content
     * InputStream!
     *
     * @param response The HTTP response of which to display the content.
     */
    private void debugResponse(final HttpResponse response) {
        final Log logger = getLog();

        logger.debug("Displaying content:");

        BufferedReader br = null;

        try {
            br = new BufferedReader(new InputStreamReader(response.getEntity().getContent()));

            for (String line = br.readLine(); line != null; line = br.readLine()) {
                logger.debug(line);
            }
        } catch (IOException ex) {
            logger.error("Failed displaying content!", ex);
        } finally {
            if (br != null) {
                try {
                    br.close();
                } catch (IOException ex) {
                    logger.warn(ex.getMessage(), ex);
                }
            }
        }
    }

    /**
     * Reset all parameters to their default values.
     */
    private void reset() {
        cookiesDirectory = DEFAULT_COOKIES_DIRECTORY;
        filename = null;
        outputDirectory = DEFAULT_OUTPUT_DIRECTORY;
        password = null;
        useSandbox = false;
        username = null;
        wsdlUri = DEFAULT_WSDL_URI;
    }

    /**
     * Execute an HTTP redirection to set additional cookies related to the
     * resource server session.
     *
     * @param client      The HTTP client to use for that redirection.
     * @param redirectUrl The URL to redirect to.
     *
     * @return The base URL of the resource server.
     *
     * @throws MojoExecutionException
     */
    private String redirect(HttpClient client, String redirectUrl) throws MojoExecutionException {
        final Log logger = getLog();

        final String resourceServer = redirectUrl.substring(0, redirectUrl.indexOf('/', 8));

        if (redirectUrl.startsWith(resourceServer + "/_nc_external/identity/ic/ICRequired")) {
            throw new MojoExecutionException(
                    "Need activation. Open the below URL with a browser from the same public IP:\n" + redirectUrl);
        }

        logger.info("Accessing resource server at " + redirectUrl);

        final HttpGet redirectRequest = new HttpGet(redirectUrl);

        try {
            final HttpResponse response = client.execute(redirectRequest);

            if (logger.isErrorEnabled()) {
                debugResponse(response);
            } else {
                final HttpEntity entity = response.getEntity();

                if (entity != null) {
                    // Do not need the rest
                    redirectRequest.abort();
                }
            }
        } catch (IOException ex) {
            throw new MojoExecutionException("Cannot access resource server!", ex);
        }

        return resourceServer;
    }

    /**
     * Save the cookie store for the given Salesforce environment and current
     * username.
     *
     * @param env         Salesforce environment. Either 'sandbox' or 'dev-prod'.
     * @param cookieStore The cookie store to save.
     */
    private void saveCookies(final String env, final CookieStore cookieStore) {
        final Log logger = getLog();
        final File cookieStorePath = new File(cookiesDirectory, env);

        if (!cookieStorePath.exists()) {
            cookieStorePath.mkdirs();
        }

        final File cookieStoreFile = new File(cookieStorePath, username + COOKIES_SUFFIX);

        logger.info("Saving cookies to '" + cookieStoreFile + "'...");

        ObjectOutputStream oos = null;

        try {
            oos = xstream.createObjectOutputStream(new BufferedOutputStream(new FileOutputStream(cookieStoreFile)));

            oos.writeObject(cookieStore);
        } catch (IOException ex) {
            logger.warn("Failed saving cookies from current run!", ex);
        } finally {
            if (oos != null) {
                try {
                    oos.close();
                } catch (IOException ex) {
                    logger.warn(ex.getMessage(), ex);
                }
            }
        }
    }

    /**
     * Inner HTTP client.
     */
    private class InnerHttpClient extends DefaultHttpClient {
        private final String env;

        /**
         * Initialize the instance.
         *
         * @param env Salesforce environment for that instance.
         */
        private InnerHttpClient(String env) {
            this.env = env;
        }

        /**
         * Create a cookie store.
         *
         * @return The new cookie store.
         */
        @Override
        protected CookieStore createCookieStore() {
            CookieStore cookieStore = loadCookies(env);

            if (cookieStore == null) {
                cookieStore = super.createCookieStore();
            }

            return cookieStore;
        }

        /**
         * Save the cookie store.
         *
         * @throws Throwable
         */
        private void saveCookieStore() {
            saveCookies(env, getCookieStore());
        }
    }
}