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;
public class AppPersistence implements ServiceTrackerCustomizer {
private static final String PROP_CONFIG_AREA = "osgi.configuration.area";
private static final String FILTER_PREFIX = "(&(objectClass=org.eclipse.osgi.service.datalocation.Location)(type=";
private static final String FILE_APPLOCKS = ".locks";
private static final String FILE_APPSCHEDULED = ".scheduled";
private static final String EVENT_HANDLER = "org.osgi.service.event.EventHandler";
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();
Filter filter = null;
try {
filter = context.createFilter(FILTER_PREFIX + PROP_CONFIG_AREA + "))");
} catch (InvalidSyntaxException e) {
}
configTracker = new ServiceTracker(context, filter, new AppPersistence());
configTracker.open();
}
private static void closeConfiguration() {
if (configTracker != null)
configTracker.close();
configTracker = null;
}
public static boolean isLocked(ApplicationDescriptor desc) {
synchronized (locks) {
return locks.contains(desc.getApplicationId());
}
}
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);
}
}
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");
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;
}
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);
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");
return result;
}
private static boolean checkSchedulingSupport() {
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);
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);
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));
}
}
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());
}
}
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");
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);
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;
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) {
}
}
}
}
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;
configLocation = (Location) context.getService(reference);
loadData(FILE_APPLOCKS);
loadData(FILE_APPSCHEDULED);
return configLocation;
}
@Override
public void modifiedService(ServiceReference reference, Object service) {
}
@Override
public void removedService(ServiceReference reference, Object service) {
if (service == configLocation)
configLocation = null;
}
}