/** * Copyright (c) 2012 - 2018 Data In Motion 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: * Data In Motion - initial API and implementation */ package org.gecko.runtime.application.internal; import java.util.Collections; import java.util.HashSet; import java.util.Hashtable; import java.util.LinkedHashSet; import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import java.util.logging.Level; import java.util.logging.Logger; import java.util.stream.Collectors; import org.gecko.runtime.application.ApplicationManager; import org.gecko.runtime.application.ApplicationScheduler; import org.osgi.framework.BundleContext; import org.osgi.framework.Filter; import org.osgi.framework.InvalidSyntaxException; import org.osgi.framework.ServiceRegistration; import org.osgi.service.application.ApplicationAdminPermission; import org.osgi.service.application.ApplicationDescriptor; import org.osgi.service.application.ApplicationException; import org.osgi.service.application.ApplicationHandle; import org.osgi.service.application.ScheduledApplication; import org.osgi.service.component.ComponentContext; import org.osgi.service.component.annotations.Activate; import org.osgi.service.component.annotations.Component; import org.osgi.service.component.annotations.Deactivate; import org.osgi.service.component.annotations.Reference; import org.osgi.service.component.annotations.ReferenceCardinality; import org.osgi.service.component.annotations.ReferencePolicy; import org.osgi.service.event.EventConstants; /** * Gecko application container * @author Mark Hoffmann * @since 23.03.2018 */ @Component(service=ApplicationManager.class, immediate=true) public class GeckoApplicationContainer implements ApplicationScheduler, ApplicationManager { private static Logger logger = Logger.getLogger("org.gecko.applicationContainer"); private static final String EVENT_HANDLER = "org.osgi.service.event.EventHandler"; //$NON-NLS-1$ private Set descriptors = new LinkedHashSet<>(); private Set activeHandles = new LinkedHashSet<>(); private Map scheduledApplications = new ConcurrentHashMap<>(); private Set timerApplications = new LinkedHashSet<>(); private ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(4); private ServiceRegistration schedulerRegistration; private BundleContext ctx; private volatile int nextScheduledID; private ScheduledFuture scheduledFuture = null; @Activate public void activate(ComponentContext context, Map properties) { ctx = context.getBundleContext(); schedulerRegistration = ctx.registerService(ApplicationScheduler.class, this, null); } @Deactivate public void deactivate() { stopTimer(); scheduledExecutorService.shutdown(); schedulerRegistration.unregister(); } /** * Adds an {@link ApplicationDescriptor} to the registry * @param descriptor the descriptor to be added */ @Reference(policy=ReferencePolicy.DYNAMIC, cardinality=ReferenceCardinality.MULTIPLE, unbind="removeApplicationDescriptor") public void addApplicationDescriptor(ApplicationDescriptor descriptor) { synchronized (descriptors) { descriptors.add(descriptor); } } /** * Removes an {@link ApplicationDescriptor} from the registry * @param descriptor the descriptor to be removed */ public void removeApplicationDescriptor(ApplicationDescriptor descriptor) { synchronized (descriptors) { descriptors.add(descriptor); } } /* * (non-Javadoc) * @see org.gecko.application.ApplicationScheduler#schedule(org.osgi.service.application.ApplicationDescriptor, java.lang.String, java.util.Map, java.lang.String, java.lang.String, boolean) */ @Override public ScheduledApplication schedule(ApplicationDescriptor descriptor, String scheduleId, Map arguments, String topic, String eventFilter, boolean recurring) throws InvalidSyntaxException, ApplicationException { ctx.createFilter(eventFilter); SecurityManager sm = System.getSecurityManager(); if( sm != null ) { sm.checkPermission(new ApplicationAdminPermission( descriptor, ApplicationAdminPermission.SCHEDULE_ACTION)); } GeckoScheduledApplication result; synchronized (scheduledApplications) { String nextScheduleId = getNextScheduledID(scheduleId); result = new GeckoScheduledApplication(ctx, nextScheduleId, descriptor.getApplicationId(), arguments, topic, eventFilter, recurring); addScheduledApp(result); // saveData(FILE_APPSCHEDULED); } return result; } /* * (non-Javadoc) * @see org.gecko.application.ApplicationScheduler#unschedule(java.lang.String) */ @Override public void unschedule(String scheduleId) throws InvalidSyntaxException, ApplicationException { GeckoScheduledApplication gsa = scheduledApplications.get(scheduleId); if (gsa != null) { removeScheduledApp(gsa); } } /** * Adds a scheduled application and start the timer or event handler * @param scheduledApplication the application to be scheduled */ private void addScheduledApp(GeckoScheduledApplication scheduledApplication) { if (ScheduledApplication.TIMER_TOPIC.equals(scheduledApplication.getTopic())) { synchronized (timerApplications) { timerApplications.add(scheduledApplication); startTimer(); } } scheduledApplications.put(scheduledApplication.getScheduleId(), scheduledApplication); Hashtable serviceProps = new Hashtable<>(); if (scheduledApplication.getTopic() != null) serviceProps.put(EventConstants.EVENT_TOPIC, new String[] {scheduledApplication.getTopic()}); if (scheduledApplication.getEventFilter() != null) serviceProps.put(EventConstants.EVENT_FILTER, scheduledApplication.getEventFilter()); serviceProps.put(ScheduledApplication.SCHEDULE_ID, scheduledApplication.getScheduleId()); serviceProps.put(ScheduledApplication.APPLICATION_PID, scheduledApplication.getApplicationPid()); ServiceRegistration scheduledRegistration = ctx.registerService(new String[] {ScheduledApplication.class.getName(), EVENT_HANDLER}, scheduledApplication, serviceProps); scheduledApplication.setServiceRegistration(scheduledRegistration); } /** * Removes a scheduled application * @param scheduledApplication the scheduled application to be removed */ private void removeScheduledApp(GeckoScheduledApplication scheduledApplication) { boolean removed; synchronized (scheduledApplications) { removed = scheduledApplications.remove(scheduledApplication.getScheduleId()) != null; // if (removed) { // saveData(FILE_APPSCHEDULED); // } } if (removed) { synchronized (timerApplications) { timerApplications.remove(scheduledApplication); if (timerApplications.isEmpty()) { stopTimer(); } } } } /** * Starts the timer */ private void startTimer() { if (scheduledFuture == null) { ScheduledApplicationTimer timer = new ScheduledApplicationTimer(scheduledApplications.values()); scheduledFuture = scheduledExecutorService.scheduleAtFixedRate(timer, 10, 30, TimeUnit.SECONDS); } } /** * Stopps the timer */ private void stopTimer() { if (scheduledFuture != null && !scheduledFuture.isCancelled() && !scheduledFuture.isDone()) { scheduledFuture.cancel(true); } scheduledFuture = null; } /* * (non-Javadoc) * @see org.gecko.application.ApplicationManager#getAllApplications() */ @Override public Set getAllApplications() { return getCurrentDescriptor() .stream() .map(ApplicationDescriptor::getApplicationId) .collect(Collectors.toSet()); } /* * (non-Javadoc) * @see org.gecko.application.ApplicationManager#getActiveApplications() */ @Override public Set getActiveApplications() { Set current; synchronized (activeHandles) { current = new HashSet(activeHandles); } return current .stream() .filter(h->h.getState().equals(ApplicationHandle.RUNNING)) .map(ApplicationHandle::getApplicationDescriptor) .map(ApplicationDescriptor::getApplicationId) .collect(Collectors.toSet()); } /* * (non-Javadoc) * @see org.gecko.application.ApplicationManager#lockApplication(java.lang.String) */ @Override public boolean lockApplication(String applicationId) { Optional o = getCurrentDescriptor() .stream() .filter(d->d.getApplicationId().contentEquals(applicationId)) .findFirst(); boolean locked = o.isPresent(); o.ifPresent(ApplicationDescriptor::lock); return locked; } /* * (non-Javadoc) * @see org.gecko.application.ApplicationManager#unlockApplication(java.lang.String) */ @Override public boolean unlockApplication(String applicationId) { Optional o = getCurrentDescriptor() .stream() .filter(d->d.getApplicationId().contentEquals(applicationId)) .findFirst(); boolean unlocked = o.isPresent(); o.ifPresent(ApplicationDescriptor::unlock); return unlocked; } /* * (non-Javadoc) * @see org.gecko.application.ApplicationManager#startApplication(java.lang.String, java.util.Map) */ @Override public boolean startApplication(String applicationId, final Map launchProperties) { Optional o = getCurrentDescriptor() .stream() .filter(d->d.getApplicationId().contentEquals(applicationId)) .findFirst(); AtomicBoolean started = new AtomicBoolean(o.isPresent()); o.ifPresent(ad->{ try { ApplicationHandle h = ad.launch(launchProperties); started.compareAndSet(false, true); activeHandles.add(h); logger.info("[" + applicationId + "] Started application successfully"); } catch (Exception e) { logger.log(Level.SEVERE, "[" + applicationId + "] Not able to start application", e); } }); return started.get(); } /* * (non-Javadoc) * @see org.gecko.application.ApplicationManager#stopApplication(java.lang.String) */ @Override public boolean stopApplication(String applicationId) { Optional o = getCurrentHandles() .stream() .filter(h->h.getApplicationDescriptor().getApplicationId().contentEquals(applicationId)) .findFirst(); AtomicBoolean stopped = new AtomicBoolean(o.isPresent()); o.ifPresent(ah->{ try { synchronized (activeHandles) { if (activeHandles.remove(ah)) { ah.destroy(); stopped.compareAndSet(false, true); } } logger.info("[" + ah.getInstanceId() + "] Removed application handle successfully"); } catch (Exception e) { logger.log(Level.SEVERE, "[" + ah.getInstanceId() + "] Not able to remove application handle", e); } }); return stopped.get(); } /* * (non-Javadoc) * @see org.gecko.application.ApplicationManager#scheduleApplication(java.lang.String, java.util.Map, org.osgi.framework.Filter) */ @Override public ScheduledApplication scheduleApplication(String applicationId, Map launchProperties, Filter scheduleFilter) { // TODO Auto-generated method stub return null; } /* * (non-Javadoc) * @see org.gecko.application.ApplicationManager#unscheduleApplication(java.lang.String) */ @Override public boolean unscheduleApplication(String applicationId) { // TODO Auto-generated method stub return false; } /** * Returns the current {@link Set} of descriptors * @return the current {@link Set} of descriptors */ private Set getCurrentDescriptor() { synchronized (descriptors) { return Collections.unmodifiableSet(descriptors); } } /** * Returns the current {@link Set} of handles * @return the current {@link Set} of handles */ private Set getCurrentHandles() { synchronized (activeHandles) { return Collections.unmodifiableSet(activeHandles); } } private String getNextScheduledID(String scheduledId) throws ApplicationException { if (scheduledId != null) { if (scheduledApplications.containsKey(scheduledId)) throw new ApplicationException(ApplicationException.APPLICATION_DUPLICATE_SCHEDULE_ID, "Duplicate scheduled ID: " + scheduledId); //$NON-NLS-1$ return scheduledId; } if (nextScheduledID == Integer.MAX_VALUE) { nextScheduledID = 0; } AtomicInteger intResult = new AtomicInteger(nextScheduledID++); scheduledApplications.keySet().stream().filter(s->s.equals(Integer.valueOf(intResult.get()).toString()) && nextScheduledID < Integer.MAX_VALUE).forEach((s)->intResult.incrementAndGet()); String result = Integer.valueOf(intResult.get()).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; } }