org.eclipse.edt.ide.deployment.services.internal.testserver.ConfigServlet.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.edt.ide.deployment.services.internal.testserver.ConfigServlet.java

Source

/*******************************************************************************
 * Copyright  2011, 2013 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 * IBM Corporation - initial API and implementation
 *
 *******************************************************************************/
package org.eclipse.edt.ide.deployment.services.internal.testserver;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;
import java.util.Set;
import java.util.StringTokenizer;

import javax.naming.InitialContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.sql.DataSource;

import org.eclipse.edt.ide.deployment.core.model.DeploymentDesc;
import org.eclipse.edt.ide.deployment.core.model.Restservice;
import org.eclipse.edt.ide.testserver.LogLevel;
import org.eclipse.edt.ide.testserver.TestServer;
import org.eclipse.edt.javart.Constants;
import org.eclipse.edt.javart.JEERunUnit;
import org.eclipse.edt.javart.ide.IDEBindingResourceProcessor;
import org.eclipse.edt.javart.ide.IDEBindingResourceProcessor.SQLConnectionInfo;
import org.eclipse.edt.javart.resources.StartupInfo;
import org.eclipse.edt.javart.resources.egldd.RuntimeDeploymentDesc;
import org.eclipse.edt.javart.resources.egldd.SQLDatabaseBinding;
import org.eclipse.edt.javart.services.servlet.rest.rpc.PreviewServiceServlet;
import org.eclipse.jetty.plus.jndi.NamingEntry;
import org.eclipse.jetty.plus.webapp.PlusDescriptorProcessor;
import org.eclipse.jetty.xml.XmlConfiguration;

/**
 * Servlet that handles configuration of the test server, providing support for JNDI, service mappings,
 * and resource bindings.
 */
public class ConfigServlet extends HttpServlet {

    private static final long serialVersionUID = Constants.SERIAL_VERSION_UID;

    /**
     * The path of the servlet running inside the context root.
     */
    public static final String SERVLET_PATH = "config"; //$NON-NLS-1$

    /**
     * The name of the argument for setting the default deployment descriptor name.
     */
    public static final String ARG_DEFAULT_DD = "defaultDD"; //$NON-NLS-1$

    /**
     * The name of the argument for passing deployment descriptor additions.
     */
    public static final String ARG_DD_ADDED = "ddAdded"; //$NON-NLS-1$

    /**
     * The name of the argument for passing deployment descriptor removals.
     */
    public static final String ARG_DD_REMOVED = "ddRemoved"; //$NON-NLS-1$

    /**
     * The name of the argument for setting the order of the deployment descriptors.
     */
    public static final String ARG_ORDERED_DDS = "orderedDDs"; //$NON-NLS-1$

    /**
     * The delimeter used between mappings.
     */
    public static final String MAPPING_ARG_DELIMETER = "|"; //$NON-NLS-1$

    /**
     * The delimeter used between components of a mapping.
     */
    public static final String MAPPING_DELIMETER = ";"; //$NON-NLS-1$

    /**
     * The servlet which is being updated.
     */
    private final PreviewServiceServlet previewServlet;

    /**
     * The binding processor.
     */
    private final IDEBindingResourceProcessor bindingProcessor;

    /**
     * The test server.
     */
    private final TestServer server;

    /**
     * ID for the next data source that gets created.
     */
    private int nextDataSourceId;

    /**
     * List of DD names in the order that they take precedence.
     */
    private List<String> orderedDDNames;

    /**
     * List of resource-ref names added to the server.
     */
    private List<String> addedResourceRefs;

    /**
     * List of data source names added to the server.
     */
    private List<NamingEntry> addedDataSources;

    /**
     * Class to use for connection pooling, possibly null.
     */
    private final String basicDataSourceClassName;

