blob: c1caad5ac1c363b39b40cd18e42a22cfb7525112 [file] [log] [blame]
/*
* 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.apache.ace.agent.impl;
import static org.apache.ace.agent.impl.ReflectionUtil.configureField;
import static org.apache.ace.agent.impl.ReflectionUtil.invokeMethod;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
import java.util.SortedSet;
import org.apache.ace.agent.DeploymentHandler;
import org.apache.ace.agent.DownloadHandle;
import org.apache.ace.agent.InstallationFailedException;
import org.apache.ace.agent.RetryAfterException;
import org.apache.felix.deploymentadmin.DeploymentAdminImpl;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceReference;
import org.osgi.framework.Version;
import org.osgi.service.deploymentadmin.DeploymentAdmin;
import org.osgi.service.deploymentadmin.DeploymentException;
import org.osgi.service.deploymentadmin.DeploymentPackage;
import org.osgi.service.event.Event;
import org.osgi.service.event.EventAdmin;
import org.osgi.service.log.LogService;
import org.osgi.service.packageadmin.PackageAdmin;
@SuppressWarnings({"restriction","deprecation"})
public class DeploymentHandlerImpl extends UpdateHandlerBase implements DeploymentHandler {
/**
* Internal EventAdmin that delegates to actual InternalEvents. Used to inject into the DeploymentAdmin only. If it
* can find an EventAdmin service in the framework, it will also send events to that service. It does so without
* ever trying to import any API, because of two reasons:
* <ol>
* <li>We want to isolate the management agent as well as possible from the rest of the framework.</li>
* <li>We have an internal copy of the EventAdmin API, which means we cannot be exposed to another version anyway.</li>
* </ol>
*/
final class EventAdminBridge implements EventAdmin {
private final BundleContext m_context;
public EventAdminBridge(BundleContext context) {
m_context = context;
}
@Override
public void postEvent(Event event) {
getEventsHandler().postEvent(event.getTopic(), getPayload(event));
invokeExternalEventAdmin("postEvent", event);
}
@Override
public void sendEvent(Event event) {
getEventsHandler().sendEvent(event.getTopic(), getPayload(event));
invokeExternalEventAdmin("sendEvent", event);
}
/**
* Bridges events from out local event-handling methods to the first EventAdmin service. As we do not have a
* dependency on the (external!) EventAdmin API we cannot always call like we normally would do for
* OSGi-services. Instead, we need to do some advanced reflection trickery in order to call an EventAdmin.
*/
private void invokeExternalEventAdmin(String method, Event event) {
try {
// try to find an EventAdmin service
ServiceReference<?>[] refs = m_context.getAllServiceReferences(EventAdmin.class.getName(), null);
if (refs != null && refs.length > 0) {
// if we've found one (or more) we pick the first match
Object svc = m_context.getService(refs[0]);
if (svc != null) {
try {
// if the service is still around, we use the instance to find its classloader
// and obtain a reference to its "Event" class
Class<?> clazz = svc.getClass().getClassLoader().loadClass(Event.class.getName());
// and try to find a constructor
Constructor<?> ctor = clazz.getConstructor(String.class, Map.class);
// instantiate the event, using the topic and payload
Object eventAdminEvent = ctor.newInstance(event.getTopic(), getPayload(event));
// and now try to find the supplied method (either postEvent or sendEvent)
Method methodReference = svc.getClass().getMethod(method, clazz);
// and invoke it
methodReference.invoke(svc, eventAdminEvent);
}
finally {
// make sure we always unget our service reference
m_context.ungetService(refs[0]);
}
}
}
}
catch (Exception e) {
// there is a lot that can go wrong, but not much we can do at this point
// beyond logging the error message
logError("Failed to invoke EventAdmin: %s", e, e.getMessage());
}
}
private Map<String, String> getPayload(Event event) {
Map<String, String> payload = new HashMap<>();
for (String propertyName : event.getPropertyNames()) {
payload.put(propertyName, event.getProperty(propertyName).toString());
}
return payload;
}
}
/**
* Internal LogService that wraps delegates to actual InternalLogger. Used to inject into the DeploymentAdmin only.
*/
final class LogServiceBridge implements LogService {
@Override
public void log(int level, String message) {
log(level, message, null);
}
@Override
public void log(int level, String message, Throwable exception) {
invokeInternalLogService(level, message, exception);
}
@Override
public void log(ServiceReference sr, int level, String message) {
log(level, message, null);
}
@Override
public void log(ServiceReference sr, int level, String message, Throwable exception) {
log(level, message, exception);
}
private void invokeInternalLogService(int level, String message, Throwable exception) {
switch (level) {
case LogService.LOG_ERROR:
logError(message, exception);
break;
case LogService.LOG_WARNING:
logWarning(message, exception);
break;
case LogService.LOG_INFO:
logInfo(message, exception);
break;
case LogService.LOG_DEBUG:
default:
logDebug(message, exception);
break;
}
}
}
private final DeploymentAdmin m_deploymentAdmin;
private final boolean m_ownDeploymentAdmin;
public DeploymentHandlerImpl(BundleContext bundleContext, PackageAdmin packageAdmin) {
super("deployment");
m_ownDeploymentAdmin = true;
m_deploymentAdmin = new DeploymentAdminImpl();
configureField(m_deploymentAdmin, BundleContext.class, bundleContext);
configureField(m_deploymentAdmin, PackageAdmin.class, packageAdmin);
configureField(m_deploymentAdmin, EventAdmin.class, new EventAdminBridge(bundleContext));
configureField(m_deploymentAdmin, LogService.class, new LogServiceBridge());
}
DeploymentHandlerImpl(DeploymentAdmin deploymentAdmin) {
super("deployment");
m_ownDeploymentAdmin = false;
m_deploymentAdmin = deploymentAdmin;
}
@Override
public SortedSet<Version> getAvailableVersions() throws RetryAfterException, IOException {
return getAvailableVersions(getEndpoint(getServerURL(), getIdentification()));
}
@Override
public DownloadHandle getDownloadHandle(Version version, boolean fixPackage) throws RetryAfterException {
return getDownloadHandle(getPackageURL(version, fixPackage));
}
@Override
public InputStream getInputStream(Version version, boolean fixPackage) throws RetryAfterException, IOException {
return getInputStream(getPackageURL(version, fixPackage));
};
@Override
public Version getInstalledVersion() {
Version highestVersion = Version.emptyVersion;
String identification = getIdentification();
DeploymentPackage[] installedPackages = m_deploymentAdmin.listDeploymentPackages();
for (DeploymentPackage installedPackage : installedPackages) {
String packageId = installedPackage.getName();
Version packageVersion = installedPackage.getVersion();
if (identification.equals(packageId) && packageVersion.compareTo(highestVersion) > 0) {
highestVersion = packageVersion;
}
}
return highestVersion;
};
@Override
public String getName() {
return "deployment";
};
@Override
public long getSize(Version version, boolean fixPackage) throws RetryAfterException, IOException {
return getPackageSize(getPackageURL(version, fixPackage));
}
@Override
public void install(InputStream inputStream) throws InstallationFailedException, IOException {
try {
m_deploymentAdmin.installDeploymentPackage(inputStream);
}
catch (DeploymentException exception) {
Throwable cause = exception.getCause();
// Properly handle possible server overload...
if (cause instanceof RetryAfterException) {
throw (RetryAfterException) cause;
}
throw new InstallationFailedException("Installation of deployment package failed!", exception);
}
}
@Override
protected void onStart() throws Exception {
if (m_ownDeploymentAdmin) {
invokeMethod(m_deploymentAdmin, "start", new Class<?>[] {}, new Object[] {});
}
}
@Override
protected void onStop() throws Exception {
if (m_ownDeploymentAdmin) {
invokeMethod(m_deploymentAdmin, "stop", new Class<?>[] {}, new Object[] {});
}
}
private URL getEndpoint(URL serverURL, String identification) {
try {
return new URL(serverURL, "deployment/" + identification + "/versions/");
}
catch (MalformedURLException e) {
throw new IllegalStateException(e);
}
}
private URL getEndpoint(URL serverURL, String identification, Version from, Version to) {
try {
if (from == null || from.equals(Version.emptyVersion)) {
return new URL(serverURL, "deployment/" + identification + "/versions/" + to.toString());
}
else {
return new URL(serverURL, "deployment/" + identification + "/versions/" + to.toString() + "?current=" + from);
}
}
catch (MalformedURLException e) {
throw new IllegalStateException(e);
}
}
private URL getPackageURL(Version version, boolean fixPackage) throws RetryAfterException {
return getEndpoint(getServerURL(), getIdentification(), fixPackage ? getInstalledVersion() : Version.emptyVersion, version);
}
}