Copyright (c) 2005, 2018 IBM Corporation and others. This program and the accompanying materials are made available under the terms of the Eclipse Public License 2.0 which accompanies this distribution, and is available at https://www.eclipse.org/legal/epl-2.0/ SPDX-License-Identifier: EPL-2.0 Contributors: IBM Corporation - initial API and implementation
/******************************************************************************* * Copyright (c) 2005, 2018 IBM Corporation and others. * * This program and the accompanying materials * are made available under the terms of the Eclipse Public License 2.0 * which accompanies this distribution, and is available at * https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * 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.)
/** * 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<String> locks = new ArrayList<>(); private static Map<String, EclipseScheduledApplication> scheduledApps = new HashMap<>(); static ArrayList<EclipseScheduledApplication> 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 ApplicationDescriptor to determine if an application is locked.
Params:
  • desc – the application descriptor
Returns:true if the application is persistently locked.
/** * 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 ApplicationDescriptor to determine lock and unlock and application.
Params:
  • desc – the application descriptor
  • locked – the locked flag
/** * 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 ScheduledApplication to persistently schedule an application launch
Params:
  • descriptor –
  • arguments –
  • topic –
  • eventFilter –
  • recurring –
Throws:
Returns:the scheduled application
/** * 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<String, Object> 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<String, Object> 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 = Integer.valueOf(nextScheduledID++).toString(); while (scheduledApps.get(result) != null && nextScheduledID < Integer.MAX_VALUE) result = Integer.valueOf(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 { try (ObjectInputStream 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()); } } } private static void loadSchedules(File schedulesData) throws IOException { try (ObjectInputStream 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 | ClassNotFoundException e) { throw new IOException(e.getMessage()); } } 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 { try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(locksData))) { out.writeInt(DATA_VERSION); out.writeInt(locks.size()); for (Iterator<String> iterLocks = locks.iterator(); iterLocks.hasNext();) out.writeUTF(iterLocks.next()); } } // must call this while holding the scheduledApps lock private static void saveSchedules(File schedulesData) throws IOException { try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(schedulesData))) { out.writeInt(DATA_VERSION); out.writeInt(scheduledApps.size()); for (Iterator<EclipseScheduledApplication> apps = scheduledApps.values().iterator(); apps.hasNext();) { EclipseScheduledApplication app = 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()); } } } 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 { @Override 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<String, Object> props = new Hashtable<>(); props.put(ScheduledApplication.YEAR, Integer.valueOf(cal.get(Calendar.YEAR))); props.put(ScheduledApplication.MONTH, Integer.valueOf(cal.get(Calendar.MONTH))); props.put(ScheduledApplication.DAY_OF_MONTH, Integer.valueOf(cal.get(Calendar.DAY_OF_MONTH))); props.put(ScheduledApplication.DAY_OF_WEEK, Integer.valueOf(cal.get(Calendar.DAY_OF_WEEK))); props.put(ScheduledApplication.HOUR_OF_DAY, Integer.valueOf(cal.get(Calendar.HOUR_OF_DAY))); props.put(ScheduledApplication.MINUTE, Integer.valueOf(minute)); Event timerEvent = new Event(ScheduledApplication.TIMER_TOPIC, (Dictionary<String, ?>) 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 = timerApps.toArray(new EclipseScheduledApplication[timerApps.size()]); } for (EclipseScheduledApplication app : apps) { try { String filterString = app.getEventFilter(); Filter filter = filterString == null ? null : FrameworkUtil.createFilter(filterString); if (filter == null || filter.match(props)) { app.handleEvent(timerEvent); } } catch (Throwable t) { String message = NLS.bind(Messages.scheduled_app_launch_error, app.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); } } @Override 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; } @Override public void modifiedService(ServiceReference reference, Object service) { // don't care } @Override public void removedService(ServiceReference reference, Object service) { if (service == configLocation) configLocation = null; } }