    /**
     * Constructor.
     */
    public ConfigServlet(PreviewServiceServlet previewServlet, IDEBindingResourceProcessor bindingProcessor,
            TestServer server) {
        this.previewServlet = previewServlet;
        this.bindingProcessor = bindingProcessor;
        this.server = server;
        this.orderedDDNames = new ArrayList<String>();
        this.addedResourceRefs = new ArrayList<String>();
        this.addedDataSources = new ArrayList<NamingEntry>();

        // See if a connection pooling data source is available.
        String className = null;
        try {
            Class.forName("org.apache.tomcat.dbcp.dbcp.BasicDataSource"); //$NON-NLS-1$
            className = EGLBasicDataSource1.class.getCanonicalName();
        } catch (Throwable t) {
        }
        if (className == null) {
            try {
                Class.forName("org.apache.commons.dbcp.BasicDataSource"); //$NON-NLS-1$
                className = EGLBasicDataSource2.class.getCanonicalName();
            } catch (Throwable t) {
            }
        }
        basicDataSourceClassName = className;

        if (basicDataSourceClassName == null) {
            TestServer.logInfo(
                    "If you access a database, you will get the performance benefit of connection pooling only if you add the "
                            + "Apache Tomcat runtime code to your workspace. To add that code, go to Window > Preferences > Server > Runtime Environments.");
        }
    }

    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        initRunUnit();

        String addedDDs = req.getParameter(ARG_DD_ADDED);
        String removedDDs = req.getParameter(ARG_DD_REMOVED);
        String defaultDD = req.getParameter(ARG_DEFAULT_DD);
        String orderedDDs = req.getParameter(ARG_ORDERED_DDS);

        boolean reconfigure = false;
        if (addedDDs != null && addedDDs.length() > 0) {
            reconfigure = true;
            parseDDFiles(addedDDs, true);
        }
        if (removedDDs != null && removedDDs.length() > 0) {
            reconfigure = true;
            parseDDFiles(removedDDs, false);
        }
        if (defaultDD != null) {
            server.log("Default DD changed: " + defaultDD, LogLevel.INFO); //$NON-NLS-1$
            setDefaultDD(defaultDD);
        }
        if (orderedDDs != null) {
            reconfigure = parseOrderedDDs(orderedDDs) || reconfigure;
        }

        if (reconfigure) {
            initRunUnit();

            // Remove everything we previously processed and re-configure the server.
            previewServlet.clearServiceMappings();
            clearJNDIEnvironment();

            configureServiceMappings();
            configureJNDIEnvironment();
        }
    }

    protected void initRunUnit() {
        if (org.eclipse.edt.javart.Runtime.getRunUnit() == null) {
            org.eclipse.edt.javart.Runtime
                    .setThreadRunUnit(new JEERunUnit(new StartupInfo("ConfigServlet", "", null))); //$NON-NLS-1$ //$NON-NLS-2$
        }
    }

    public void parseDDFiles(String ddFiles, boolean added) {
        initRunUnit();
        List<String[]> parsed = bindingProcessor.getResourceLocator().parseDDArgument(ddFiles, added);
        for (String[] next : parsed) {
            if (added) {
                server.log("DD file added or changed: " + next[0] + ", " + next[1], LogLevel.INFO); //$NON-NLS-1$ //$NON-NLS-2$
            } else {
                server.log("DD file removed: " + next[0] + ", " + next[1], LogLevel.INFO); //$NON-NLS-1$ //$NON-NLS-2$
            }
        }
    }

    public void setDefaultDD(String defaultDD) {
        initRunUnit();
        try {
            bindingProcessor.setDefaultDD(defaultDD);
        } catch (Exception e) {
            // Not fatal. Log it and continue.
            server.log(e);
        }
    }

    /**
     * Parses the value for a list of DD names, which are in the order they should be processed.
     * 
     * @return true if the ordering of DD names has changed.
     */
    public boolean parseOrderedDDs(String ddFiles) {
        server.log("Parsing DD file order argument: " + ddFiles, LogLevel.INFO);
        List<String> oldNames = orderedDDNames;

        orderedDDNames = new ArrayList<String>();
        StringTokenizer tok = new StringTokenizer(ddFiles, File.pathSeparator);
        while (tok.hasMoreTokens()) {
            orderedDDNames.add(tok.nextToken());
        }

        int size = orderedDDNames.size();
        if (size == oldNames.size()) {
            for (int i = 0; i < size; i++) {
                if (!oldNames.get(i).equals(orderedDDNames.get(i))) {
                    return true;
                }
            }
            return false;
        }
        return true;
    }

    public void configureServiceMappings() {
        initRunUnit();

        // We go in reverse order so that the correct mapping is used when there are duplicate URIs.
        List<String> names = orderedDDNames;
        int size = names.size();
        for (int i = size - 1; i >= 0; i--) {
            RuntimeDeploymentDesc dd = bindingProcessor.getResourceLocator().getDeploymentDesc(names.get(i));
            if (dd instanceof DeploymentDesc) {
                List<Restservice> services = ((DeploymentDesc) dd).getRestservices();
                for (Restservice service : services) {
                    String className = service.getImplementation();
                    String uri;

                    // We use the unaliased class name for the uri, prefixed with a '/'
                    if (className.length() > 0 && className.charAt(0) != '/') {
                        uri = '/' + className;
                    } else {
                        uri = className;
                    }

                    previewServlet.addServiceMapping(uri, className, service.isStateful());
                    server.log("rest service mapping added: " + uri, LogLevel.INFO);
                }
            }
        }
    }

    /**
     * Uninstalls all JNDI configurations from the server.
     */
    public void clearJNDIEnvironment() {
        for (String name : addedResourceRefs) {
            removeResourceRef(name);
        }

        for (NamingEntry entry : addedDataSources) {
            removeDataSource(entry);
        }

        addedResourceRefs.clear();
        addedDataSources.clear();
    }

    /**
     * Registers resource-refs and data sources for use in JNDI environments, based on the DD files.
     */
    public void configureJNDIEnvironment() {
        initRunUnit();

        // 1. Define data sources.

        // If Apache's BasicDataSource is available, use it. It supports connection pooling so it's much faster.
        // If not, use our own basic data source which does not support connection pooling.
        Map<String, SQLConnectionInfo> dataSources = getSQLDataSources();
        for (Entry<String, SQLConnectionInfo> entry : dataSources.entrySet()) {
            try {
                SQLConnectionInfo info = entry.getValue();

                String dataSourceClass;
                Properties settings = new Properties();
                settings.put("driverClassName", info.driverClass); //$NON-NLS-1$
                settings.put("username", info.username); //$NON-NLS-1$
                settings.put("password", info.password); //$NON-NLS-1$
                settings.put("url", info.url); //$NON-NLS-1$
                if (basicDataSourceClassName != null) {
                    settings.put("maxActive", "5"); //$NON-NLS-1$ //$NON-NLS-2$
                    settings.put("maxIdle", "2"); //$NON-NLS-1$ //$NON-NLS-2$
                    settings.put("maxWait", "5000"); //$NON-NLS-1$ //$NON-NLS-2$
                    dataSourceClass = basicDataSourceClassName;
                } else {
                    dataSourceClass = SimpleDataSource.class.getCanonicalName();
                }

                addDataSource(entry.getKey(), dataSourceClass, settings);
            } catch (Exception e) {
                server.log(e);
            }
        }

        // 2. Register resource-refs. This must be done after the data sources are defined.
        Set<String> refs = getSQLResourceRefs();
        for (String jndiName : refs) {
            // Only add a resource-ref if we added the data source of that name, to prevent an error.
            for (NamingEntry entry : addedDataSources) {
                if (entry.getJndiName().equals(jndiName)) {
                    addResourceRef(jndiName, DataSource.class);
                    break;
                }
            }
        }
    }

    /**
     * Adds a data source to the JNDI environment.
     * 
     * @param name  The JNDI name.
     * @param className  The data source class name.
     * @param settings  The properties of the data source instance (url, username, password, etc).
     * @throws Exception
     */
    public void addDataSource(String name, String className, Properties settings) throws Exception {
        StringBuilder buf = new StringBuilder(200);
        String id = "ds" + (++nextDataSourceId); //$NON-NLS-1$
        buf.append("<Configure id=\"Server\" class=\"org.eclipse.jetty.webapp.WebAppContext\"><New id=\""); //$NON-NLS-1$
        buf.append(id);
        buf.append("\" class=\"org.eclipse.jetty.plus.jndi.Resource\"><Arg><Ref id=\"Server\"/></Arg><Arg>"); //$NON-NLS-1$
        buf.append(name);
        buf.append("</Arg><Arg><New class=\""); //$NON-NLS-1$
        buf.append(className);
        buf.append("\">"); //$NON-NLS-1$

        for (Entry<Object, Object> entry : settings.entrySet()) {
            Object key = entry.getKey();
            Object value = entry.getValue();
            if (key instanceof String && value instanceof String) {
                buf.append("<Set name=\""); //$NON-NLS-1$
                buf.append(key);
                buf.append("\">"); //$NON-NLS-1$
                buf.append(value);
                buf.append("</Set>"); //$NON-NLS-1$
            }
        }
        buf.append("</New></Arg></New></Configure>"); //$NON-NLS-1$

        ClassLoader savedLoader = Thread.currentThread().getContextClassLoader();
        Thread.currentThread().setContextClassLoader(server.getWebApp().getClassLoader());
        try {
            XmlConfiguration config = new XmlConfiguration(buf.toString());
            config.configure(server.getWebApp());
            Object boundResource = config.getIdMap().get(id);
            if (boundResource instanceof NamingEntry) {
                addedDataSources.add((NamingEntry) boundResource);
            }

            server.log("data source added: name=" + name + " class=" + className, LogLevel.INFO); //$NON-NLS-1$ //$NON-NLS-2$
        } finally {
            Thread.currentThread().setContextClassLoader(savedLoader);
        }
    }

    /**
     * Removes a data source from the JNDI environment.
     * 
     * @param name  The JNDI name.
     * @return true if the data source was successfully removed.
     */
    public boolean removeDataSource(String name) {
        for (NamingEntry entry : addedDataSources) {
            if (entry.getJndiName().equals(name)) {
                return removeDataSource(entry);
            }
        }

        return false;
    }

    /**
     * Removes a data source from the JNDI environment.
     * 
     * @param entry  The bound resource.
     * @return true if the data source was successfully removed.
     */
    public boolean removeDataSource(NamingEntry dataSource) {
        ClassLoader savedLoader = Thread.currentThread().getContextClassLoader();
        Thread.currentThread().setContextClassLoader(server.getWebApp().getClassLoader());
        try {
            dataSource.release();
            server.log("data source removed: name=" + dataSource.getJndiName(), LogLevel.INFO); //$NON-NLS-1$
            return true;
        } finally {
            Thread.currentThread().setContextClassLoader(savedLoader);
        }
    }

    /**
     * Adds a resource-ref to the web application context. This must be called AFTER the corresponding resource has been created.
     */
    public void addResourceRef(String name, Class type) {
        ClassLoader savedLoader = Thread.currentThread().getContextClassLoader();
        Thread.currentThread().setContextClassLoader(server.getWebApp().getClassLoader());
        try {
            PlusDescriptorProcessor processor = new PlusDescriptorProcessor();
            processor.bindResourceRef(server.getWebApp(), name, type);
            if (!addedResourceRefs.contains(name)) {
                addedResourceRefs.add(name);
            }
            server.log("resource-ref added: name=" + name + " type=" + type.getCanonicalName(), LogLevel.INFO); //$NON-NLS-1$ //$NON-NLS-2$
        } catch (Exception e) {
            server.log(e);
        } finally {
            Thread.currentThread().setContextClassLoader(savedLoader);
        }
    }

    /**
     * Removes a resource-ref from the web application context.
     * 
     * @return true if the resource-ref was successfully removed.
     */
    public boolean removeResourceRef(String name) {
        ClassLoader savedLoader = Thread.currentThread().getContextClassLoader();
        Thread.currentThread().setContextClassLoader(server.getWebApp().getClassLoader());
        try {
            if (!name.startsWith("java:comp/env/")) { //$NON-NLS-1$
                name = "java:comp/env/" + name; //$NON-NLS-1$
            }
            InitialContext ctx = new InitialContext();
            ctx.unbind(name);
            server.log("resource-ref removed: name=" + name, LogLevel.INFO); //$NON-NLS-1$
            return true;
        } catch (Exception e) {
            server.log(e);
            return false;
        } finally {
            Thread.currentThread().setContextClassLoader(savedLoader);
        }
    }

    /**
     * @return a map whose key is the JNDI name and value is an SQLConnectionInfo.
     */
    protected Map<String, SQLConnectionInfo> getSQLDataSources() {
        initRunUnit();

        Map<String, SQLConnectionInfo> dataSources = new HashMap<String, SQLConnectionInfo>();

        // We go in reverse order so that the correct mapping is used when there are duplicate URIs.
        List<String> names = orderedDDNames;
        int size = names.size();
        for (int i = size - 1; i >= 0; i--) {
            try {
                RuntimeDeploymentDesc dd = bindingProcessor.getResourceLocator().getDeploymentDesc(names.get(i));
                if (dd == null) {
                    continue;
                }

                List<SQLDatabaseBinding> bindings = dd.getSqlDatabaseBindings();

                for (SQLDatabaseBinding binding : bindings) {
                    if (binding.isDeployAsJndi()) {
                        SQLConnectionInfo info = null;
                        if (binding.isUseURI()) {
                            String uri = binding.getUri();
                            if (uri != null && uri.startsWith("workspace://")) { //$NON-NLS-1$
                                info = bindingProcessor.getParsedConnectionProfileSettings(uri.substring(12)); // "workspace://".length()
                            }
                        } else {
                            // Need at least a URL and class name
                            info = new SQLConnectionInfo();
                            info.url = binding.getSqlDB();
                            info.driverClass = binding.getSqlJDBCDriverClass();
                            info.username = binding.getSqlID();
                            info.password = binding.getSqlPassword();
                            info.defaultSchema = binding.getSqlSchema();
                        }

                        if (info != null) {
                            String jndiName = binding.getJndiName();
                            if (jndiName != null && (jndiName = jndiName.trim()).length() > 0) {
                                // First one in wins, not last.
                                if (!dataSources.containsKey(jndiName)) {
                                    dataSources.put(jndiName, info);
                                }
                            }
                        }
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        return dataSources;
    }

    /**
     * @return a list of JNDI names from the DD files.
     */
    protected Set<String> getSQLResourceRefs() {
        initRunUnit();

        Set<String> refs = new HashSet<String>();

        // We go in reverse order so that the correct mapping is used when there are duplicate URIs.
        List<String> names = orderedDDNames;
        int size = names.size();
        for (int i = size - 1; i >= 0; i--) {
            try {
                RuntimeDeploymentDesc dd = bindingProcessor.getResourceLocator().getDeploymentDesc(names.get(i));
                if (dd == null) {
                    continue;
                }

                List<SQLDatabaseBinding> bindings = dd.getSqlDatabaseBindings();

                for (SQLDatabaseBinding binding : bindings) {
                    String jndiName = null;
                    if (binding.isDeployAsJndi()) {
                        jndiName = binding.getJndiName();
                    } else if (binding.isUseURI() && binding.getUri() != null
                            && binding.getUri().startsWith("jndi://")) { //$NON-NLS-1$
                        // JNDI name comes from the URI
                        jndiName = binding.getUri().substring(7); // "jndi://".length()
                    }

                    if (jndiName != null && (jndiName = jndiName.trim()).length() > 0) {
                        refs.add(jndiName);
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        return refs;
    }
}