/******************************************************************************* * 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; } }