| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you 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. |
| */ |
| |
| package org.netbeans.modules.j2ee.deployment.impl; |
| |
| import java.io.File; |
| import java.io.IOException; |
| import java.lang.ref.WeakReference; |
| import java.net.URL; |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.WeakHashMap; |
| import java.util.concurrent.ExecutorService; |
| import java.util.concurrent.Executors; |
| import java.util.concurrent.Future; |
| import java.util.concurrent.ThreadFactory; |
| import java.util.logging.Level; |
| import java.util.logging.Logger; |
| import org.netbeans.api.java.source.BuildArtifactMapper; |
| import org.netbeans.api.java.source.BuildArtifactMapper.ArtifactsUpdated; |
| import org.netbeans.modules.j2ee.deployment.common.api.ConfigurationException; |
| import org.netbeans.modules.j2ee.deployment.common.api.DatasourceAlreadyExistsException; |
| import org.netbeans.modules.j2ee.deployment.config.ConfigSupportImpl; |
| import org.netbeans.modules.j2ee.deployment.devmodules.spi.ArtifactListener; |
| import org.netbeans.modules.j2ee.deployment.devmodules.spi.ArtifactListener.Artifact; |
| import org.netbeans.modules.j2ee.deployment.devmodules.spi.J2eeApplicationProvider; |
| import org.netbeans.modules.j2ee.deployment.devmodules.spi.J2eeModuleProvider; |
| import org.netbeans.modules.j2ee.deployment.impl.projects.DeploymentTarget; |
| import org.netbeans.modules.j2ee.deployment.impl.ui.ProgressUI; |
| import org.openide.awt.StatusDisplayer; |
| import org.openide.filesystems.FileObject; |
| import org.openide.filesystems.FileSystem; |
| import org.openide.filesystems.FileUtil; |
| import org.openide.filesystems.URLMapper; |
| import org.openide.util.Exceptions; |
| import org.openide.util.Lookup; |
| import org.openide.util.NbBundle; |
| |
| /** |
| * |
| * @author Petr Hejl |
| */ |
| public final class DeployOnSaveManager { |
| |
| public static enum DeploymentState { |
| |
| MODULE_NOT_DEPLOYED, |
| |
| MODULE_UPDATED, |
| |
| DEPLOYMENT_FAILED, |
| |
| SERVER_STATE_UNSUPPORTED |
| } |
| |
| private static final Logger LOGGER = Logger.getLogger(DeployOnSaveManager.class.getName()); |
| |
| private static final int DELAY = 300; |
| |
| private static final int PROGRESS_DELAY = 200; |
| |
| private static DeployOnSaveManager instance; |
| |
| private final WeakHashMap<J2eeModuleProvider, CompileOnSaveListener> compileListeners = new WeakHashMap<J2eeModuleProvider, CompileOnSaveListener>(); |
| |
| private final WeakHashMap<J2eeModuleProvider, CopyOnSaveListener> copyListeners = new WeakHashMap<J2eeModuleProvider, CopyOnSaveListener>(); |
| |
| private final WeakHashMap<J2eeModuleProvider, Object> suspended = new WeakHashMap<J2eeModuleProvider, Object>(); |
| |
| private final WeakHashMap<J2eeModuleProvider, List<ConfigSupportImpl.DeployOnSaveListener>> projectListeners = new |
| WeakHashMap<J2eeModuleProvider, List<ConfigSupportImpl.DeployOnSaveListener>>(); |
| |
| /** |
| * We need a custom thread factory because the default one stores the |
| * ThreadGroup in constructor. If the group is destroyed in between |
| * the submit throws IllegalThreadStateException. |
| */ |
| private final ExecutorService EXECUTOR = Executors.newFixedThreadPool(1, new ThreadFactory() { |
| |
| public Thread newThread(Runnable r) { |
| Thread t = new Thread(r); |
| |
| if (t.isDaemon()) { |
| t.setDaemon(false); |
| } |
| if (t.getPriority() != Thread.NORM_PRIORITY) { |
| t.setPriority(Thread.NORM_PRIORITY); |
| } |
| return t; |
| } |
| }); |
| |
| //private final ExecutorService EXECUTOR = Executors.newFixedThreadPool(1); |
| |
| /**<i>GuardedBy("this")</i>*/ |
| private Map<J2eeModuleProvider, Set<Artifact>> toDeploy = new HashMap<J2eeModuleProvider, Set<Artifact>>(); |
| |
| /**<i>GuardedBy("this")</i>*/ |
| private Map<J2eeModuleProvider, DeploymentState> lastDeploymentStates = new HashMap<J2eeModuleProvider, DeploymentState>(); |
| |
| /**<i>GuardedBy("this")</i>*/ |
| private Future<?> current; |
| |
| private DeployOnSaveManager() { |
| super(); |
| } |
| |
| public static synchronized DeployOnSaveManager getDefault() { |
| if (instance == null) { |
| instance = new DeployOnSaveManager(); |
| } |
| return instance; |
| } |
| |
| public void startListening(J2eeModuleProvider j2eeProvider) { |
| synchronized (this) { |
| if (compileListeners.containsKey(j2eeProvider)) { |
| // this is due to EAR childs :( |
| if (j2eeProvider instanceof J2eeApplicationProvider) { |
| stopListening(j2eeProvider); |
| } else { |
| LOGGER.log(Level.FINE, "Already listening on {0}", j2eeProvider); |
| return; |
| } |
| } |
| |
| List<J2eeModuleProvider> providers = new ArrayList<J2eeModuleProvider>(4); |
| providers.add(j2eeProvider); |
| |
| if (j2eeProvider instanceof J2eeApplicationProvider) { |
| Collections.addAll(providers, |
| ((J2eeApplicationProvider) j2eeProvider).getChildModuleProviders()); |
| } |
| |
| // get all binary urls |
| List<URL> urls = new ArrayList<URL>(); |
| for (J2eeModuleProvider provider : providers) { |
| for (FileObject file : provider.getSourceFileMap().getSourceRoots()) { |
| URL url = URLMapper.findURL(file, URLMapper.EXTERNAL); |
| if (url != null) { |
| urls.add(url); |
| } |
| } |
| } |
| |
| // register CLASS listener |
| CompileOnSaveListener listener = new CompileOnSaveListener(j2eeProvider, urls); |
| for (URL url :urls) { |
| BuildArtifactMapper.addArtifactsUpdatedListener(url, listener); |
| } |
| compileListeners.put(j2eeProvider, listener); |
| |
| // register WEB listener |
| J2eeModuleProvider.DeployOnSaveSupport support = j2eeProvider.getDeployOnSaveSupport(); |
| if (support != null) { |
| CopyOnSaveListener copyListener = new CopyOnSaveListener(j2eeProvider); |
| support.addArtifactListener(copyListener); |
| copyListeners.put(j2eeProvider, copyListener); |
| } |
| } |
| } |
| |
| public void stopListening(J2eeModuleProvider j2eeProvider) { |
| synchronized (this) { |
| CompileOnSaveListener removed = compileListeners.remove(j2eeProvider); |
| if (removed == null) { |
| LOGGER.log(Level.FINE, "Not compile-listening on {0}", j2eeProvider); |
| } else { |
| for (URL url : removed.getRegistered()) { |
| BuildArtifactMapper.removeArtifactsUpdatedListener(url, removed); |
| } |
| } |
| |
| CopyOnSaveListener copyRemoved = copyListeners.remove(j2eeProvider); |
| if (removed == null) { |
| LOGGER.log(Level.FINE, "Not copy-listening on {0}", j2eeProvider); |
| } else { |
| J2eeModuleProvider.DeployOnSaveSupport support = j2eeProvider.getDeployOnSaveSupport(); |
| if (support != null) { |
| support.removeArtifactListener(copyRemoved); |
| } |
| } |
| } |
| } |
| |
| public void suspendListening(J2eeModuleProvider provider) { |
| synchronized (this) { |
| suspended.put(provider, new Object()); |
| LOGGER.log(Level.FINE, "Listening suspended for {0}", provider); |
| } |
| } |
| |
| public void resumeListening(final J2eeModuleProvider provider) { |
| boolean resume = false; |
| synchronized (this) { |
| resume = suspended.containsKey(provider); |
| } |
| |
| // don't do resume unless it is really needed |
| if (resume) { |
| FileObject fo = ((ConfigSupportImpl) provider.getConfigSupport()).getProjectDirectory(); |
| FileUtil.refreshAll(); |
| |
| try { |
| FileSystem fs = (fo != null) ? fo.getFileSystem() : FileUtil.getConfigRoot().getFileSystem(); |
| fs.runAtomicAction(new FileSystem.AtomicAction() { |
| |
| @Override |
| public void run() throws IOException { |
| clearSuspended(provider); |
| } |
| }); |
| } catch (IOException ex) { |
| LOGGER.log(Level.INFO, null, ex); |
| clearSuspended(provider); |
| } |
| } |
| } |
| |
| private void clearSuspended(J2eeModuleProvider provider) { |
| Object prev = null; |
| synchronized (this) { |
| prev = suspended.remove(provider); |
| } |
| if (LOGGER.isLoggable(Level.FINE) && prev != null) { |
| LOGGER.log(Level.FINE, "Resuming listening for {0}", provider); |
| } |
| } |
| |
| public void addDeployOnSaveListener( J2eeModuleProvider provider, ConfigSupportImpl.DeployOnSaveListener listener ) |
| { |
| synchronized (this) { |
| List<ConfigSupportImpl.DeployOnSaveListener> listeners = projectListeners.get(provider); |
| if (listeners == null) { |
| listeners = new ArrayList<ConfigSupportImpl.DeployOnSaveListener>(); |
| projectListeners.put(provider, listeners); |
| } |
| listeners.add(listener); |
| } |
| } |
| |
| public void removeDeployOnSaveListener( J2eeModuleProvider provider, ConfigSupportImpl.DeployOnSaveListener listener ) |
| { |
| synchronized (this) { |
| List<ConfigSupportImpl.DeployOnSaveListener> listeners = projectListeners.get(provider); |
| if (listeners == null) { |
| return; |
| } |
| listeners.remove(listener); |
| if (listeners.isEmpty()) { |
| projectListeners.remove(provider); |
| } |
| } |
| } |
| |
| public static boolean isServerStateSupported(ServerInstance si) { |
| return si.isRunning() && !si.isSuspended(); |
| } |
| |
| public void notifyInitialDeployment(J2eeModuleProvider provider) { |
| synchronized (this) { |
| if (compileListeners.containsKey(provider)) { |
| // this is due to EAR childs :( |
| if (provider instanceof J2eeApplicationProvider) { |
| startListening(provider); |
| } |
| } |
| |
| if (!lastDeploymentStates.containsKey(provider)) { |
| lastDeploymentStates.put(provider, DeploymentState.MODULE_UPDATED); |
| } |
| } |
| } |
| |
| public void submitChangedArtifacts(J2eeModuleProvider provider, Iterable<Artifact> artifacts) { |
| assert provider != null; |
| assert artifacts != null; |
| |
| synchronized (this) { |
| // TODO should go through deploy task and return from the notification task ? |
| if (suspended.containsKey(provider)) { |
| return; |
| } |
| |
| Set<Artifact> preparedArtifacts = toDeploy.get(provider); |
| if (preparedArtifacts == null) { |
| preparedArtifacts = new HashSet<Artifact>(); |
| toDeploy.put(provider, preparedArtifacts); |
| } |
| for (Artifact artifact : artifacts) { |
| preparedArtifacts.add(artifact); |
| } |
| |
| boolean delayed = false; |
| if (current != null && !current.isDone()) { |
| // TODO interruption throws exception to user from lower levels :(( |
| // this is dummy interruption signal handling :( |
| current.cancel(false); |
| delayed = true; |
| } |
| |
| current = EXECUTOR.submit(new DeployTask(delayed)); |
| } |
| } |
| |
| private static final class CompileOnSaveListener implements ArtifactsUpdated { |
| |
| private final WeakReference<J2eeModuleProvider> provider; |
| |
| private final List<URL> registered; |
| |
| public CompileOnSaveListener(J2eeModuleProvider provider, List<URL> registered) { |
| this.provider = new WeakReference<J2eeModuleProvider>(provider); |
| this.registered = registered; |
| } |
| |
| public List<URL> getRegistered() { |
| return registered; |
| } |
| |
| public void artifactsUpdated(Iterable<File> artifacts) { |
| J2eeModuleProvider realProvider = provider.get(); |
| if (realProvider == null) { |
| return; |
| } |
| |
| J2eeModuleProvider.DeployOnSaveClassInterceptor interceptor = realProvider.getDeployOnSaveClassInterceptor(); |
| Set<Artifact> realArtifacts = new HashSet<Artifact>(); |
| for (File file : artifacts) { |
| if (file != null) { |
| Artifact a = Artifact.forFile(file); |
| if (interceptor != null) { |
| a = interceptor.convert(a); |
| } |
| realArtifacts.add(a); |
| } |
| } |
| |
| if (LOGGER.isLoggable(Level.FINE)) { |
| for (Artifact artifact : realArtifacts) { |
| LOGGER.log(Level.FINE, "Delivered compile artifact: {0}", artifact); |
| } |
| } |
| DeployOnSaveManager.getDefault().submitChangedArtifacts(realProvider, realArtifacts); |
| } |
| |
| } |
| |
| private static final class CopyOnSaveListener implements ArtifactListener { |
| |
| private final WeakReference<J2eeModuleProvider> provider; |
| |
| public CopyOnSaveListener(J2eeModuleProvider provider) { |
| this.provider = new WeakReference<J2eeModuleProvider>(provider); |
| } |
| |
| @Override |
| public void artifactsUpdated(Iterable<Artifact> artifacts) { |
| if (LOGGER.isLoggable(Level.FINE)) { |
| for (Artifact artifact : artifacts) { |
| LOGGER.log(Level.FINE, "Delivered copy artifact: {0}", artifact); |
| } |
| } |
| J2eeModuleProvider realProvider = provider.get(); |
| if (realProvider != null) { |
| DeployOnSaveManager.getDefault().submitChangedArtifacts(realProvider, artifacts); |
| } |
| } |
| } |
| |
| private class DeployTask implements Runnable { |
| |
| private final boolean delayed; |
| |
| public DeployTask(boolean delayed) { |
| this.delayed = delayed; |
| } |
| |
| public void run() { |
| if (delayed) { |
| try { |
| Thread.sleep(DELAY); |
| } catch (InterruptedException ex) { |
| LOGGER.log(Level.INFO, null, ex); |
| return; |
| } |
| } |
| |
| LOGGER.log(Level.FINE, "Performing pending deployments"); |
| |
| Map<J2eeModuleProvider, Set<Artifact>> deployNow; |
| Map<J2eeModuleProvider, List<ConfigSupportImpl.DeployOnSaveListener>> listeners = new HashMap<J2eeModuleProvider, List<ConfigSupportImpl.DeployOnSaveListener>>(); |
| synchronized (DeployOnSaveManager.this) { |
| if (toDeploy.isEmpty()) { |
| return; |
| } |
| |
| deployNow = toDeploy; |
| toDeploy = new HashMap<J2eeModuleProvider, Set<Artifact>>(); |
| |
| // copy the listeners |
| for (Map.Entry<J2eeModuleProvider, List<ConfigSupportImpl.DeployOnSaveListener>> entry : projectListeners.entrySet()) { |
| if (!deployNow.containsKey(entry.getKey())) { |
| continue; |
| } |
| listeners.put(entry.getKey(), new ArrayList<ConfigSupportImpl.DeployOnSaveListener>(entry.getValue())); |
| } |
| } |
| |
| for (Map.Entry<J2eeModuleProvider, Set<Artifact>> entry : deployNow.entrySet()) { |
| if (entry.getValue().isEmpty()) { |
| continue; |
| } |
| try { |
| boolean updated = notifyServer(entry.getKey(), entry.getValue()); |
| if (updated) { |
| // run nbjpdaapprealoaded task. |
| runJPDAAppReloaded(); |
| |
| |
| List<ConfigSupportImpl.DeployOnSaveListener> toFire = listeners.get(entry.getKey()); |
| if (toFire != null) { |
| for (ConfigSupportImpl.DeployOnSaveListener listener : toFire) { |
| listener.deployed(entry.getValue()); |
| } |
| } |
| } |
| } catch (Throwable t) { |
| // do not throw away any exception: |
| LOGGER.log(Level.SEVERE, null, t); |
| } |
| } |
| } |
| |
| private boolean notifyServer(J2eeModuleProvider provider, Iterable<Artifact> artifacts) { |
| if (LOGGER.isLoggable(Level.FINEST)) { |
| StringBuilder builder = new StringBuilder("Artifacts updated: ["); |
| for (Artifact artifact : artifacts) { |
| builder.append(artifact.getFile().getAbsolutePath()).append(","); |
| } |
| builder.setLength(builder.length() - 1); |
| builder.append("]"); |
| LOGGER.log(Level.FINEST, builder.toString()); |
| } |
| |
| String instanceID = provider.getServerInstanceID (); |
| ServerInstance inst = ServerRegistry.getInstance ().getServerInstance (instanceID); |
| if (inst == null && "DEV-NULL".equals(instanceID)) { // NOI18N |
| LOGGER.log(Level.INFO, "No server set for Maven project - Deploy on Save will not be performed"); // NOI18N |
| return false; |
| } else if (null == inst) { |
| // the server is not in the registry... so we should not try to deploy |
| LOGGER.log(Level.INFO, "Project''s server {0} is not registered - Deploy on Save will not be performed", instanceID); // NOI18N |
| return false; |
| } |
| |
| DeploymentState lastState; |
| synchronized (this) { |
| lastState = lastDeploymentStates.get(provider); |
| if (lastState == null) { |
| lastState = DeploymentState.MODULE_NOT_DEPLOYED; |
| } |
| } |
| |
| DeploymentTarget deploymentTarget = new DeploymentTarget(provider, null); |
| TargetServer server = new TargetServer(deploymentTarget); |
| |
| DeploymentState state; |
| // DEPLOYMENT_FAILED - this can happen when metadata are invalid for example |
| // SERVER_STATE_UNSUPPORTED - this can happen when server in suspended mode |
| // null - app has not been deployed so far |
| if (lastState == null || lastState == DeploymentState.DEPLOYMENT_FAILED |
| || (lastState == DeploymentState.SERVER_STATE_UNSUPPORTED |
| && isServerStateSupported(deploymentTarget.getServer().getServerInstance()))) { |
| |
| ProgressUI ui = new ProgressUI(NbBundle.getMessage(TargetServer.class, |
| "MSG_DeployOnSave", provider.getDeploymentName()), false); |
| ui.start(PROGRESS_DELAY); |
| try { |
| DeploymentHelper.deployServerLibraries(provider); |
| DeploymentHelper.deployDatasources(provider); |
| DeploymentHelper.deployMessageDestinations(provider); |
| |
| TargetModule[] modules = server.deploy(ui, true); |
| if (modules == null || modules.length <= 0) { |
| state = DeploymentState.DEPLOYMENT_FAILED; |
| } else { |
| state = DeploymentState.MODULE_UPDATED; |
| } |
| // TODO start listening ? |
| } catch (IOException ex) { |
| LOGGER.log(Level.INFO, null, ex); |
| state = DeploymentState.DEPLOYMENT_FAILED; |
| } catch (ServerException ex) { |
| LOGGER.log(Level.INFO, null, ex); |
| state = DeploymentState.DEPLOYMENT_FAILED; |
| } catch (ConfigurationException ex) { |
| LOGGER.log(Level.INFO, null, ex); |
| state = DeploymentState.DEPLOYMENT_FAILED; |
| } catch (DatasourceAlreadyExistsException ex) { |
| LOGGER.log(Level.INFO, null, ex); |
| state = DeploymentState.DEPLOYMENT_FAILED; |
| } catch (TargetServer.NoArchiveException ex) { |
| LOGGER.log(Level.INFO, null, ex); |
| state = DeploymentState.DEPLOYMENT_FAILED; |
| } finally { |
| ui.finish(); |
| } |
| // standard incremental deploy |
| } else { |
| state = server.notifyArtifactsUpdated(provider, artifacts); |
| if (state == DeploymentState.SERVER_STATE_UNSUPPORTED |
| && lastState == DeploymentState.MODULE_NOT_DEPLOYED) { |
| state = DeploymentState.MODULE_NOT_DEPLOYED; |
| } |
| } |
| |
| if (state == DeploymentState.MODULE_UPDATED) { |
| deploymentTarget.getServer().getServerInstance().notifyUpdated(artifacts); |
| } |
| |
| String message = null; |
| switch (state) { |
| case MODULE_UPDATED: |
| message = NbBundle.getMessage(DeployOnSaveManager.class, |
| "MSG_DeployOnSave_Deployed", provider.getDeploymentName()); |
| break; |
| case DEPLOYMENT_FAILED: |
| message = NbBundle.getMessage(DeployOnSaveManager.class, |
| "MSG_DeployOnSave_Failed", provider.getDeploymentName()); |
| break; |
| case SERVER_STATE_UNSUPPORTED: |
| message = NbBundle.getMessage(DeployOnSaveManager.class, |
| "MSG_DeployOnSave_Unsupported", provider.getDeploymentName()); |
| break; |
| default: |
| message = null; |
| } |
| |
| if (message != null) { |
| StatusDisplayer.getDefault().setStatusText(message); |
| } |
| |
| LOGGER.log(Level.FINE, "Deployment state {0}", state); |
| synchronized (this) { |
| lastDeploymentStates.put(provider, state); |
| } |
| return state == DeploymentState.MODULE_UPDATED; |
| } |
| |
| private void runJPDAAppReloaded() { |
| // Hack: run nbjpdaappreloaded ANT task after deploy to fix breakpoints. |
| String reloadedClassName = org.apache.tools.ant.module.api.IntrospectedInfo.getKnownInfo().getDefs("task").get("nbjpdaappreloaded"); // NOI18N |
| if (reloadedClassName == null) { |
| // seems to be null during some unit tests |
| return; |
| } |
| String reloadedPackageName = reloadedClassName.substring(0, reloadedClassName.lastIndexOf('.')); |
| try { |
| Map<String, ClassLoader> customDefClassLoaders = (Map<String, ClassLoader>) |
| Lookup.getDefault().lookup(ClassLoader.class). |
| loadClass("org.apache.tools.ant.module.bridge.AntBridge"). // NOI18N |
| getMethod("getCustomDefClassLoaders").invoke(null); // NOI18N |
| //Class reloadedClass = org.apache.tools.ant.module.bridge.AntBridge.getCustomDefClassLoaders().get(reloadedPackageName).loadClass(reloadedClassName); |
| ClassLoader reloadedClassLoader = customDefClassLoaders.get(reloadedPackageName); |
| if (reloadedClassLoader != null) { |
| Class reloadedClass = reloadedClassLoader.loadClass(reloadedClassName); |
| reloadedClass.getMethod("execute").invoke(reloadedClass.newInstance()); |
| } |
| } catch (Exception ex) { |
| Exceptions.printStackTrace(ex); |
| } |
| } |
| |
| } |
| |
| } |