/*******************************************************************************
* Copyright (c) 2008, 2009 Bug Labs, Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* - Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* - Neither the name of Bug Labs, Inc. nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*******************************************************************************/
package com.buglabs.bug.bmi;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;
import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
import org.osgi.framework.FrameworkEvent;
import org.osgi.framework.FrameworkListener;
import org.osgi.framework.InvalidSyntaxException;
import org.osgi.framework.ServiceEvent;
import org.osgi.framework.ServiceListener;
import org.osgi.framework.ServiceReference;
import org.osgi.service.log.LogService;
import com.buglabs.bug.bmi.api.IModlet;
import com.buglabs.bug.bmi.api.IModletFactory;
import com.buglabs.bug.bmi.sysfs.BMIDevice;
import com.buglabs.bug.bmi.sysfs.BMIDeviceHelper;
import com.buglabs.util.osgi.FilterUtil;
import com.buglabs.util.osgi.LogServiceUtil;
import com.buglabs.util.shell.pub.ShellSession;
/**
* Activator for BMI Bundle. BMI bundle handles event notification and IModlet
* initialization upon hardware change events. This bundle is specific to BUG
* hardware that has BMI ports.
*
* @author kgilmer
*
*/
public class Activator implements BundleActivator, ServiceListener, FrameworkListener {
private static final String DEFAULT_PIPE_FILENAME = "/tmp/eventpipe";
/**
* If present as a property, used as the name of the pipe used to
* communicate udev events.
*/
public static final String PIPE_FILENAME_KEY = "com.buglabs.pipename";
/**
* Thread to read input from fs pipe.
*/
private PipeReader pipeReader;
/**
* Filename of fs pipe.
*/
private String pipeFilename;
private LogService logService;
/**
* Map of all IModletFactories.
*/
private Map<String, List<IModletFactory>> modletFactories;
/**
* Map of all active (modules attached and running) IModlets.
*/
private Map<String, List<IModlet>> activeModlets;
private static BundleContext context;
private BMIModuleEventHandler eventHandler;
/*
* (non-Javadoc)
*
* @see
* org.osgi.framework.BundleActivator#start(org.osgi.framework.BundleContext
* )
*/
public void start(BundleContext context) throws Exception {
Activator.context = context;
modletFactories = new Hashtable<String, List<IModletFactory>>();
activeModlets = new Hashtable<String, List<IModlet>>();
logService = LogServiceUtil.getLogService(context);
context.addServiceListener(this, FilterUtil.generateServiceFilter(IModletFactory.class.getName()));
registerExistingServices();
pipeFilename = context.getProperty(PIPE_FILENAME_KEY);
if (pipeFilename == null || pipeFilename.length() == 0)
pipeFilename = DEFAULT_PIPE_FILENAME;
createPipe(pipeFilename);
eventHandler = new BMIModuleEventHandler(context, logService, modletFactories, activeModlets);
pipeReader = new PipeReader(pipeFilename, eventHandler, logService);
//Run coldplug and processing of BMI events after framework has fully loaded.
context.addFrameworkListener(this);
}
/*
* (non-Javadoc)
*
* @see
* org.osgi.framework.BundleActivator#stop(org.osgi.framework.BundleContext)
*/
public void stop(BundleContext context) throws Exception {
context.removeServiceListener(this);
stopModlets(activeModlets.values());
if (pipeReader != null) {
pipeReader.shutdown();
pipeReader.interrupt();
logService.log(LogService.LOG_DEBUG, "Deleting pipe " + pipeFilename);
destroyPipe(new File(pipeFilename));
}
modletFactories.clear();
}
/**
* Discover attached BMI devices and send INSERT events into system.
*
* @throws IOException
* on File I/O error
*/
private void coldPlug() throws IOException {
List<BMIDevice> devices = Arrays.asList(BMIDeviceHelper.getDevices(context));
if (devices != null) {
logService.log(LogService.LOG_DEBUG, "(coldplug) Initializing existing modules.");
for (BMIDevice bmiMessage : devices) {
if (bmiMessage != null) {
logService.log(LogService.LOG_DEBUG
, "Registering existing module with message: " + bmiMessage.toString());
eventHandler.handleEvent(new BMIModuleEvent(bmiMessage));
}
}
} else {
logService.log(LogService.LOG_DEBUG, "(coldplug) Not registering existing modules, none found.");
}
}
/**
* @param factory
* factory to use to create modlet.
* @throws Exception
* on bundle or system error
*/
private void createModlets(IModletFactory factory) throws Exception {
IModlet modlet = factory.createModlet(context, 0, null);
modlet.setup();
modlet.start();
}
/**
* Create a pipe file by executing an external process. Requires that the
* host system has the "mkfifo" program.
*
* @param filename
* absolute path of pipe
* @throws IOException
* on File I/O error
*/
private void createPipe(String filename) throws IOException {
File f = new File(filename);
// Check to see if file exists. If so delete and recreate to confirm
// it's a pipe.
if (f.exists()) {
logService.log(LogService.LOG_INFO, "Pipe " + f.getAbsolutePath() + " already exists, deleting.");
destroyPipe(f);
}
String cmd = "/usr/bin/mkfifo " + f.getAbsolutePath();
ShellSession shell = new ShellSession(f.getParentFile());
shell.execute(cmd);
logService.log(LogService.LOG_INFO, "Created pipe " + pipeFilename);
}
/**
* Deletes a file.
*
* @param file
* to delete
* @throws IOException
* on File I/O error
*/
private void destroyPipe(File file) throws IOException {
if (!file.delete()) {
throw new IOException("Unable to delete file " + file.getAbsolutePath());
}
logService.log(LogService.LOG_DEBUG, "Deleted " + file.getAbsolutePath());
}
/**
* @return active IModlets.
*/
protected Map<String, List<IModlet>> getActiveModlets() {
return activeModlets;
}
/**
* @return all IModletFactories
*/
protected Map<String, List<IModletFactory>> getModletFactories() {
return modletFactories;
}
/**
* @param element
* input
* @return true if string is null or of 0 length.
*/
private boolean isEmpty(String element) {
return element == null || element.length() == 0;
}
/**
* @throws InvalidSyntaxException
* on Filter syntax error
*/
private void registerExistingServices() throws InvalidSyntaxException {
ServiceReference[] sr = context.getServiceReferences((String) null
, FilterUtil.generateServiceFilter(IModletFactory.class.getName()));
if (sr != null) {
for (int i = 0; i < sr.length; ++i) {
registerService(sr[i], ServiceEvent.REGISTERED);
}
}
}
/**
* @param sr ServiceReference
* @param eventType type of event
*/
private void registerService(ServiceReference sr, int eventType) {
IModletFactory factory = (IModletFactory) context.getService(sr);
validateFactory(factory);
switch (eventType) {
case ServiceEvent.REGISTERED:
if (!modletFactories.containsKey(factory.getModuleId())) {
modletFactories.put(factory.getModuleId(), new ArrayList<IModletFactory>());
} else {
logService.log(LogService.LOG_WARNING, "IModletFactory "
+ factory.getName() + " is already registered, ignoring registration.");
return;
}
List<IModletFactory> ml = modletFactories.get(factory.getModuleId());
if (!ml.contains(factory)) {
ml.add(factory);
}
logService.log(LogService.LOG_INFO, "Added modlet factory " + factory.getName()
+ " (" + factory.getModuleId() + ") to map.");
// Discovery Mode needs to know of all services a BUG contains. This
// causes all available modlets to be created and started.
if (context.getProperty("com.buglabs.bug.discoveryMode") != null
&& context.getProperty("com.buglabs.bug.discoveryMode").equals("true")) {
try {
createModlets(factory);
} catch (Exception e) {
logService.log(LogService.LOG_ERROR, "Unable to start modlet in discovery mode: " + e.getMessage());
}
}
break;
case ServiceEvent.UNREGISTERING:
if (modletFactories.containsKey(factory.getModuleId())) {
modletFactories.get(factory.getModuleId()).remove(factory);
logService.log(LogService.LOG_DEBUG, "Removed modlet factory " + factory.getName() + " to map.");
} else {
logService.log(LogService.LOG_ERROR, "Unable unregister non-existant module " + factory.getModuleId());
}
break;
default:
logService.log(LogService.LOG_ERROR, "Unhandled BMI event type " + eventType);
}
}
/*
* (non-Javadoc)
*
* @see
* org.osgi.framework.ServiceListener#serviceChanged(org.osgi.framework.
* ServiceEvent)
*/
public void serviceChanged(ServiceEvent event) {
registerService(event.getServiceReference(), event.getType());
}
/**
* Stop all active modlets.
*
* @param modlets
* stop all modlets
*/
private void stopModlets(Collection<List<IModlet>> modlets) {
for (List<IModlet> ml : modlets)
for (IModlet modlet : ml)
try {
modlet.stop();
} catch (Exception e) {
logService.log(LogService.LOG_ERROR
, "Error occured while stopping " + modlet.getModuleId() + ": " + e.getMessage());
}
}
/**
* @param factory
* factory to validate
*/
private void validateFactory(IModletFactory factory) {
if (isEmpty(factory.getModuleId())) {
throw new RuntimeException("IModletFactory has empty Module ID.");
}
if (isEmpty(factory.getName())) {
throw new RuntimeException("IModletFactory has empty Name.");
}
}
/**
* @return BundleContext
*/
public static BundleContext getContext() {
return context;
}
@Override
public void frameworkEvent(FrameworkEvent event) {
if (event.getType() == FrameworkEvent.STARTED) {
logService.log(LogService.LOG_DEBUG, "Framework has started, now handling coldplug and listening for udev events.");
try {
coldPlug();
} catch (IOException e) {
logService.log(LogService.LOG_ERROR
, "An error occured in coldplug, some devices may not be initialized.", e);
}
pipeReader.start();
}
}
}