All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.ogema.application.manager.impl.SimulationClock Maven / Gradle / Ivy

Go to download

Application Manager Package of the OGEMA reference implementation by Fraunhofer Society.

The newest version!
/**
 * Copyright 2011-2018 Fraunhofer-Gesellschaft zur Förderung der angewandten Wissenschaften e.V.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.ogema.application.manager.impl;

import java.io.IOException;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.Dictionary;
import java.util.Hashtable;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;

import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Service;
import org.ogema.core.administration.FrameworkClock;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceReference;
import org.osgi.service.cm.Configuration;
import org.osgi.service.cm.ConfigurationAdmin;
import org.osgi.service.component.ComponentContext;
import org.osgi.service.component.ComponentException;
import org.slf4j.LoggerFactory;

/**
 * 
 * @author jlapp
 */
@Component(
		immediate = true,
		configurationPid=SimulationClock.SERVICE_PID
)
@Service(FrameworkClock.class)
public class SimulationClock implements FrameworkClock {

	final static String SERVICE_PID = "org.ogema.application.manager.impl.SimulationClock";
	private volatile BundleContext ctx;
	protected volatile long startTimeSystem;
	protected volatile long startTimeFramework;
	protected volatile float simulationFactor = 1.0f;
	// required to check whether config properties changed
	private Object startTimeFrameworkProp;
	private Object startTimeSystemProp;
	protected java.beans.PropertyChangeSupport propertyListeners;
    protected ConcurrentLinkedQueue listeners = new ConcurrentLinkedQueue<>();
    
    final class ClockEvent implements ClockChangedEvent {
        
        final float factor;

        public ClockEvent(float factor) {
            this.factor = factor;
        }
        
        @Override
        public float getSimulationFactor() {
            return factor;
        }

        @Override
        public FrameworkClock getClock() {
            return SimulationClock.this;
        }
    }

	/**
	 * System or configuration property ({@value} ) that can be used to disable this clock (set to {@code true}).
	 */
	public static final String DISABLE = "org.ogema.defaultclock.disable";
	
	/**
	 * System property to set initial framework time on clean start
	 */
	public static final String TIMESTAMP = "org.ogema.defaultclock.timestamp";
	
	/**
	 * System property to set initial simulation factor
	 */
	public static final String SIMULATION_FACTOR = "org.ogema.defaultclock.speedfactor";

	protected synchronized void activate(ComponentContext ctx, Map config) {
		this.ctx = ctx.getBundleContext();
		boolean disabledInProperties = Boolean.getBoolean(DISABLE);
		boolean disabledInConfig = Boolean.valueOf(String.valueOf(config.get(DISABLE)));
		if (disabledInProperties) {
			throw new ComponentException("disabled by system property.");
		}
		if (disabledInConfig) {
			throw new ComponentException("disabled by component configuration.");
		}
		startTimeSystem = System.currentTimeMillis();
		this.simulationFactor = getSimFactor(config);
		this.startTimeFrameworkProp = config.get("startTimeFramework");
		this.startTimeSystemProp = config.get("startTimeSystem");
		final Long startTimeFramework0 = getStartTimeFramework(config, startTimeSystem, simulationFactor);
		if (startTimeFramework0 != null) {
			this.startTimeFramework = startTimeFramework0;
		}
		else {  // or from system property, or use default: system time
			startTimeFramework = Long.getLong(TIMESTAMP, startTimeSystem);
			// if only the sim factor is set via config admin we need to persist the start times for the next framework start
			if (simulationFactor != 1.0F) {
				persistTimeConfig();
			}
		}
		if (startTimeFramework != startTimeSystem)
			LoggerFactory.getLogger(SimulationClock.class).info("Starting framework at simulated time {}", new java.util.Date(startTimeFramework));
		if (simulationFactor != 1.0F) 			
			LoggerFactory.getLogger(SimulationClock.class).info("Starting framework with simulation factor {}", simulationFactor);
	}
	
