org.opencastproject.util.osgi.SimpleServicePublisher Maven / Gradle / Ivy
/**
* Licensed to The Apereo Foundation under one or more contributor license
* agreements. See the NOTICE file distributed with this work for additional
* information regarding copyright ownership.
*
*
* The Apereo Foundation licenses this file to you under the Educational
* Community 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://opensource.org/licenses/ecl2.txt
*
* 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.opencastproject.util.osgi;
import static org.opencastproject.util.data.Collections.dict;
import static org.opencastproject.util.data.Collections.list;
import static org.opencastproject.util.data.Monadics.mlist;
import static org.opencastproject.util.data.Tuple.tuple;
import static org.opencastproject.util.data.functions.Functions.noop;
import static org.opencastproject.util.data.functions.Functions.run;
import static org.osgi.framework.Constants.SERVICE_DESCRIPTION;
import static org.osgi.framework.Constants.SERVICE_PID;
import org.opencastproject.util.data.Effect0;
import org.opencastproject.util.persistence.PersistenceEnv;
import org.osgi.framework.ServiceRegistration;
import org.osgi.service.cm.ConfigurationException;
import org.osgi.service.cm.ManagedService;
import org.osgi.service.component.ComponentContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.Closeable;
import java.util.Dictionary;
import java.util.List;
/**
* An implementation of SimpleServicePublisher creates and registers another service with the OSGi environment.
*
* Using this class helps to decouple business logic from the OSGi environment. The business class can be kept clean
* from any OSGi specific needs like dependency setters, activate/deactivate methods or the implementation of OSGi
* related interfaces like {@link ManagedService} to receive configuration data. This makes the business logic far
* easier to test and results in a mucher cleaner design.
*
* Another approach to decouple the service logic from the OSGi environment is to create an abstract service class which
* declares all it's dependencies as abstract getter methods. The OSGi connection is then implemented in an inheriting
* class. This method, however, has the drawback of having to renounce a constructor implementation since no
* dependencies have been set yet.
*/
public abstract class SimpleServicePublisher implements ManagedService {
/** Log facility */
private static final Logger logger = LoggerFactory.getLogger(SimpleServicePublisher.class);
private ComponentContext cc;
private Effect0 shutdown = noop;
public static final class ServiceReg {
private final List serviceRegistrations;
private final List onShutdown;
private ServiceReg(List serviceRegistrations, List onShutdown) {
this.serviceRegistrations = serviceRegistrations;
this.onShutdown = onShutdown;
}
public static ServiceReg reg(List serviceRegistrations, List onShutdown) {
return new ServiceReg(serviceRegistrations, onShutdown);
}
public static ServiceReg reg(ServiceRegistration serviceRegistration, Effect0 onShutdown) {
return new ServiceReg(list(serviceRegistration), list(onShutdown));
}
public static ServiceReg reg(ServiceRegistration serviceRegistration, Effect0... onShutdown) {
return new ServiceReg(list(serviceRegistration), list(onShutdown));
}
public static ServiceReg reg(Effect0 onShutdown, ServiceRegistration... serviceRegistration) {
return new ServiceReg(list(serviceRegistration), list(onShutdown));
}
public List getServiceRegistrations() {
return serviceRegistrations;
}
public List getOnShutdown() {
return onShutdown;
}
}
/**
* Create and register a service object. Register with the OSGi environment using
* {@link #registerService(org.osgi.service.component.ComponentContext, Object, Class, String)} or
* cc.getBundleContext().registerService(..)
.
*
* @param properties
* a configuration object received by the {@link ManagedService#updated(java.util.Dictionary)} callback,
* never null
* @return a structure of registered services and a list of (optional) effects to run on service shutdown
*/
public abstract ServiceReg registerService(Dictionary properties, ComponentContext cc) throws ConfigurationException;
/**
* Return false if the service to be registered does not need a configuration dictionary provided by the ConfigAdmin.
*/
public abstract boolean needConfig();
/** OSGi callback. */
public synchronized void activate(ComponentContext cc) throws ConfigurationException {
logger.info("[{}] Start", this.getClass().getName());
this.cc = cc;
if (!needConfig()) {
updated(dict());
}
}
/** OSGi callback. */
public synchronized void deactivate() {
logger.info("[{}] Stop", this.getClass().getName());
shutdown.apply();
}
@Override
public synchronized void updated(Dictionary properties) throws ConfigurationException {
shutdown.apply();
if (properties != null) {
final SimpleServicePublisher self = this;
logger.info("[{}] Registering service", self.getClass().getName());
final ServiceReg registrations = registerService(properties, cc);
shutdown = new Effect0() {
@Override
protected void run() {
logger.info("[{}] Unregister service", self.getClass().getName());
for (ServiceRegistration reg : registrations.getServiceRegistrations())
reg.unregister();
mlist(registrations.getOnShutdown()).each(run);
}
};
} else {
logger.info("[{}] No config", this.getClass().getName());
}
}
/**
* Register a service with the OSGi environment in a standardized way. The service properties
* {@link org.osgi.framework.Constants#SERVICE_PID} and {@link org.osgi.framework.Constants#SERVICE_DESCRIPTION} are
* created properly.
*/
public static ServiceRegistration registerService(ComponentContext cc, Object o, Class serviceClass,
String serviceDescription) {
if (!serviceClass.isAssignableFrom(o.getClass()))
throw new Error("Service " + o + " is not of type " + serviceClass);
final Dictionary props = dict(tuple(SERVICE_PID, o.getClass().getName()),
tuple(SERVICE_DESCRIPTION, serviceDescription));
return cc.getBundleContext().registerService(serviceClass.getName(), o, props);
}
/**
* Create an effect to unregister a service suitable to return by
* {@link #registerService(java.util.Dictionary, org.osgi.service.component.ComponentContext)}.
*/
public static Effect0 unregisterService(final ServiceRegistration sr) {
return new Effect0() {
@Override
protected void run() {
sr.unregister();
}
};
}
/**
* Create an effect to close a closeable suitable to return by
* {@link #registerService(java.util.Dictionary, org.osgi.service.component.ComponentContext)}.
*/
public static Effect0 close(final Closeable c) {
return new Effect0.X() {
@Override
protected void xrun() throws Exception {
c.close();
}
};
}
/**
* Create an effect to close a persistence environment suitable to return by
* {@link #registerService(java.util.Dictionary, org.osgi.service.component.ComponentContext)}.
*/
public static Effect0 close(final PersistenceEnv penv) {
return new Effect0.X() {
@Override
protected void xrun() throws Exception {
penv.close();
}
};
}
}