blob: 055ea77b39d23b749b7015bb0a52b9f30282c38a [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2005, 2009 IBM Corporation and others.
* 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:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.equinox.internal.app;
import java.io.*;
import java.util.*;
import org.eclipse.osgi.framework.log.FrameworkLogEntry;
import org.eclipse.osgi.service.datalocation.Location;
import org.eclipse.osgi.storagemanager.StorageManager;
import org.eclipse.osgi.util.NLS;
import org.osgi.framework.*;
import org.osgi.service.application.*;
import org.osgi.service.event.Event;
import org.osgi.service.event.EventConstants;
import org.osgi.util.tracker.ServiceTracker;
import org.osgi.util.tracker.ServiceTrackerCustomizer;
/**
* Manages all persistent data for ApplicationDescriptors (lock status,
* scheduled applications etc.)
*/
public class AppPersistence implements ServiceTrackerCustomizer {
private static final String PROP_CONFIG_AREA = "osgi.configuration.area"; //$NON-NLS-1$
private static final String FILTER_PREFIX = "(&(objectClass=org.eclipse.osgi.service.datalocation.Location)(type="; //$NON-NLS-1$
private static final String FILE_APPLOCKS = ".locks"; //$NON-NLS-1$
private static final String FILE_APPSCHEDULED = ".scheduled"; //$NON-NLS-1$
private static final String EVENT_HANDLER = "org.osgi.service.event.EventHandler"; //$NON-NLS-1$
private static final int DATA_VERSION = 2;
private static final byte NULL = 0;
private static final int OBJECT = 1;
private static BundleContext context;
private static ServiceTracker configTracker;
private static Location configLocation;
private static Collection locks = new ArrayList();
private static Map scheduledApps = new HashMap();
static ArrayList timerApps = new ArrayList();
private static StorageManager storageManager;
private static boolean scheduling = false;
static boolean shutdown = false;
private static int nextScheduledID = 1;
private static Thread timerThread;
static void start(BundleContext bc) {
context = bc;
shutdown = false;
initConfiguration();
}
static void stop() {
shutdown = true;
stopTimer();
if (storageManager != null) {
storageManager.close();
storageManager = null;
}
closeConfiguration();
context = null;
}
private static void initConfiguration() {
closeConfiguration(); // just incase
Filter filter = null;
try {
filter = context.createFilter(FILTER_PREFIX + PROP_CONFIG_AREA + "))"); //$NON-NLS-1$
} catch (InvalidSyntaxException e) {
// ignore this. It should never happen as we have tested the above format.
}
configTracker = new ServiceTracker(context, filter, new AppPersistence());
configTracker.open();
}
private static void closeConfiguration() {
if (configTracker != null)
configTracker.close();
configTracker = null;
}
/**
* Used by {@link ApplicationDescriptor} to determine if an application is locked.
* @param desc the application descriptor
* @return true if the application is persistently locked.
*/
public static boolean isLocked(ApplicationDescriptor desc) {
synchronized (locks) {
return locks.contains(desc.getApplicationId());
}
}
/**
* Used by {@link ApplicationDescriptor} to determine lock and unlock and application.
* @param desc the application descriptor
* @param locked the locked flag
*/
public static void saveLock(ApplicationDescriptor desc, boolean locked) {
synchronized (locks) {
if (locked) {
if (!locks.contains(desc.getApplicationId())) {
locks.add(desc.getApplicationId());
saveData(FILE_APPLOCKS);
}
} else if (locks.remove(desc.getApplicationId())) {
saveData(FILE_APPLOCKS);
}
}
}
static void removeScheduledApp(EclipseScheduledApplication scheduledApp) {
boolean removed;
synchronized (scheduledApps) {
removed = scheduledApps.remove(scheduledApp.getScheduleId()) != null;
if (removed) {
saveData(FILE_APPSCHEDULED);
}
}
if (removed)
synchronized (timerApps) {
timerApps.remove(scheduledApp);
}
}
/**
* Used by {@link ScheduledApplication} to persistently schedule an application launch
* @param descriptor
* @param arguments
* @param topic
* @param eventFilter
* @param recurring
* @return the scheduled application
* @throws InvalidSyntaxException
* @throws ApplicationException
*/
public static ScheduledApplication addScheduledApp(ApplicationDescriptor descriptor, String scheduleId, Map arguments, String topic, String eventFilter, boolean recurring) throws InvalidSyntaxException, ApplicationException {
if (!scheduling && !checkSchedulingSupport())
throw new ApplicationException(ApplicationException.APPLICATION_SCHEDULING_FAILED, "Cannot support scheduling without org.osgi.service.event package"); //$NON-NLS-1$
// check the event filter for correct syntax
context.createFilter(eventFilter);
EclipseScheduledApplication result;
synchronized (scheduledApps) {
result = new EclipseScheduledApplication(context, getNextScheduledID(scheduleId), descriptor.getApplicationId(), arguments, topic, eventFilter, recurring);
addScheduledApp(result);
saveData(FILE_APPSCHEDULED);
}
return result;
}
// must call this method while holding the scheduledApps lock
private static void addScheduledApp(EclipseScheduledApplication scheduledApp) {
if (ScheduledApplication.TIMER_TOPIC.equals(scheduledApp.getTopic())) {
synchronized (timerApps) {
timerApps.add(scheduledApp);
if (timerThread == null)
startTimer();
}
}
scheduledApps.put(scheduledApp.getScheduleId(), scheduledApp);
Hashtable serviceProps = new Hashtable();
if (scheduledApp.getTopic() != null)
serviceProps.put(EventConstants.EVENT_TOPIC, new String[] {scheduledApp.getTopic()});
if (scheduledApp.getEventFilter() != null)
serviceProps.put(EventConstants.EVENT_FILTER, scheduledApp.getEventFilter());
serviceProps.put(ScheduledApplication.SCHEDULE_ID, scheduledApp.getScheduleId());
serviceProps.put(ScheduledApplication.APPLICATION_PID, scheduledApp.getAppPid());
ServiceRegistration sr = context.registerService(new String[] {ScheduledApplication.class.getName(), EVENT_HANDLER}, scheduledApp, serviceProps);
scheduledApp.setServiceRegistration(sr);
}
private static String getNextScheduledID(String scheduledId) throws ApplicationException {
if (scheduledId != null) {
if (scheduledApps.get(scheduledId) != null)
throw new ApplicationException(ApplicationException.APPLICATION_DUPLICATE_SCHEDULE_ID, "Duplicate scheduled ID: " + scheduledId); //$NON-NLS-1$
return scheduledId;
}
if (nextScheduledID == Integer.MAX_VALUE)
nextScheduledID = 0;
String result = new Integer(nextScheduledID++).toString();
while (scheduledApps.get(result) != null && nextScheduledID < Integer.MAX_VALUE)
result = new Integer(nextScheduledID++).toString();
if (nextScheduledID == Integer.MAX_VALUE)
throw new ApplicationException(ApplicationException.APPLICATION_DUPLICATE_SCHEDULE_ID, "Maximum number of scheduled applications reached"); //$NON-NLS-1$
return result;
}
private static boolean checkSchedulingSupport() {
// cannot support scheduling without the event admin package
try {
Class.forName(EVENT_HANDLER);
scheduling = true;
return true;
} catch (ClassNotFoundException e) {
scheduling = false;
return false;
}
}
private synchronized static boolean loadData(String fileName) {
try {
Location location = configLocation;
if (location == null)
return false;
File theStorageDir = new File(location.getURL().getPath() + '/' + Activator.PI_APP);
if (storageManager == null) {
boolean readOnly = location.isReadOnly();
storageManager = new StorageManager(theStorageDir, readOnly ? "none" : null, readOnly); //$NON-NLS-1$
storageManager.open(!readOnly);
}
File dataFile = storageManager.lookup(fileName, false);
if (dataFile == null || !dataFile.isFile()) {
Location parent = location.getParentLocation();
if (parent != null) {
theStorageDir = new File(parent.getURL().getPath() + '/' + Activator.PI_APP);
StorageManager tmp = new StorageManager(theStorageDir, "none", true); //$NON-NLS-1$
tmp.open(false);
dataFile = tmp.lookup(fileName, false);
tmp.close();
}
}
if (dataFile == null || !dataFile.isFile())
return true;
if (FILE_APPLOCKS.equals(fileName))
loadLocks(dataFile);
else if (FILE_APPSCHEDULED.equals(fileName))
loadSchedules(dataFile);
} catch (IOException e) {
return false;
}
return true;
}
private static void loadLocks(File locksData) throws IOException {
ObjectInputStream in = null;
try {
in = new ObjectInputStream(new FileInputStream(locksData));
int dataVersion = in.readInt();
if (dataVersion != DATA_VERSION)
return;
int numLocks = in.readInt();
synchronized (locks) {
for (int i = 0; i < numLocks; i++)
locks.add(in.readUTF());
}
} finally {
if (in != null)
in.close();
}
}
private static void loadSchedules(File schedulesData) throws IOException {
ObjectInputStream in = null;
try {
in = new ObjectInputStream(new FileInputStream(schedulesData));
int dataVersion = in.readInt();
if (dataVersion != DATA_VERSION)
return;
int numScheds = in.readInt();
for (int i = 0; i < numScheds; i++) {
String id = readString(in, false);
String appPid = readString(in, false);
String topic = readString(in, false);
String eventFilter = readString(in, false);
boolean recurring = in.readBoolean();
Map args = (Map) in.readObject();
EclipseScheduledApplication schedApp = new EclipseScheduledApplication(context, id, appPid, args, topic, eventFilter, recurring);
addScheduledApp(schedApp);
}
} catch (InvalidSyntaxException e) {
throw new IOException(e.getMessage());
} catch (NoClassDefFoundError e) {
throw new IOException(e.getMessage());
} catch (ClassNotFoundException e) {
throw new IOException(e.getMessage());
} finally {
if (in != null)
in.close();
}
}
private synchronized static void saveData(String fileName) {
if (storageManager == null || storageManager.isReadOnly())
return;
try {
File data = storageManager.createTempFile(fileName);
if (FILE_APPLOCKS.equals(fileName))
saveLocks(data);
else if (FILE_APPSCHEDULED.equals(fileName))
saveSchedules(data);
storageManager.lookup(fileName, true);
storageManager.update(new String[] {fileName}, new String[] {data.getName()});
} catch (IOException e) {
Activator.log(new FrameworkLogEntry(Activator.PI_APP, FrameworkLogEntry.ERROR, 0, NLS.bind(Messages.persistence_error_saving, fileName), 0, e, null));
}
}
// must call this while holding the locks lock
private static void saveLocks(File locksData) throws IOException {
ObjectOutputStream out = null;
try {
out = new ObjectOutputStream(new FileOutputStream(locksData));
out.writeInt(DATA_VERSION);
out.writeInt(locks.size());
for (Iterator iterLocks = locks.iterator(); iterLocks.hasNext();)
out.writeUTF((String) iterLocks.next());
} finally {
if (out != null)
out.close();
}
}
// must call this while holding the scheduledApps lock
private static void saveSchedules(File schedulesData) throws IOException {
ObjectOutputStream out = null;
try {
out = new ObjectOutputStream(new FileOutputStream(schedulesData));
out.writeInt(DATA_VERSION);
out.writeInt(scheduledApps.size());
for (Iterator apps = scheduledApps.values().iterator(); apps.hasNext();) {
EclipseScheduledApplication app = (EclipseScheduledApplication) apps.next();
writeStringOrNull(out, app.getScheduleId());
writeStringOrNull(out, app.getAppPid());
writeStringOrNull(out, app.getTopic());
writeStringOrNull(out, app.getEventFilter());
out.writeBoolean(app.isRecurring());
out.writeObject(app.getArguments());
}
} finally {
if (out != null)
out.close();
}
}
private static void startTimer() {
timerThread = new Thread(new AppTimer(), "app schedule timer"); //$NON-NLS-1$
timerThread.start();
}
private static void stopTimer() {
if (timerThread != null)
timerThread.interrupt();
timerThread = null;
}
static class AppTimer implements Runnable {
public void run() {
int lastMin = -1;
while (!shutdown) {
try {
Thread.sleep(30000); // sleeping 30 secs instead of 60 to try to avoid skipping minutes
Calendar cal = Calendar.getInstance();
int minute = cal.get(Calendar.MINUTE);
if (minute == lastMin)
continue;
lastMin = minute;
Hashtable props = new Hashtable();
props.put(ScheduledApplication.YEAR, new Integer(cal.get(Calendar.YEAR)));
props.put(ScheduledApplication.MONTH, new Integer(cal.get(Calendar.MONTH)));
props.put(ScheduledApplication.DAY_OF_MONTH, new Integer(cal.get(Calendar.DAY_OF_MONTH)));
props.put(ScheduledApplication.DAY_OF_WEEK, new Integer(cal.get(Calendar.DAY_OF_WEEK)));
props.put(ScheduledApplication.HOUR_OF_DAY, new Integer(cal.get(Calendar.HOUR_OF_DAY)));
props.put(ScheduledApplication.MINUTE, new Integer(minute));
Event timerEvent = new Event(ScheduledApplication.TIMER_TOPIC, (Dictionary) props);
EclipseScheduledApplication[] apps = null;
// poor mans implementation of dispatching events; the spec will not allow us to use event admin to dispatch the virtual timer events; boo!!
synchronized (timerApps) {
if (timerApps.size() == 0)
continue;
apps = (EclipseScheduledApplication[]) timerApps.toArray(new EclipseScheduledApplication[timerApps.size()]);
}
for (int i = 0; i < apps.length; i++) {
try {
String filterString = apps[i].getEventFilter();
Filter filter = filterString == null ? null : FrameworkUtil.createFilter(filterString);
if (filter == null || filter.match(props))
apps[i].handleEvent(timerEvent);
} catch (Throwable t) {
String message = NLS.bind(Messages.scheduled_app_launch_error, apps[i].getAppPid());
Activator.log(new FrameworkLogEntry(Activator.PI_APP, FrameworkLogEntry.WARNING, 0, message, 0, t, null));
}
}
} catch (InterruptedException e) {
// do nothing;
}
}
}
}
private static String readString(ObjectInputStream in, boolean intern) throws IOException {
byte type = in.readByte();
if (type == NULL)
return null;
return intern ? in.readUTF().intern() : in.readUTF();
}
private static void writeStringOrNull(ObjectOutputStream out, String string) throws IOException {
if (string == null)
out.writeByte(NULL);
else {
out.writeByte(OBJECT);
out.writeUTF(string);
}
}
public Object addingService(ServiceReference reference) {
if (configLocation != null)
return null; // only care about one configuration
configLocation = (Location) context.getService(reference);
loadData(FILE_APPLOCKS);
loadData(FILE_APPSCHEDULED);
return configLocation;
}
public void modifiedService(ServiceReference reference, Object service) {
// don't care
}
public void removedService(ServiceReference reference, Object service) {
if (service == configLocation)
configLocation = null;
}
}