	@org.apache.felix.scr.annotations.Modified
	protected synchronized void modified(ComponentContext ctx, Map config) {
		// avoid component restarts when we update the configuration properties... need to check if props changed
		final float simFactor = getSimFactor(config);
		final double diff = Math.abs(simFactor - this.simulationFactor);
		final double sum = simFactor + this.simulationFactor;
		boolean changed = diff > sum / 100000;
		if (!changed && (!Objects.equals(config.get("startTimeSystem"), startTimeSystemProp) ||
					!Objects.equals(config.get("startTimeFramework"), startTimeFrameworkProp))) {
			changed = true;
		}
		if (changed) {
			final Long frameworkStart0 = getStartTimeFramework(config, startTimeSystem, simFactor);
			final long frameworkStart = frameworkStart0 != null ? frameworkStart0 : getExecutionTime();
			final Object systemTime0 = frameworkStart0 != null ? config.get("systemTime") : null;
			final long systemTime = systemTime0 instanceof Long ? (Long) systemTime0 : System.currentTimeMillis();
			this.startTimeSystemProp = config.get("startTimeSystem");
			this.startTimeFrameworkProp = config.get("startTimeFramework");
			LoggerFactory.getLogger(SimulationClock.class).info("Framework clock properties changed: start time: {}, simulation factor {}", 
					new java.util.Date(frameworkStart), simFactor);
			setSimulationTimeAndFactorInternal(frameworkStart, simFactor, systemTime, false);
		}
	}

	// note: this is only executed on a proper shutdown
	protected void deactivate(ComponentContext ctx, Map config) {
		this.ctx = null;
	}
	
	// persistence is only used if simulation clock deviates from system clock, i.e. if simulation factor 
	// or timestamp have been set via the admin interface
	// requires sync on this
	private void persistTimeConfig() {
		ServiceReference sr = null;
		try {
			sr = ctx.getServiceReference(ConfigurationAdmin.class);
		} catch (IllegalStateException | NullPointerException e) {}
		if (sr == null) {
			LoggerFactory.getLogger(SimulationClock.class).warn("Configuration admin not found... cannot persist time information");
			return;
		}
		final ConfigurationAdmin configAdmin = ctx.getService(sr);
		try {
			Configuration cfg = configAdmin.getConfiguration(SERVICE_PID);
			Dictionary props = cfg.getProperties();
			if (props == null) {
				props = new Hashtable<>();
			}
			props.put("startTimeSystem", startTimeSystem);
			props.put("startTimeFramework", startTimeFramework);
			props.put("simulationFactor", simulationFactor);
			cfg.update(props);
			this.startTimeFrameworkProp = Long.valueOf(startTimeFramework);
			this.startTimeSystemProp = Long.valueOf(startTimeSystem);
		} catch (IOException e) { // TODO
			e.printStackTrace();
		} finally {
			ctx.ungetService(sr);
		}
	}

	@Override
	public long getExecutionTime() {
		long elapsedSystemTime = System.currentTimeMillis() - startTimeSystem;
		return startTimeFramework + (long) Math.floor(elapsedSystemTime * simulationFactor);
	}

	@Override
	public String getName() {
		return getClass().getSimpleName();
	}

	@Override
	public float getSimulationFactor() {
		return simulationFactor;
	}
	
	@Override
	public boolean setSimulationTimeAndFactor(long timestamp, float factor) {
		return setSimulationTimeAndFactorInternal(timestamp, factor, null, true);
	}
	
	private synchronized boolean setSimulationTimeAndFactorInternal(long timestamp, float factor, 
			Long systemTime, boolean doPersist) {
		final float oldFactor = this.simulationFactor;
		final long oldT = getExecutionTime();
		final boolean result = setSimulationTimeInternal(timestamp, systemTime) | setSimulationFactorInternal(factor);
		if (propertyListeners != null) {
            propertyListeners.firePropertyChange(EXECUTION_TIME_CHANGED_PROPERTY, oldT, timestamp);
            propertyListeners.firePropertyChange(SIMULATION_FACTOR_CHANGED_PROPERTY, oldFactor, simulationFactor);
        }
		dispatchClockChangedEvent(new ClockEvent(simulationFactor));
        if (doPersist)
        	persistTimeConfig();
		return result;
	}
	
	private boolean setSimulationTimeInternal(final long timestamp, Long systemTime) {
		this.startTimeFramework = timestamp;
		this.startTimeSystem = systemTime != null ? systemTime : System.currentTimeMillis();
		return true;
	}
	
	private boolean setSimulationFactorInternal(final float simulationFactor) {
		if (simulationFactor < 0) {
			throw new IllegalArgumentException("illegal simulation factor: " + simulationFactor);
		}
		this.startTimeFramework = getExecutionTime();
		this.startTimeSystem = System.currentTimeMillis();
		this.simulationFactor = simulationFactor;
		return true;
	}
	
	@Override
	public synchronized boolean setSimulationTime(long timestamp) {
		final long oldTimestamp = getExecutionTime();
		final boolean result  = setSimulationTimeInternal(timestamp, null);
        if (propertyListeners != null) {
            propertyListeners.firePropertyChange(EXECUTION_TIME_CHANGED_PROPERTY, oldTimestamp, timestamp);
        }
        dispatchClockChangedEvent(new ClockEvent(simulationFactor));
        persistTimeConfig();
		return result;
	}

