| /* |
| * 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.weblogic9.deploy; |
| |
| import java.io.File; |
| import java.io.IOException; |
| import java.util.Set; |
| import java.util.logging.Level; |
| import java.util.logging.Logger; |
| import javax.enterprise.deploy.shared.ActionType; |
| import javax.enterprise.deploy.shared.CommandType; |
| import javax.enterprise.deploy.shared.StateType; |
| import javax.enterprise.deploy.spi.Target; |
| import javax.enterprise.deploy.spi.TargetModuleID; |
| import javax.enterprise.deploy.spi.status.ProgressEvent; |
| import javax.enterprise.deploy.spi.status.ProgressListener; |
| import javax.enterprise.deploy.spi.status.ProgressObject; |
| import javax.management.MBeanServerConnection; |
| import javax.management.ObjectName; |
| import org.netbeans.api.project.FileOwnerQuery; |
| import org.netbeans.api.project.Project; |
| import org.netbeans.api.project.ProjectUtils; |
| import org.netbeans.modules.j2ee.deployment.devmodules.api.J2eeModule; |
| import org.netbeans.modules.j2ee.deployment.plugins.api.AppChangeDescriptor; |
| import org.netbeans.modules.j2ee.deployment.plugins.api.DeploymentChangeDescriptor; |
| import org.netbeans.modules.j2ee.deployment.plugins.spi.DeploymentContext; |
| import org.netbeans.modules.j2ee.deployment.plugins.spi.IncrementalDeployment; |
| import org.netbeans.modules.j2ee.deployment.plugins.spi.IncrementalDeployment2; |
| import org.netbeans.modules.j2ee.deployment.plugins.spi.config.ModuleConfiguration; |
| import org.netbeans.modules.j2ee.weblogic9.WLConnectionSupport; |
| import org.openide.filesystems.FileObject; |
| import org.openide.filesystems.FileUtil; |
| import org.openide.util.NbBundle; |
| import org.openide.util.RequestProcessor; |
| |
| /** |
| * |
| * @author Petr Hejl |
| */ |
| public class WLIncrementalDeployment extends IncrementalDeployment implements IncrementalDeployment2 { |
| |
| private static final Logger LOGGER = Logger.getLogger(WLIncrementalDeployment.class.getName()); |
| |
| private static RequestProcessor DEPLOYMENT_RP = new RequestProcessor("Weblogic Incremental Deployment", 5); // NOI18N |
| |
| private static final boolean FORBID_DIRECTORY_DEPLOYMENT = Boolean.getBoolean(WLIncrementalDeployment.class.getName() + ".forbidDirectoryDeployment"); |
| |
| private static final int FAST_SWAP_TIMEOUT = 3000; |
| |
| private static final int FAST_SWAP_POLLING = 100; |
| |
| private static final String WLDEPLOY = "wldeploy"; // NOI18N |
| |
| private final WLDeploymentManager dm; |
| |
| public WLIncrementalDeployment(WLDeploymentManager dm) { |
| this.dm = dm; |
| } |
| |
| @Override |
| public boolean canFileDeploy(Target target, J2eeModule deployable) { |
| if (FORBID_DIRECTORY_DEPLOYMENT || dm.isRemote()) { |
| return false; |
| } |
| return deployable != null && !J2eeModule.Type.CAR.equals(deployable.getType()) |
| && !J2eeModule.Type.RAR.equals(deployable.getType()); |
| } |
| |
| @Override |
| public File getDirectoryForModule(final TargetModuleID module) { |
| if (module.getParentTargetModuleID() == null) { |
| return null; |
| } |
| |
| File file = null; |
| // this won't happen currently just being defensive |
| if (module instanceof WLTargetModuleID) { |
| file = ((WLTargetModuleID) module).getDir(); |
| } |
| |
| // the following code should work even for standalone apps (not just in |
| // EAR) but the condition above prevents such call to be defensive |
| if (file == null) { |
| WLConnectionSupport support = dm.getConnectionSupport(); |
| try { |
| file = support.executeAction(new WLConnectionSupport.JMXRuntimeAction<File>() { |
| |
| @Override |
| public File call(MBeanServerConnection con, ObjectName service) throws Exception { |
| Object uri = null; |
| Object path = null; |
| |
| TargetModuleID parent = module.getParentTargetModuleID() == null ? module : module.getParentTargetModuleID(); |
| |
| ObjectName testPattern = new ObjectName("com.bea:Name=" + parent.getModuleID() + ",Location=" |
| + parent.getTarget().getName() + ",Type=AppDeployment,*"); // NOI18N |
| Set<ObjectName> deployments = con.queryNames(testPattern, null); |
| if (!deployments.isEmpty()) { |
| ObjectName appItem = (ObjectName) con.getAttribute( |
| deployments.iterator().next(), "AppMBean"); |
| if (appItem == null) { |
| return null; |
| } |
| |
| ObjectName[] comps = (ObjectName[]) con.getAttribute(appItem, "Components"); |
| for (ObjectName comp : comps) { |
| String name = (String) con.getAttribute(comp, "Name"); |
| if (module.getModuleID().equals(name)) { |
| uri = (String) con.getAttribute(comp, "URI"); |
| if (uri != null) { |
| break; |
| } |
| } |
| } |
| // FullPath attribute is sometimes wrong :( |
| // TODO resolve relative paths - is it possible ? |
| path = con.getAttribute(appItem, "Path"); |
| } |
| |
| if (path == null || (uri == null && module.getParentTargetModuleID() != null)) { |
| return null; |
| } |
| |
| return (uri != null) ? new File(path.toString() + File.separator + uri.toString()) : new File(path.toString()); |
| } |
| }); |
| } catch (Exception ex) { |
| // pass through |
| } |
| } |
| |
| if (null != file && !file.isDirectory()) { |
| throw new IllegalStateException(NbBundle.getMessage(WLIncrementalDeployment.class, |
| "ERR_UndeployAndRedeploy")); |
| } |
| return file; |
| } |
| |
| @Override |
| public File getDirectoryForNewApplication(Target target, J2eeModule app, ModuleConfiguration configuration) { |
| // FIXME more or less copied from GlassFish |
| // 1) should be in common place |
| // 2) the logic is inverted - this should be in project |
| File dest = null; |
| if (app.getType() == J2eeModule.Type.EAR) { |
| File tmp = getProjectDir(app); |
| if (null == tmp) { |
| return dest; |
| } |
| dest = new File(tmp, "target"); // NOI18N |
| if (!dest.exists()) { |
| // the app wasn't a maven project |
| dest = new File(tmp, "dist"); // NOI18N |
| } |
| if (dest.isFile() || (dest.isDirectory() && !dest.canWrite())) { |
| throw new IllegalStateException(); |
| } |
| String moduleName = sanitizeName(computeModuleID(app)); |
| String dirName = WLDEPLOY; |
| if (null != moduleName) { |
| dirName += "/"+moduleName; // NOI18N |
| } |
| dest = new File(dest, dirName); |
| boolean retval = true; |
| if (!dest.exists()) { |
| retval = dest.mkdirs(); |
| } |
| if (!retval || !dest.isDirectory()) { |
| dest = null; |
| } |
| } |
| return dest; |
| } |
| |
| @Override |
| public File getDirectoryForNewModule(File appDir, String uri, J2eeModule module, ModuleConfiguration configuration) { |
| //return new File(appDir, transform(removeLeadSlash(uri))); |
| return new File(appDir, removeLeadSlash(uri)); |
| } |
| |
| @Override |
| public ProgressObject incrementalDeploy(TargetModuleID module, AppChangeDescriptor changes) { |
| boolean redeploy = changes.classesChanged() || changes.descriptorChanged() |
| || changes.ejbsChanged() || changes.manifestChanged() || changes.serverDescriptorChanged(); |
| if (changes instanceof DeploymentChangeDescriptor) { |
| DeploymentChangeDescriptor deploymentChanges = (DeploymentChangeDescriptor) changes; |
| redeploy = redeploy || deploymentChanges.serverResourcesChanged(); |
| } |
| |
| if (!redeploy) { |
| WLProgressObject progress = new WLProgressObject(module); |
| progress.fireProgressEvent(module, new WLDeploymentStatus( |
| ActionType.EXECUTE, CommandType.DISTRIBUTE, StateType.COMPLETED, |
| NbBundle.getMessage(WLIncrementalDeployment.class, "MSG_Deployment_Completed"))); |
| return progress; |
| } |
| |
| return dm.redeploy(module); |
| // CommandBasedDeployer deployer = new CommandBasedDeployer(dm); |
| // return deployer.directoryRedeploy(module, dm.getDeployTargets()); |
| } |
| |
| @Override |
| public ProgressObject initialDeploy(Target target, J2eeModule app, ModuleConfiguration configuration, |
| File dir) { |
| |
| String name = dir.getName(); |
| // FIXME this needs more fine tuning (escape chars) |
| try { |
| FileObject content = app.getContentDirectory(); |
| if (content != null) { |
| Project project = FileOwnerQuery.getOwner(content); |
| if (project != null) { |
| name = ProjectUtils.getInformation(project).getName(); |
| } |
| } |
| } catch (IOException ex) { |
| LOGGER.log(Level.FINE, null, ex); |
| } |
| |
| CommandBasedDeployer deployer = new CommandBasedDeployer(dm); |
| return deployer.directoryDeploy(target, name, dir, dm.getHost(), dm.getPort(), |
| dm.getCommonConfiguration().isSecured(), app.getType()); |
| } |
| |
| @Override |
| public ProgressObject incrementalDeploy(TargetModuleID module, DeploymentContext context) { |
| dm.deployOptionalPackages(context.getRequiredLibraries()); |
| return incrementalDeploy(module, context.getChanges()); |
| } |
| |
| @Override |
| public ProgressObject initialDeploy(Target target, DeploymentContext context) { |
| dm.deployOptionalPackages(context.getRequiredLibraries()); |
| return initialDeploy(target, context.getModule(), null, context.getModuleFile()); |
| } |
| |
| @Override |
| public boolean isDeployOnSaveSupported() { |
| return !dm.isRemote(); |
| } |
| |
| @Override |
| public ProgressObject deployOnSave(final TargetModuleID module, |
| final DeploymentChangeDescriptor desc) { |
| |
| if (desc.classesChanged() && !desc.descriptorChanged() |
| && !desc.ejbsChanged() && !desc.manifestChanged() |
| && !desc.serverDescriptorChanged() && !desc.serverResourcesChanged()) { |
| final BridgingProgressObject progress = new BridgingProgressObject(module); |
| progress.fireProgressEvent(null, new WLDeploymentStatus( |
| ActionType.EXECUTE, CommandType.REDEPLOY, StateType.RUNNING, |
| NbBundle.getMessage(CommandBasedDeployer.class, "MSG_Redeployment_Started"))); |
| |
| DEPLOYMENT_RP.submit(new Runnable() { |
| |
| @Override |
| public void run() { |
| progress.fireProgressEvent(null, new WLDeploymentStatus( |
| ActionType.EXECUTE, CommandType.DISTRIBUTE, StateType.RUNNING, |
| NbBundle.getMessage(CommandBasedDeployer.class, "MSG_Redeploying", module.getModuleID()))); |
| // DISABLED SINCE 7.1.1 see #206798 |
| // if (deployFastSwap(module)) { |
| // LOGGER.log(Level.FINE, "Fast swap successful"); |
| // progress.fireProgressEvent(null, new WLDeploymentStatus( |
| // ActionType.EXECUTE, CommandType.REDEPLOY, StateType.COMPLETED, |
| // NbBundle.getMessage(CommandBasedDeployer.class, "MSG_Redeployment_Completed"))); |
| // return; |
| // } |
| // LOGGER.log(Level.FINE, "Fast swap failed doing incremental deploy"); |
| progress.startListening(module, incrementalDeploy(module, desc)); |
| } |
| }); |
| return progress; |
| } |
| return incrementalDeploy(module, desc); |
| } |
| |
| @Override |
| public String getModuleUrl(final TargetModuleID module) { |
| assert module != null; |
| |
| if (module.getWebURL() == null) { |
| String url = module.getModuleID(); |
| return url.startsWith("/") ? url : "/" + url; |
| } |
| |
| // TODO is this hack ? |
| // looks like weblogic (TargetModulesIDs returned by server) |
| // is using weburl as moduleID for war in ear |
| // and ejb jar name for ejb in ear, we need moduleURI |
| final String id = module.getModuleID(); |
| final String slashId = id.startsWith("/") ? id : "/" + id; |
| WLConnectionSupport support = dm.getConnectionSupport(); |
| String url = null; |
| try { |
| url = support.executeAction(new WLConnectionSupport.JMXRuntimeAction<String>() { |
| |
| @Override |
| public String call(MBeanServerConnection con, ObjectName service) throws Exception { |
| ObjectName pattern = new ObjectName( |
| "com.bea:Type=WebAppComponentRuntime,*"); // NOI18N |
| |
| Set<ObjectName> runtimes = con.queryNames(pattern, null); |
| for (ObjectName runtime : runtimes) { |
| String moduleId = (String) con.getAttribute(runtime, "ModuleId"); // NOI18N |
| if (id.equals(moduleId) |
| || slashId.equals(moduleId)) { |
| return (String) con.getAttribute(runtime, "ModuleURI"); // NOI18N |
| } |
| } |
| |
| TargetModuleID parent = module.getParentTargetModuleID(); |
| ObjectName testPattern = new ObjectName("com.bea:Name=" + module.getModuleID() + ",Location=" |
| + module.getTarget().getName() + ",Type=WebAppComponent" |
| + (parent != null ? ",Application=" + parent.getModuleID() + ",*" : ",*")); // NOI18N |
| Set<ObjectName> deployments = con.queryNames(testPattern, null); |
| if (!deployments.isEmpty()) { |
| return (String) con.getAttribute(deployments.iterator().next(), "URI"); // NOI18N |
| } |
| |
| return null; |
| } |
| }); |
| } catch (Exception ex) { |
| // pass through |
| } |
| if (url != null) { |
| return url.startsWith("/") ? url : "/" + url; // NOI18N |
| } |
| // will fail probably |
| return id.startsWith("/") ? id : "/" + id; // NOI18N |
| } |
| |
| private boolean deployFastSwap(TargetModuleID module) { |
| final String server = module.getTarget().getName(); |
| final String application = module.getModuleID(); |
| |
| WLConnectionSupport support = dm.getConnectionSupport(); |
| try { |
| Boolean ret = support.executeAction(new WLConnectionSupport.JMXAction<Boolean>() { |
| |
| @Override |
| public Boolean call(MBeanServerConnection connection) throws Exception { |
| ObjectName appRuntime = new ObjectName( |
| "com.bea:ServerRuntime=" + server + ",Name=" + application + ",Type=ApplicationRuntime"); // NOI18N |
| ObjectName redefinitionRuntime = (ObjectName) connection.getAttribute( |
| appRuntime, "ClassRedefinitionRuntime"); // NOI18N |
| |
| if (redefinitionRuntime == null) { |
| return false; |
| } |
| |
| ObjectName redef = (ObjectName) connection.invoke( |
| redefinitionRuntime, "redefineClasses", new Object[] {null, null}, // NOI18N |
| new String[] {String.class.getName(), String[].class.getName()}); |
| |
| long start = System.currentTimeMillis(); |
| String str = (String) connection.getAttribute(redef, "Status"); // NOI18N |
| while ((System.currentTimeMillis() - start) < FAST_SWAP_TIMEOUT && isRunning(str)) { |
| Thread.sleep(FAST_SWAP_POLLING); |
| str = (String) connection.getAttribute(redef, "Status"); // NOI18N |
| } |
| |
| Integer candidates = (Integer) connection.getAttribute(redef, "CandidateClassesCount"); // NOI18N |
| if (LOGGER.isLoggable(Level.FINE)) { |
| Integer processed = (Integer) connection.getAttribute(redef, "ProcessedClassesCount"); // NOI18N |
| LOGGER.log(Level.FINE, "Processed {0} from {1} candidate classes", // NOI18N |
| new Object[] {processed, candidates}); |
| } |
| |
| if (isRunning(str)) { |
| connection.invoke(redef, "cancel", new Object[0], new String[0]); // NOI18N |
| return false; |
| } |
| |
| if (!("FINISHED".equals(str)) // NOI18N |
| || (candidates != null && candidates.intValue() == 0)) { |
| return false; |
| } |
| return true; |
| } |
| |
| @Override |
| public String getPath() { |
| return "/jndi/weblogic.management.mbeanservers.runtime"; // NOI18N |
| } |
| }); |
| return ret.booleanValue(); |
| }catch (Exception ex) { |
| LOGGER.log(Level.INFO, null, ex); |
| return false; |
| } |
| } |
| |
| private static boolean isRunning(String status) { |
| return "RUNNING".equals(status) || "SCHEDULED".equals(status); // NOI18N |
| } |
| |
| // FIXME copied from GF |
| // try to get the Project Directory as a File |
| // use a couple different stratgies, since the resource.dir is in a user- |
| // editable file -- but it is quicker to access, if it is there.... |
| // |
| private File getProjectDir(J2eeModule app) { |
| try { |
| FileObject fo = app.getContentDirectory(); |
| Project p = FileOwnerQuery.getOwner(fo); |
| if (null != p) { |
| fo = p.getProjectDirectory(); |
| return FileUtil.toFile(fo); |
| } |
| } catch (IOException ex) { |
| Logger.getLogger("glassfish-javaee").log(Level.FINER, // NOI18N |
| null,ex); |
| } |
| java.io.File tmp = app.getResourceDirectory(); |
| |
| if (tmp != null) { |
| return tmp.getParentFile(); |
| } |
| return null; |
| } |
| |
| // FIXME copied from GF |
| public static String sanitizeName(String name) { |
| if (null == name || name.matches("[\\p{L}\\p{N}_][\\p{L}\\p{N}\\-_./;#:]*")) { |
| return name; |
| } |
| // the string is bad... |
| return "_" + name.replaceAll("[^\\p{L}\\p{N}\\-_./;#:]", "_"); |
| } |
| |
| // FIXME copied from GF |
| public static String computeModuleID(J2eeModule module) { |
| String moduleID = null; |
| FileObject fo = null; |
| try { |
| fo = module.getContentDirectory(); |
| if (null != fo) { |
| moduleID = ProjectUtils.getInformation(FileOwnerQuery.getOwner(fo)).getDisplayName(); |
| } |
| } catch (IOException ex) { |
| LOGGER.log(Level.FINER, null, ex); |
| } |
| |
| // if (null == moduleID || moduleID.trim().length() < 1) { |
| // J2eeModuleHelper j2eeModuleHelper = J2eeModuleHelper.getSunDDModuleHelper(module.getType()); |
| // if(j2eeModuleHelper != null) { |
| // RootInterface rootDD = j2eeModuleHelper.getStandardRootDD(module); |
| // if(rootDD != null) { |
| // try { |
| // moduleID = rootDD.getDisplayName(null); |
| // } catch (VersionNotSupportedException ex) { |
| // // ignore, handle as null below. |
| // } |
| // } |
| // } |
| // } |
| |
| return moduleID; |
| } |
| |
| // FIXME copied from GF |
| // private static String transform(String s) { |
| // int len = s.length(); |
| // if (len > 4) { |
| // StringBuilder sb = new StringBuilder(s); |
| // char tmp = sb.charAt(len - 4); |
| // if (tmp == '.') { |
| // sb.setCharAt(len-4, '_'); |
| // return sb.toString(); |
| // } |
| // } |
| // return s; |
| // } |
| |
| // FIXME copied from GF |
| private static String removeLeadSlash(String s) { |
| if (null == s) { |
| return s; |
| } |
| if (s.length() < 1) { |
| return s; |
| } |
| if (!s.startsWith("/")) { |
| return s; |
| } |
| return s.substring(1); |
| } |
| |
| private static class BridgingProgressObject extends WLProgressObject implements ProgressListener { |
| |
| public BridgingProgressObject(TargetModuleID... moduleIds) { |
| super(moduleIds); |
| } |
| |
| public void startListening(TargetModuleID moduleID, ProgressObject po) { |
| po.addProgressListener(this); |
| if (!po.getDeploymentStatus().isRunning()) { |
| po.removeProgressListener(this); |
| } |
| fireProgressEvent(moduleID, po.getDeploymentStatus()); |
| } |
| |
| @Override |
| public void handleProgressEvent(ProgressEvent pe) { |
| if (!getDeploymentStatus().isRunning() && pe.getDeploymentStatus().isRunning()) { |
| // prevent wrong ordering |
| return; |
| } |
| fireProgressEvent(pe.getTargetModuleID(), pe.getDeploymentStatus()); |
| } |
| |
| } |
| } |