/*******************************************************************************
* Copyright (c) 2015 GianMaria Romanato
* 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:
* GianMaria Romanato - initial API and implementation
*******************************************************************************/
package org.eclipse.virgo.ide.runtime.internal.core.command;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.util.Collections;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeoutException;
import javax.management.Attribute;
import javax.management.AttributeList;
import javax.management.MBeanServerConnection;
import javax.management.MalformedObjectNameException;
import javax.management.ObjectName;
import org.eclipse.core.resources.IProject;
import org.eclipse.osgi.util.ManifestElement;
import org.eclipse.virgo.ide.facet.core.FacetCorePlugin;
import org.eclipse.virgo.ide.runtime.core.IServerBehaviour;
import org.eclipse.virgo.ide.runtime.internal.core.DeploymentIdentity;
import org.eclipse.wst.server.core.IModule;
import org.osgi.framework.Constants;
/**
* An {@link IServerCommand} for Virgo 3.5+ to check whether a given bundle is already deployed or not. Created to fix
* #454015.
*
* @author GianMaria Romanato
* @since 1.0.2
*/
class JmxServerCheckBundleDeployedCommand extends AbstractJmxServerCommand implements IServerCommand<DeploymentIdentity> {
private static final String META_INF_MANIFEST_MF = "META-INF/MANIFEST.MF"; //$NON-NLS-1$
private static final String VERSION_ATTRIBUTE = "Version"; //$NON-NLS-1$
private static final String STATE_ATTRIBUTE = "State"; //$NON-NLS-1$
private static final String STATE_ATTRIBUTE_ACTIVE = "ACTIVE"; //$NON-NLS-1$
private static final String START_OPERATION = "start"; //$NON-NLS-1$
private static final String[] START_OPERATION_SIGNATURE = new String[0];
private static final Object[] START_OPERATION_PARAMETERS = new Object[0];
private static final String OBJECT_NAME = "org.eclipse.virgo.kernel:type=ArtifactModel,artifact-type=bundle,version=*,region=org.eclipse.virgo.region.user,name="; //$NON-NLS-1$
private final IModule module;
/**
* Creates a new {@link JmxServerCheckBundleDeployedCommand}.
*/
JmxServerCheckBundleDeployedCommand(IServerBehaviour serverBehaviour, IModule module) {
super(serverBehaviour);
this.module = module;
}
/**
* {@inheritDoc}
*/
public DeploymentIdentity execute() throws IOException, TimeoutException {
// if and only if the artifact is a module
if (isBundle()) {
// and we could determine its version number
// enquiry the artifact repository for a MBean named after the bundle
JmxServerCommandTemplate tpl = new JmxServerCommandTemplate() {
public Object invokeOperation(MBeanServerConnection connection) throws Exception {
return checkIfBundleAlreadyDeployed(connection);
}
};
// and return true if it was found
return (DeploymentIdentity) execute(tpl);
}
// if not a bundle (plan) or the manifest could not be parsed
// return null, the IDE tool will then assume the bundle was not already
// deployed and this will make it behave the same way it did before
return null;
}
protected DeploymentIdentity checkIfBundleAlreadyDeployed(MBeanServerConnection connection) throws MalformedObjectNameException {
ObjectName name = javax.management.ObjectName.getInstance(OBJECT_NAME + this.module.getName());
Set<ObjectName> candidates;
try {
candidates = connection.queryNames(name, null);
} catch (Exception e1) {
// silently ignore, this is a best effort tentative
return null;
}
if (!candidates.isEmpty()) {
final String localVersion = discoverBundleVersion();
for (ObjectName objectName : candidates) {
try {
AttributeList attributes = connection.getAttributes(objectName, new String[] { VERSION_ATTRIBUTE, STATE_ATTRIBUTE });
Attribute version = (Attribute) attributes.get(0);
Attribute state = (Attribute) attributes.get(1);
if (localVersion.equals(version.getValue())) {
if (!STATE_ATTRIBUTE_ACTIVE.equals(state.getValue())) {
// if bundle is not active, activate it, because this
// is the expected behaviour for a bundle that is added
// to a Virgo server instance in Eclipse
connection.invoke(objectName, START_OPERATION, START_OPERATION_PARAMETERS, START_OPERATION_SIGNATURE);
}
return new DeploymentIdentity(this.module.getName(), localVersion);
}
} catch (Exception e) {
// silently ignore, this is a best effort tentative
}
}
}
return null;
}
/**
* Parse the bundle manifest to extract the version information. Note that the manifest is taken from the deployment
* folder (stage) as opposed to the {@link IProject} because the {@link IProject} may contain changes not yet
* published.
*
* @return the version as a string or <code>null</code> if the manifest could not be found or could not be parsed
*/
protected String discoverBundleVersion() {
InputStream is = null;
Map<String, String> manifest = Collections.<String, String> emptyMap();
try {
URI manifestURI = AbstractJmxServerDeployerCommand.getUri(
this.serverBehaviour.getModuleDeployUri(this.module).append(META_INF_MANIFEST_MF));
is = manifestURI.toURL().openStream();
manifest = ManifestElement.parseBundleManifest(is, null);
} catch (Exception e) {
return null;
} finally {
if (is != null) {
try {
is.close();
} catch (IOException e) {
}
}
}
return manifest.get(Constants.BUNDLE_VERSION);
}
protected boolean isBundle() {
return this.module.getModuleType().getId().equals(FacetCorePlugin.BUNDLE_FACET_ID);
}
}