	@Override
    @SuppressWarnings("deprecation")
	public synchronized boolean setSimulationFactor(final float simulationFactor) {
		float oldFactor = this.simulationFactor;
		final boolean result = setSimulationFactorInternal(simulationFactor);
        if (propertyListeners != null) {
            propertyListeners.firePropertyChange(SIMULATION_FACTOR_CHANGED_PROPERTY, oldFactor, simulationFactor);
        }
        dispatchClockChangedEvent(new ClockEvent(simulationFactor));
        persistTimeConfig();
		return result;
	}

	@Override
    @Deprecated
	public synchronized void addPropertyChangeListener(java.beans.PropertyChangeListener listener) {
        if (propertyListeners == null) {
            propertyListeners = new java.beans.PropertyChangeSupport(this);
        }
		propertyListeners.addPropertyChangeListener(listener);
	}

	@Override
    @Deprecated
	public void removePropertyChangeListener(java.beans.PropertyChangeListener listener) {
        if (propertyListeners != null) {
            propertyListeners.removePropertyChangeListener(listener);
        }
	}

    @Override
    public void addClockChangeListener(ClockChangeListener l) {
        listeners.add(l);
    }

    @Override
    public void removeClockChangeListener(ClockChangeListener l) {
        listeners.remove(l);
    }
    
    private void dispatchClockChangedEvent(final ClockChangedEvent e) {
    	if (listeners.isEmpty())
    		return;
		final ExecutorService exec = Executors.newSingleThreadExecutor(new ThreadFactory() {
			
			@Override
			public Thread newThread(Runnable r) {
				return new Thread(r, "clock-listener-dispatch");
			}
		});
		try {
			for (ClockChangeListener l: listeners) {
				exec.submit(new ClockListenerCallback(l, e));
			}
		} finally {
			try {
				AccessController.doPrivileged(new PrivilegedAction() {
	
					@Override
					public Void run() {
						exec.shutdown();
						return null;
					}
					
				});
			} catch (SecurityException ee) {
				ee.printStackTrace();
			}
		}
    }
    
    private static class ClockListenerCallback implements Runnable {
    	
    	private final ClockChangeListener listener;
    	private final ClockChangedEvent event;
    	
    	public ClockListenerCallback(ClockChangeListener listener, ClockChangedEvent event) {
    		this.listener = listener;
    		this.event = event;
		}
    	
    	@Override
    	public void run() {
    		listener.clockChanged(event);
    	}
    	
    }
    
    
    private static float getSimFactor(final Map config) {
    	float simulationFactor = 1.0F;
    	if (config.containsKey("simulationFactor")) {
			final Object factor = config.get("simulationFactor");
			if (factor instanceof Number)
				simulationFactor = ((Number) factor).floatValue();
			else {
				try {
					simulationFactor = Float.parseFloat((String) config.get("simulationFactor"));
				} catch (ClassCastException | NumberFormatException e) {
					LoggerFactory.getLogger(SimulationClock.class).error("Simulation factor must be of type Float, got {}", factor,e);
				}
			}
		}
		else {
			String aux = System.getProperty(SIMULATION_FACTOR);
			if (aux != null) {
				try {
					simulationFactor = Float.parseFloat(aux);
				} catch (NumberFormatException e) { 
					LoggerFactory.getLogger(SimulationClock.class).error("Simulation factor must be of type Float, got {}", aux);
				}
			}
		}
    	return simulationFactor;
    }
    
    private static Long getStartTimeFramework(final Map config, final long startTimeSystem, final float simulationFactor) {
    	if (!config.containsKey("startTimeFramework") || !(config.get("startTimeFramework") instanceof Long))
    		return null;
    	if (!config.containsKey("startTimeSystem"))
    		return (Long) config.get("startTimeFramework");
		try {
			final Object startSystem0 = config.get("startTimeSystem");
			final Object startFramework0 = config.get("startTimeFramework");
			final long lastStartTimeSystem = startSystem0 instanceof Long ? (Long) startSystem0 : Long.parseLong((String) config.get("startTimeSystem"));
			final long lastStartTimeFramework = startFramework0 instanceof Long ? (Long) startFramework0 : Long.parseLong((String) config.get("startTimeFramework"));
			return (long) ((startTimeSystem - lastStartTimeSystem) * simulationFactor + lastStartTimeFramework);
		} catch (ClassCastException | NumberFormatException e) {
			LoggerFactory.getLogger(SimulationClock.class).error("Simulation start times must be of type Long, got {}, {}", 
					config.get("startTimeSystem"), config.get("startTimeFramework"));
			return null;
		}
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy