/*
* Copyright (c) 2010 Dmytro Pishchukhin (http://knowhowlab.org)
*
* 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.knowhowlab.osgi.monitoradmin;
import org.knowhowlab.osgi.monitoradmin.job.AbstractMonitoringJob;
import org.knowhowlab.osgi.monitoradmin.job.MonitoringJobVisitor;
import org.knowhowlab.osgi.monitoradmin.util.StatusVariablePath;
import org.knowhowlab.osgi.monitoradmin.util.Utils;
import org.osgi.framework.Constants;
import org.osgi.framework.ServiceReference;
import org.osgi.service.event.Event;
import org.osgi.service.monitor.MonitorListener;
import org.osgi.service.monitor.Monitorable;
import org.osgi.service.monitor.MonitoringJob;
import org.osgi.service.monitor.StatusVariable;
import java.io.UnsupportedEncodingException;
import java.util.*;
/**
* MonitorAdmin common actions that are not related on Permissions
*
* @author dpishchukhin
*/
public class MonitorAdminCommon implements MonitorListener, MonitoringJobVisitor {
private static final String SYMBOLIC_NAME_CHARACTERS =
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" + "0123456789" +
"-_."; // a subset of the characters allowed in DMT URIs
private static final int MAX_ID_LENGTH = 32;
public static final String PATH_PATERN = "%s/%s";
/**
* Set of StatusVariable paths for which events are disabled
*/
private final Set<String> disabledPaths = new HashSet<String>();
/**
* List of run jobs
*/
private final List<AbstractMonitoringJob> jobs = new ArrayList<AbstractMonitoringJob>();
private final OsgiVisitor osgiVisitor;
private final LogVisitor logVisitor;
public MonitorAdminCommon(OsgiVisitor osgiVisitor, LogVisitor logVisitor) {
this.osgiVisitor = osgiVisitor;
this.logVisitor = logVisitor;
}
/**
* Callback for notification of a <code>StatusVariable</code> change.
*
* @param monitorableId the identifier of the <code>Monitorable</code>
* instance reporting the change
* @param statusVariable the <code>StatusVariable</code> that has changed
* @throws java.lang.IllegalArgumentException
* if the specified monitorable
* ID is invalid (<code>null</code>, empty, or contains illegal
* characters) or points to a non-existing <code>Monitorable</code>,
* or if <code>statusVariable</code> is <code>null</code>
*/
public void updated(String monitorableId, StatusVariable statusVariable) throws IllegalArgumentException {
// validate monitorableId
findMonitorableById(monitorableId);
if (statusVariable == null) {
throw new IllegalArgumentException("StatusVariable is null");
}
StatusVariablePath path = new StatusVariablePath(monitorableId, statusVariable.getID());
if (isEventEnabled(path.getPath())) {
fireEvent(monitorableId, statusVariable, null);
logVisitor.info("Fire new SV update Event: " + path.getPath(), null);
}
// find jobs that handle this StatusVariable update event
if (!jobs.isEmpty()) {
synchronized (jobs) {
for (AbstractMonitoringJob job : jobs) {
if (job.isHandleUpdateEvent(path.getPath())) {
job.handleUpdateEvent(monitorableId, statusVariable);
}
}
}
}
}
/**
* Get array with paths that are disabled for notificatios with switchEvents() method
*
* @return array with StatusVariable paths
*/
public String[] getDisabledNotificationPaths() {
return disabledPaths.toArray(new String[disabledPaths.size()]);
}
/**
* Check if events are disabled for given path
*
* @param path path to check
* @return if events are disabled - <code>true</code>, otherwise - <code>false</code>
*/
private boolean isEventEnabled(String path) {
return !disabledPaths.contains(path);
}
/**
* Find Monitorable service by monitorable Id. Returns Monitorable service or
* throws exception.
* If multiple services exist for the same monitorableId,
* the service with the highest ranking (as specified in its Constants.SERVICE_RANKING property)
* is returned.
* If there is a tie in ranking, the service with the lowest service ID (as specified
* in its Constants.SERVICE_ID property); that is, the service that was regis-
* tered first is returned.
*
* @param monitorableId id that is userd to filter services
* @return Monitorable service with specified monitorableId.
* @throws IllegalArgumentException monitorableId is <code>null</code> or monitorableId points
* to non-existing service or monitorableId is invalid
*/
private Monitorable findMonitorableById(String monitorableId) throws IllegalArgumentException {
if (monitorableId == null) {
throw new IllegalArgumentException("MonitorableId is null");
}
if (!Utils.validatePathId(monitorableId)) {
throw new IllegalArgumentException("MonitorableId is invalid");
}
ServiceReference mostSuitableMonitorable = null;
ServiceReference[] serviceReferences = osgiVisitor.findMonitorableReferences(monitorableId);
if (serviceReferences != null) {
for (ServiceReference serviceReference : serviceReferences) {
if (mostSuitableMonitorable == null ||
mostSuitableMonitorable.compareTo(serviceReference) < 0) {
mostSuitableMonitorable = serviceReference;
}
}
}
if (mostSuitableMonitorable == null) {
throw new IllegalArgumentException("Monitorable ID: " + monitorableId + " points to non-existing service");
}
return osgiVisitor.getService(mostSuitableMonitorable);
}
/**
* Find Monitorable service reference by monitorable Id. Returns Monitorable service reference or
* throws exception.
* If multiple services exist for the same monitorableId,
* the service with the highest ranking (as specified in its Constants.SERVICE_RANKING property)
* is returned.
* If there is a tie in ranking, the service with the lowest service ID (as specified
* in its Constants.SERVICE_ID property); that is, the service that was regis-
* tered first is returned.
*
* @param monitorableId id that is userd to filter services
* @return Monitorable service reference with specified monitorableId.
* @throws IllegalArgumentException monitorableId is <code>null</code> or monitorableId points
* to non-existing service or monitorableId is invalid
*/
public ServiceReference findMonitorableReferenceById(String monitorableId) throws IllegalArgumentException {
if (monitorableId == null) {
throw new IllegalArgumentException("MonitorableId is null");
}
if (!Utils.validatePathId(monitorableId)) {
throw new IllegalArgumentException("MonitorableId is invalid");
}
ServiceReference mostSuitableMonitorable = null;
ServiceReference[] serviceReferences = osgiVisitor.findMonitorableReferences(monitorableId);
if (serviceReferences != null) {
for (ServiceReference serviceReference : serviceReferences) {
if (mostSuitableMonitorable == null ||
mostSuitableMonitorable.compareTo(serviceReference) < 0) {
mostSuitableMonitorable = serviceReference;
}
}
}
if (mostSuitableMonitorable == null) {
throw new IllegalArgumentException("Monitorable ID: " + monitorableId + " points to non-existing service");
}
return mostSuitableMonitorable;
}
/**
* Returns array of the <code>Monitorable</code> <code>ServiceReference</code>s that are
* currently registered.
* <p/>
* The returned array contains the <code>ServiceTeference</code>s in alphabetical order by service PID.
* It cannot be <code>null</code>, an empty array is returned if no
* <code>Monitorable</code> services are registered.
*
* @return the array of <code>Monitorable</code> names
*/
public ServiceReference[] getMonitorableReferences() {
return getMonitorableReferences(null);
}
/**
* Returns array of the <code>Monitorable</code> <code>ServiceReference</code>s that are
* currently registered.
* <p/>
* The returned array contains the <code>ServiceTeference</code>s in alphabetical order by service PID.
* It cannot be <code>null</code>, an empty array is returned if no
* <code>Monitorable</code> services are registered.
*
* @param monitorableIdFilter <code>Monitorable</code> SERVICE_PID filter
* @return the array of <code>Monitorable</code> names
*/
public ServiceReference[] getMonitorableReferences(String monitorableIdFilter) {
// sorted set that contains Monitorable ServiceReferences
SortedSet<ServiceReference> names = new TreeSet<ServiceReference>(new ServiceReferencePidComparator());
ServiceReference[] serviceReferences = osgiVisitor.findMonitorableReferences(monitorableIdFilter);
if (serviceReferences != null) {
for (ServiceReference serviceReference : serviceReferences) {
String pid = (String) serviceReference.getProperty(Constants.SERVICE_PID);
if (pid != null && isValidId(pid)) {
names.add(serviceReference);
}
}
}
return names.toArray(new ServiceReference[names.size()]);
}
/**
* Add a new <code>MonitoringJob</code> to the list
*
* @param job MonitoringJob
*/
public void addJob(AbstractMonitoringJob job) {
synchronized (jobs) {
jobs.add(job);
}
}
/**
* Get list of all running <code>MonitoringJob</code>s
*
* @return list of running jobs
*/
public List<MonitoringJob> getRunningJobs() {
List<MonitoringJob> runningJobs = new ArrayList<MonitoringJob>();
synchronized (jobs) {
for (AbstractMonitoringJob job : jobs) {
if (job.isRunning()) {
runningJobs.add(job);
}
}
}
return runningJobs;
}
/**
* Returns a <code>StatusVariable</code> addressed by its full path.
*
* @param path the full path of the <code>StatusVariable</code> in
* [Monitorable_ID]/[StatusVariable_ID] format
* @return the <code>StatusVariable</code> object
* @throws java.lang.IllegalArgumentException
* if <code>path</code> is
* <code>null</code> or otherwise invalid, or points to a
* non-existing <code>StatusVariable</code>
*/
public StatusVariable getStatusVariable(String path)
throws IllegalArgumentException {
logVisitor.debug("ENTRY: getStatusVariable: " + path, null);
try {
StatusVariablePath statusVariablePath = new StatusVariablePath(path);
Monitorable monitorable = findMonitorableById(statusVariablePath.getMonitorableId());
return monitorable.getStatusVariable(statusVariablePath.getStatusVariableId());
} finally {
logVisitor.debug("EXIT: getStatusVariable: " + path, null);
}
}
/**
* Returns a <code>StatusVariable</code> addressed by Monitorable service reference and its id.
*
* @param serviceReference <code>Monitorable</code> service reference
* @param statusVariableId <code>StatusVariable</code> id
* @return the <code>StatusVariable</code> object
* @throws java.lang.IllegalArgumentException
* if points to a
* non-existing <code>StatusVariable</code>
*/
public StatusVariable getStatusVariable(ServiceReference serviceReference, String statusVariableId) {
return osgiVisitor.getService(serviceReference).getStatusVariable(statusVariableId);
}
/**
* Returns a <code>StatusVariable</code> description addressed by
* Monitorable service reference and its id.
*
* @param serviceReference <code>Monitorable</code> service reference
* @param statusVariableId <code>StatusVariable</code> id
* @return the <code>StatusVariable</code> description
* @throws java.lang.IllegalArgumentException
* if points to a
* non-existing <code>StatusVariable</code>
*/
public String getDescription(ServiceReference serviceReference, String statusVariableId) {
return osgiVisitor.getService(serviceReference).getDescription(statusVariableId);
}
/**
* Returns a <code>StatusVariable</code> notification flag addressed by
* Monitorable service reference and its id.
*
* @param serviceReference <code>Monitorable</code> service reference
* @param statusVariableId <code>StatusVariable</code> id
* @return the <code>StatusVariable</code> notification flag
* @throws java.lang.IllegalArgumentException
* if points to a
* non-existing <code>StatusVariable</code>
*/
public boolean notifiesOnChange(ServiceReference serviceReference, String statusVariableId) {
return osgiVisitor.getService(serviceReference).notifiesOnChange(statusVariableId);
}
/**
* Reset <code>StatusVariable</code> description addressed by
* Monitorable service reference and its id.
*
* @param serviceReference <code>Monitorable</code> service reference
* @param statusVariableId <code>StatusVariable</code> id
* @return the <code>StatusVariable</code> description
* @throws java.lang.IllegalArgumentException
* if points to a
* non-existing <code>StatusVariable</code>
*/
public boolean resetStatusVariable(ServiceReference serviceReference, String statusVariableId) {
return osgiVisitor.getService(serviceReference).resetStatusVariable(statusVariableId);
}
/**
* Cancel Job and remove it from jobs list
*
* @param job job to cancel
*/
public void cancelJob(AbstractMonitoringJob job) {
synchronized (jobs) {
jobs.remove(job);
job.cancel();
}
}
/**
* Cancel all jobs and clear the list
*/
public void cancelAllJobs() {
logVisitor.debug("ENTRY: cancelJobs", null);
try {
if (!jobs.isEmpty()) {
synchronized (jobs) {
Iterator<AbstractMonitoringJob> iterator = jobs.iterator();
while (iterator.hasNext()) {
AbstractMonitoringJob job = iterator.next();
job.cancel();
iterator.remove();
}
}
}
} finally {
logVisitor.debug("ENTRY: cancelJobs", null);
}
}
/**
* Fire StatusVariable update event
*
* @param monitorableId monitorableId
* @param statusVariable status variable
* @param initiator initiator. if <code>null</code> - is not added to event
*/
public void fireEvent(String monitorableId, StatusVariable statusVariable, String initiator) {
Dictionary<String, String> eventProperties = new Hashtable<String, String>();
eventProperties.put(ConstantsMonitorAdmin.MON_MONITORABLE_PID, monitorableId);
eventProperties.put(ConstantsMonitorAdmin.MON_STATUSVARIABLE_NAME, statusVariable.getID());
String value = null;
switch (statusVariable.getType()) {
case StatusVariable.TYPE_BOOLEAN:
value = Boolean.toString(statusVariable.getBoolean());
break;
case StatusVariable.TYPE_FLOAT:
value = Float.toString(statusVariable.getFloat());
break;
case StatusVariable.TYPE_INTEGER:
value = Integer.toString(statusVariable.getInteger());
break;
case StatusVariable.TYPE_STRING:
value = statusVariable.getString();
break;
}
eventProperties.put(ConstantsMonitorAdmin.MON_STATUSVARIABLE_VALUE, value);
if (initiator != null) {
eventProperties.put(ConstantsMonitorAdmin.MON_LISTENER_ID, initiator);
}
Event event = new Event(ConstantsMonitorAdmin.TOPIC, eventProperties);
try {
osgiVisitor.postEvent(event);
} catch (SecurityException e) {
logVisitor.error("MonitorAdmin bundle does not have TopicPermission", e);
}
}
/**
* Switch on/off events
*
* @param paths <code>StatusVariable</code> paths
* @param on <code>false</code> if event sending should be switched off,
* <code>true</code> if it should be switched on for the given path
*/
public void switchEvents(Set<String> paths, boolean on) {
if (on) {
disabledPaths.removeAll(paths);
} else {
disabledPaths.addAll(paths);
}
}
/**
* Returns the list of <code>StatusVariable</code> names published by a
* <code>Monitorable</code> instance. Only those status variables are
* listed where the following two conditions are met:
* <ul>
* <li>the specified <code>Monitorable</code> holds a
* <code>MonitorPermission</code> for the status variable with the
* <code>publish</code> action present
* <li>the caller holds a <code>MonitorPermission</code> for
* the status variable with the <code>read</code> action present
* </ul>
* All other status variables are silently ignored, their names are omitted
* from the list.
* <p/>
* The returned array does not contain duplicates, and the elements are in
* alphabetical order. It cannot be <code>null</code>, an empty array is
* returned if no (authorized and readable) Status Variables are provided
* by the given <code>Monitorable</code>.
*
* @param monitorableId the identifier of a <code>Monitorable</code>
* instance
* @return a list of <code>StatusVariable</code> objects names
* published by the specified <code>Monitorable</code>
* @throws java.lang.IllegalArgumentException
* if <code>monitorableId</code>
* is <code>null</code> or otherwise invalid, or points to a
* non-existing <code>Monitorable</code>
*/
public String[] getStatusVariableNames(String monitorableId) {
Monitorable monitorable = findMonitorableById(monitorableId);
String[] statusVariableNames = monitorable.getStatusVariableNames();
List<String> result = new ArrayList<String>();
for (String statusVariableName : statusVariableNames) {
if (isValidId(statusVariableName)) {
result.add(statusVariableName);
}
}
return result.toArray(new String[result.size()]);
}
private static class ServiceReferencePidComparator implements Comparator<ServiceReference> {
public int compare(ServiceReference o1, ServiceReference o2) {
String pid1 = (String) o1.getProperty(Constants.SERVICE_PID);
String pid2 = (String) o2.getProperty(Constants.SERVICE_PID);
return pid1.compareTo(pid2);
}
}
/**
* Validate <code>Monitorable</code> Id or <code>StatusVariable</code> name
* @param id id
* @return <code>false</code> - id is invalid, otherwise - <code>true</code>
*/
public static boolean isValidId(String id) {
byte[] nameBytes;
try {
nameBytes = id.getBytes("UTF-8");
} catch (UnsupportedEncodingException e) {
// never happens, "UTF-8" must always be supported
return false;
}
if (nameBytes.length > MAX_ID_LENGTH) {
return false;
}
if (id.equals(".") || id.equals("..")) {
return false;
}
char[] chars = id.toCharArray();
for (char aChar : chars) {
if (SYMBOLIC_NAME_CHARACTERS.indexOf(aChar) == -1) {
return false;
}
}
return true;
}
}