/******************************************************************************* * Copyright © 2012, 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.IOException; import java.io.PrintStream; import java.io.UnsupportedEncodingException; import java.net.Socket; import java.net.URLEncoder; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IResource; import org.eclipse.core.resources.IResourceChangeEvent; import org.eclipse.core.resources.IResourceDelta; import org.eclipse.core.resources.IResourceDeltaVisitor; import org.eclipse.core.runtime.CoreException; import org.eclipse.datatools.connectivity.IConnectionProfile; import org.eclipse.edt.ide.deployment.services.internal.testserver.DeploymentDescriptorFinder.DDFile; import org.eclipse.edt.ide.internal.sql.util.EGLSQLUtility; import org.eclipse.edt.ide.testserver.AbstractTestServerContribution; import org.eclipse.edt.ide.testserver.ClasspathUtil; import org.eclipse.edt.ide.testserver.TestServerConfiguration; import org.eclipse.edt.ide.testserver.TestServerIDEConnector; import org.eclipse.edt.ide.testserver.TestServerPlugin; import org.eclipse.edt.javart.services.servlet.proxy.RuiBrowserHttpRequest; import org.eclipse.jst.server.core.internal.JavaServerPlugin; import org.eclipse.jst.server.core.internal.RuntimeClasspathContainer; import org.eclipse.jst.server.core.internal.RuntimeClasspathProviderWrapper; import org.eclipse.osgi.util.NLS; import org.eclipse.wst.server.core.IRuntime; import org.eclipse.wst.server.core.ServerUtil; /** * Provides support on the test server for desployment descriptors, services, and SQL including JNDI. */ @SuppressWarnings("restriction") public class ServicesContribution extends AbstractTestServerContribution { private final Object syncObj; private ArrayList<TestServerConfiguration> runningConfigurations; private Map<TestServerConfiguration, String> currentDefaultDDName; private Map<TestServerConfiguration, List<DDFile>> currentDDFiles; private Map<TestServerConfiguration, String> currentDDNameOrder; private DDResourceChangeListener ddListener; public ServicesContribution() { this.syncObj = new Object(); this.runningConfigurations = new ArrayList<TestServerConfiguration>(); this.currentDDFiles = new HashMap<TestServerConfiguration, List<DDFile>>(); this.currentDefaultDDName = new HashMap<TestServerConfiguration, String>(); this.currentDDNameOrder = new HashMap<TestServerConfiguration, String>(); this.ddListener = new DDResourceChangeListener(this); } @Override public void dispose() { this.ddListener.dispose(); this.runningConfigurations = null; this.currentDDFiles = null; this.currentDefaultDDName = null; this.currentDDNameOrder = null; this.ddListener = null; } @Override public void init(TestServerConfiguration config) { synchronized (syncObj) { runningConfigurations.add(config); } } @Override public void dispose(TestServerConfiguration config) { synchronized (syncObj) { runningConfigurations.remove(config); currentDDFiles.remove(config); currentDefaultDDName.remove(config); currentDDNameOrder.remove(config); } } @Override public String[] getConfiguratorClassNames(TestServerConfiguration config) { return new String[]{ServicesConfigurator.class.getCanonicalName()}; } @Override public String getArgumentAdditions(TestServerConfiguration config) { StringBuilder buf = new StringBuilder(100); buf.append(" -dd \""); //$NON-NLS-1$ List<DDFile> ddFiles = DeploymentDescriptorFinder.findDeploymentDescriptors(config.getProject()); buf.append(DeploymentDescriptorFinder.toArgumentString(ddFiles)); buf.append("\" -ddd \""); //$NON-NLS-1$ String defaultDDName = DeploymentDescriptorFinder.getDefaultDDName(config.getProject()); buf.append(defaultDDName); String orderedDDNames = DeploymentDescriptorFinder.toOrderedArgumentString(ddFiles); buf.append("\" -odd \""); //$NON-NLS-1$ buf.append(orderedDDNames); buf.append("\""); //$NON-NLS-1$ synchronized (syncObj) { currentDDFiles.put(config, ddFiles); currentDefaultDDName.put(config, defaultDDName); currentDDNameOrder.put(config, orderedDDNames); } return buf.toString(); } @Override public String[] getClasspathAdditions(TestServerConfiguration config) { List<String> entries = new ArrayList<String>(2); String entry = ClasspathUtil.getClasspathEntry("org.eclipse.edt.ide.deployment.core"); //$NON-NLS-1$ if (entry != null) { entries.add(entry); } entry = ClasspathUtil.getClasspathEntry("org.eclipse.edt.ide.deployment.services"); //$NON-NLS-1$ if (entry != null) { entries.add(entry); } entry = ClasspathUtil.getClasspathEntry("org.eclipse.edt.runtime.java"); //$NON-NLS-1$ if (entry != null) { entries.add(entry); } entry = ClasspathUtil.getClasspathEntry("com.ibm.icu"); //$NON-NLS-1$ if (entry != null) { entries.add(entry); } DDUtil.addJDBCJars(config.getProject(), new HashSet<IProject>(), new HashSet<IResource>(), entries); // Add a Tomcat runtime if one's available, so that JNDI can use connection pooling. IRuntime bestTomcat = null; for (IRuntime rt : ServerUtil.getRuntimes(null, null)) { if (rt.getRuntimeType().getName().toLowerCase().contains("tomcat")) { //$NON-NLS-1$ if (bestTomcat == null) { bestTomcat = rt; } else if (bestTomcat.getRuntimeType().getVersion().compareTo(rt.getRuntimeType().getVersion()) < 0) { bestTomcat = rt; } } } if (bestTomcat != null) { RuntimeClasspathProviderWrapper rcpw = JavaServerPlugin.findRuntimeClasspathProvider(bestTomcat.getRuntimeType()); if (rcpw != null) { entries.add("<?xml version=\"1.0\" encoding=\"UTF-8\"?><runtimeClasspathEntry containerPath=\"" //$NON-NLS-1$ + RuntimeClasspathContainer.SERVER_CONTAINER + "/" + rcpw.getId() + "/" + bestTomcat.getId() //$NON-NLS-1$ //$NON-NLS-2$ + "\" path=\"3\" type=\"4\"/>"); //$NON-NLS-1$ } } return entries.toArray(new String[entries.size()]); } @Override public boolean handleServerRequest(Socket socket, PrintStream ps) throws Exception { RuiBrowserHttpRequest request = RuiBrowserHttpRequest.createNewRequest(socket); if (request.getContentArguments().containsKey("connectionProfile")) { //$NON-NLS-1$ handleConnectionProfileRequest(request, ps); return true; } return false; } /** * Given the name of a connection profile, this returns its url, username, password, schema, and driver class name. */ private void handleConnectionProfileRequest(RuiBrowserHttpRequest request, PrintStream ps) throws Exception { String url = null; String user = null; String pass = null; String schema = null; String className = null; String profileName = request.getContentArguments().get("connectionProfile"); //$NON-NLS-1$ IConnectionProfile profile = EGLSQLUtility.getConnectionProfile(profileName); if (profile != null) { url = EGLSQLUtility.getSQLConnectionURLPreference(profile); user = EGLSQLUtility.getSQLUserId(profile); pass = EGLSQLUtility.getSQLPassword(profile); schema = EGLSQLUtility.getDefaultSchema(profile); className = EGLSQLUtility.getSQLJDBCDriverClassPreference(profile); } if (url == null) { url = ""; //$NON-NLS-1$ } if (user == null) { user = ""; //$NON-NLS-1$ } if (pass == null) { pass = ""; //$NON-NLS-1$ } if (schema == null) { schema = ""; //$NON-NLS-1$ } if (className == null) { className = ""; //$NON-NLS-1$ } String info; try { info = URLEncoder.encode(url, "UTF-8") + ';' + URLEncoder.encode(user, "UTF-8") + ';' //$NON-NLS-1$ //$NON-NLS-2$ + URLEncoder.encode(pass, "UTF-8") + ';' + URLEncoder.encode(schema, "UTF-8") + ';' //$NON-NLS-1$ //$NON-NLS-2$ + URLEncoder.encode(className, "UTF-8"); //$NON-NLS-1$ } catch (UnsupportedEncodingException e) { // Shouldn't happen. info = url + ';' + user + ';' + pass + ';' + schema + ';' + className; } ps.print(TestServerIDEConnector.getGoodResponseHeader(request.getURL(), "application/x-www-form-urlencoded;charset=UTF-8", false)); //$NON-NLS-1$ try { ps.write(info.getBytes("utf-8")); //$NON-NLS-1$ } catch (UnsupportedEncodingException e) { // Shouldn't happen. ps.write(info.getBytes()); } } @Override public void resourceChanged(IResourceChangeEvent event, final TestServerConfiguration config) { // We need to recalculate all the DD settings if even 1 DD file in the EGL path changed. It could be that there // were duplicate settings among the files, and a setting that was included before has now been changed/removed, so // the duplicate that was previously skipped needs to be used (replacing the setting on the server). try { class RecomputeSettings extends RuntimeException {private static final long serialVersionUID = 1L;}; // For fast exit of delta visitor try { if (event.getDelta() != null) { event.getDelta().accept(new IResourceDeltaVisitor() { @Override public boolean visit(IResourceDelta delta) throws CoreException { if (delta == null) { return false; } switch (delta.getKind()) { case IResourceDelta.CHANGED: if ((delta.getFlags() & IResourceDelta.CONTENT) == 0 && (delta.getFlags() & IResourceDelta.ENCODING) == 0) { // No actual change, skip it. return true; } // Fall through. case IResourceDelta.ADDED: case IResourceDelta.REMOVED: if ("egldd".equalsIgnoreCase(delta.getFullPath().getFileExtension()) //$NON-NLS-1$ && config.isOnEGLPath(config.getProject(), delta.getResource().getProject(), new HashSet<IProject>())) { throw new RecomputeSettings(); } break; } return true; } }); } } catch (RecomputeSettings e) { new Thread() { public void run() { updateDDSettingsOnServer(config); }; }.start(); } } catch (CoreException e) { TestServerPlugin.getDefault().log(e); } } public synchronized void updateDDSettingsOnServer(TestServerConfiguration config) { String oldDDNameOrder; String oldDefaultDD; List<DDFile> oldDDs; synchronized (syncObj) { oldDefaultDD = currentDefaultDDName.get(config); oldDDNameOrder = currentDDNameOrder.get(config); oldDDs = currentDDFiles.get(config); } String defaultDD = DeploymentDescriptorFinder.getDefaultDDName(config.getProject()); boolean defaultDDChanged = !oldDefaultDD.equals(defaultDD); // Iterate through, keep track of those that are no longer in the list, those that were already in the list but have // changed, and those that are new to the list. Updates are treated as additions, and the server will handle it appropriately. List<DDFile> newDDFiles = DeploymentDescriptorFinder.findDeploymentDescriptors(config.getProject()); List<DDFile> addedOrChangedDDFiles = new ArrayList<DDFile>(); List<DDFile> copyOfCurrentDDFiles = new ArrayList<DDFile>(oldDDs); for (DDFile next : newDDFiles) { int size = copyOfCurrentDDFiles.size(); DDFile old = null; for (int i = 0; i < size; i++) { if (next.name.equals(copyOfCurrentDDFiles.get(i).name)) { old = copyOfCurrentDDFiles.remove(i); break; } } if (old == null || !old.equals(next)) { addedOrChangedDDFiles.add(next); } } String newDDNameOrder = DeploymentDescriptorFinder.toOrderedArgumentString(newDDFiles); boolean ddOrderChanged = !oldDDNameOrder.equals(newDDNameOrder); if (defaultDDChanged || ddOrderChanged || addedOrChangedDDFiles.size() > 0 || copyOfCurrentDDFiles.size() > 0) { String addedDDArg = DeploymentDescriptorFinder.toArgumentString(addedOrChangedDDFiles); String removedDDArg = DeploymentDescriptorFinder.toArgumentString(copyOfCurrentDDFiles); // leftovers get removed StringBuilder args = new StringBuilder(addedDDArg.length() + newDDNameOrder.length() + removedDDArg.length() + defaultDD.length() + 50); if (addedDDArg.length() > 0) { if (args.length() > 0) { args.append('&'); } args.append(ConfigServlet.ARG_DD_ADDED); args.append('='); args.append(addedDDArg); } if (removedDDArg.length() > 0) { if (args.length() > 0) { args.append('&'); } args.append(ConfigServlet.ARG_DD_REMOVED); args.append('='); args.append(removedDDArg); } if (defaultDDChanged) { if (args.length() > 0) { args.append('&'); } args.append(ConfigServlet.ARG_DEFAULT_DD); args.append('='); try { args.append(URLEncoder.encode(defaultDD, "UTF-8")); //$NON-NLS-1$ } catch(UnsupportedEncodingException e) { // Shouldn't happen. args.append(defaultDD); } } if (ddOrderChanged) { if (args.length() > 0) { args.append('&'); } args.append(ConfigServlet.ARG_ORDERED_DDS); args.append('='); try { args.append(URLEncoder.encode(newDDNameOrder, "UTF-8")); //$NON-NLS-1$ } catch(UnsupportedEncodingException e) { // Shouldn't happen. args.append(newDDNameOrder); } } try { int status = config.invokeServlet(ConfigServlet.SERVLET_PATH, args.toString()); if (status != 200) { TestServerPlugin.getDefault().log(NLS.bind(Messages.ConfigServletBadStatus, status)); } } catch (IOException e) { TestServerPlugin.getDefault().log(e); } } synchronized (syncObj) { currentDDNameOrder.put(config, newDDNameOrder); currentDDFiles.put(config, newDDFiles); currentDefaultDDName.put(config, defaultDD); } } public List<TestServerConfiguration> getRunningConfigurationsCopy() { synchronized (syncObj) { return (List<TestServerConfiguration>)runningConfigurations.clone(); } } }