/*******************************************************************************
* This file is part of OpenNMS(R).
*
* Copyright (C) 2007-2011 The OpenNMS Group, Inc.
* OpenNMS(R) is Copyright (C) 1999-2011 The OpenNMS Group, Inc.
*
* OpenNMS(R) is a registered trademark of The OpenNMS Group, Inc.
*
* OpenNMS(R) is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published
* by the Free Software Foundation, either version 3 of the License,
* or (at your option) any later version.
*
* OpenNMS(R) is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with OpenNMS(R). If not, see:
* http://www.gnu.org/licenses/
*
* For more information contact:
* OpenNMS(R) Licensing <license@opennms.org>
* http://www.opennms.org/
* http://www.opennms.com/
*******************************************************************************/
package org.opennms.netmgt.dao.support;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.Map.Entry;
import org.apache.commons.io.IOUtils;
import org.opennms.core.utils.BundleLists;
import org.opennms.core.utils.ThreadCategory;
import org.opennms.netmgt.dao.GraphDao;
import org.opennms.netmgt.model.AdhocGraphType;
import org.opennms.netmgt.model.OnmsAttribute;
import org.opennms.netmgt.model.OnmsResource;
import org.opennms.netmgt.model.PrefabGraph;
import org.opennms.netmgt.model.PrefabGraphType;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.InputStreamResource;
import org.springframework.core.io.Resource;
import org.springframework.dao.DataAccessResourceFailureException;
import org.springframework.orm.ObjectRetrievalFailureException;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
public class PropertiesGraphDao implements GraphDao, InitializingBean {
/** Constant <code>DEFAULT_GRAPH_LIST_KEY="reports"</code> */
public static final String DEFAULT_GRAPH_LIST_KEY = "reports";
private Map<String, Resource> m_prefabConfigs;
private Map<String, Resource> m_adhocConfigs;
private Map<String, FileReloadContainer<PrefabGraphTypeDao>> m_types =
new HashMap<String, FileReloadContainer<PrefabGraphTypeDao>>();
private HashMap<String, FileReloadContainer<AdhocGraphType>> m_adhocTypes =
new HashMap<String, FileReloadContainer<AdhocGraphType>>();
private PrefabGraphTypeCallback m_prefabCallback =
new PrefabGraphTypeCallback();
private AdhocGraphTypeCallback m_adhocCallback =
new AdhocGraphTypeCallback();
/**
* <p>
* Constructor for PropertiesGraphDao.
* </p>
*/
public PropertiesGraphDao() {
}
private void initPrefab() throws IOException {
for (Map.Entry<String, Resource> configEntry : m_prefabConfigs.entrySet()) {
loadProperties(configEntry.getKey(), configEntry.getValue());
}
}
private void initAdhoc() throws IOException {
for (Map.Entry<String, Resource> configEntry : m_adhocConfigs.entrySet()) {
loadAdhocProperties(configEntry.getKey(), configEntry.getValue());
}
}
/** {@inheritDoc} */
public PrefabGraphType findPrefabGraphTypeByName(String name) {
return findPrefabGraphTypeDaoByName(name);
}
/**
* Same as findPrefabGraphTypeByName, but has the sub-class return type.
* For use within the package, where access to the subclass methods is
* required and we don't want to cast (ugly)
*
* @param name
* @return
*/
PrefabGraphTypeDao findPrefabGraphTypeDaoByName(String name) {
PrefabGraphTypeDao result = m_types.get(name).getObject();
this.rescanIncludeDirectory(result);
return result;
}
/** {@inheritDoc} */
public AdhocGraphType findAdhocGraphTypeByName(String name) {
return m_adhocTypes.get(name).getObject();
}
/**
* <p>
* loadProperties
* </p>
*
* @param type
* a {@link java.lang.String} object.
* @param resource
* a {@link org.springframework.core.io.Resource} object.
* @throws java.io.IOException
* if any.
*/
public void loadProperties(String typeName, Resource resource)
throws IOException {
PrefabGraphTypeDao type;
type = createPrefabGraphType(typeName, resource);
m_types.put(type.getName(),
new FileReloadContainer<PrefabGraphTypeDao>(type,
resource,
m_prefabCallback));
}
/**
* <p>
* loadProperties
* </p>
* Used exclusively by test code. Will ignore an "include.directory"
* because we don't have a resource/path to do any useful "relative"
* pathing to. Also anything loaded in this fashion will *not* have auto
* reloading on changes, because there's no underlying Resource/File to
* check against. Like, duh!
*
* @param type
* a {@link java.lang.String} object.
* @param in
* a {@link java.io.InputStream} object.
* @throws java.io.IOException
* if any.
*/
public void loadProperties(String type, InputStream in)
throws IOException {
Resource resource = new InputStreamResource(in);
// Not reloadable; we don't have a file to check for modifications
PrefabGraphTypeDao t = createPrefabGraphType(type, resource, false);
if (t != null) {
m_types.put(t.getName(),
new FileReloadContainer<PrefabGraphTypeDao>(t));
}
}
/**
* Encapsulates the check of the rescan interval on a given 'type',
* and if that has passed, initiates a rescan.
*
* Should be called on any accessor method which returns a graph or
* a graph type object to external callers, to ensure they're
* seeing the latest configuration
* @param type
*/
private void rescanIncludeDirectory(PrefabGraphTypeDao type) {
try {
//Always do this check; it'll only rescan modified previously munted files anyway
this.recheckMalformedIncludedFiles(type);
if (System.currentTimeMillis() > (type.getLastIncludeScan() + type.getIncludeDirectoryRescanTimeout())) {
this.scanIncludeDirectory(type);
}
} catch (IOException e) {
log().error("Unable to rescan the include directory '"
+ type.getIncludeDirectory() + "' of type "
+ type.getName() + " because:", e);
}
}
private void loadIncludedFile(PrefabGraphTypeDao type, File file)
throws FileNotFoundException, IOException {
Properties props = new Properties();
InputStream fileIn = new FileInputStream(file);
try {
props.load(fileIn);
} finally {
IOUtils.closeQuietly(fileIn);
}
//Clear any malformed setting; if everything goes ok, it'll remain cleared
// If there's problems, it'll be re-added.
type.removeMalformedFile(file);
try {
List<PrefabGraph> subGraphs = loadPrefabGraphDefinitions(type,
props);
for (PrefabGraph graph : subGraphs) {
if(graph == null) {
//Indicates a multi-graph file that had a munted graph definition
type.addMalformedFile(file); //Record that the file was partly broken
} else {
type.addPrefabGraph(new FileReloadContainer<PrefabGraph>(
graph,
new FileSystemResource(
file),
type.getCallback()));
}
}
} catch (DataAccessResourceFailureException e) {
log().error("Problem while attempting to load " + file + ":", e);
type.addMalformedFile(file); //Record that the file was completely broken
}
}
/**
* Checks for any files which were malformed last time the include directory for this type
* was scanned. If any of them have changed since then, attempt to read them again.
*
* This is meant to ensure that if the administrator made a mistake when adding a new file
* they don't need to wait for include.directory.rescan before the fix will be noticed
*
* It's necessary because if the file was broken, the broken graphs won't be stored in a
* FileReloadContainer to be noticed.
* @param type
*/
private void recheckMalformedIncludedFiles(PrefabGraphTypeDao type) throws IOException {
Map<File, Long> filesMap = type.getMalformedFiles();
//Get an immutable set of files, for iterating over safely while potentially modifying
// the set in loadIncludedFile
File[] files = filesMap.keySet().toArray(new File[0]);
for (File file : files) {
Long lastKnownTimestamp = filesMap.get(file);
if(file.lastModified() > lastKnownTimestamp) {
//Try loading it again; it's been modified since we noted it was borked
this.loadIncludedFile(type, file);
}
}
}
private void scanIncludeDirectory(PrefabGraphTypeDao type) throws IOException {
Resource includeDirectoryResource = type.getIncludeDirectoryResource();
if (includeDirectoryResource != null) {
File includeDirectory = includeDirectoryResource.getFile();
// Include all the files in the directory, knowing that the
// format is slightly different (no report name required in
// each property name, and report.id is expected)
FilenameFilter propertyFilesFilter = new FilenameFilter() {
@Override
public boolean accept(File dir, String name) {
return (name.endsWith(".properties"));
}
};
File[] propertyFiles = includeDirectory.listFiles(propertyFilesFilter);
for (File file : propertyFiles) {
loadIncludedFile(type, file);
}
}
type.setLastIncludeScan(System.currentTimeMillis());
}
/**
* Wrapper around the createPrefabGraphType which takes a reloadable
* argument, defaulting to reloadable = true For simplicity in the
* default/expected case.
*
* @param type
* @param sourceResource
* @return
*/
private PrefabGraphTypeDao createPrefabGraphType(String type,
Resource sourceResource) {
// Default to adding a callback
return this.createPrefabGraphType(type, sourceResource, true);
}
/**
* Create a PrefabGraphTypeDao from the properties file in sourceResource
* If reloadable is true, add the type's reload call back to each graph,
* otherwise don't If sourceResource is not a file-based resource, then
* reloadable should be false NB: I didn't want to get into checking for
* what the implementation class of Resource is, because that could break
* in future with new classes and types that do have a File underneath
* them. This way, it's up to the caller, who *should* be able to make a
* sensible choice as to whether the resource is reloadable or not.
*
* @param type
* @param sourceResource
* @param reloadable
* @return
*/
private PrefabGraphTypeDao createPrefabGraphType(String type,
Resource sourceResource, boolean reloadable) {
InputStream in = null;
try {
in = sourceResource.getInputStream();
Properties properties = new Properties();
properties.load(in);
PrefabGraphTypeDao t = new PrefabGraphTypeDao();
t.setName(type);
t.setCommandPrefix(getProperty(properties, "command.prefix"));
t.setOutputMimeType(getProperty(properties, "output.mime"));
t.setDefaultReport(properties.getProperty("default.report",
"none"));
String includeDirectoryString = properties.getProperty("include.directory");
t.setIncludeDirectory(includeDirectoryString);
if (includeDirectoryString != null) {
Resource includeDirectoryResource;
File includeDirectoryFile = new File(includeDirectoryString);
if (includeDirectoryFile.isAbsolute()) {
includeDirectoryResource = new FileSystemResource(
includeDirectoryString);
} else {
includeDirectoryResource = sourceResource.createRelative(includeDirectoryString);
}
File includeDirectory = includeDirectoryResource.getFile();
if (includeDirectory.isDirectory()) {
t.setIncludeDirectoryResource(includeDirectoryResource);
} else {
// Just warn; no need to throw a hissy fit or otherwise fail to load
log().warn("includeDirectory '"
+ includeDirectoryFile.getAbsolutePath()
+ "' specified in '"
+ sourceResource.getFilename()
+ "' is not a directory");
}
}
// Default to 5 minutes; it's up to users to specify a shorter
// time if they don't mind OpenNMS spamming on that directory
int interval;
try {
interval = Integer.parseInt(properties.getProperty("include.directory.rescan",
"300000"));
} catch (NumberFormatException e) {
// Default value if one was specified but it wasn't an integer
interval = 300000;
log().warn("The property 'include.directory.rescan' in "
+ sourceResource
+ " was not able to be parsed as an integer. Defaulting to "
+ interval + "ms", e);
}
t.setIncludeDirectoryRescanInterval(interval);
List<PrefabGraph> graphs = loadPrefabGraphDefinitions(t,
properties);
for (PrefabGraph graph : graphs) {
//The graphs list may contain nulls; see loadPrefabGraphDefinitions for reasons
if(graph != null) {
FileReloadContainer<PrefabGraph> container;
if (reloadable) {
container = new FileReloadContainer<PrefabGraph>(
graph,
sourceResource,
t.getCallback());
} else {
container = new FileReloadContainer<PrefabGraph>(graph);
}
t.addPrefabGraph(container);
}
}
//This *must* come after loading the main graph file, to ensure overrides are correct
this.scanIncludeDirectory(t);
return t;
} catch (IOException e) {
log().error("Failed to load prefab graph configuration of type "
+ type + " from " + sourceResource, e);
return null;
} finally {
IOUtils.closeQuietly(in);
}
}
/**
* <p>createPrefabGraphType
* loadAdhocProperties
* </p>
*
* @param type
* a {@link java.lang.String} object.
* @param resource
* a {@link org.springframework.core.io.Resource} object.
* @throws java.io.IOException
* if any.
*/
public void loadAdhocProperties(String type, Resource resource)
throws IOException {
InputStream in = resource.getInputStream();
AdhocGraphType t;
try {
t = createAdhocGraphType(type, in);
} finally {
IOUtils.closeQuietly(in);
}
m_adhocTypes.put(t.getName(),
new FileReloadContainer<AdhocGraphType>(t, resource,
m_adhocCallback));
}
/**
* <p>
* loadAdhocProperties
* </p>
*
* @param type
* a {@link java.lang.String} object.
* @param in
* a {@link java.io.InputStream} object.
* @throws java.io.IOException
* if any.
*/
public void loadAdhocProperties(String type, InputStream in)
throws IOException {
AdhocGraphType t = createAdhocGraphType(type, in);
m_adhocTypes.put(t.getName(),
new FileReloadContainer<AdhocGraphType>(t));
}
/**
* <p>
* createAdhocGraphType
* </p>
*
* @param type
* a {@link java.lang.String} object.
* @param in
* a {@link java.io.InputStream} object.
* @return a {@link org.opennms.netmgt.model.AdhocGraphType} object.
* @throws java.io.IOException
* if any.
*/
private AdhocGraphType createAdhocGraphType(String type, InputStream in)
throws IOException {
Properties properties = new Properties();
properties.load(in);
AdhocGraphType t = new AdhocGraphType();
t.setName(type);
t.setCommandPrefix(getProperty(properties, "command.prefix"));
t.setOutputMimeType(getProperty(properties, "output.mime"));
t.setTitleTemplate(getProperty(properties, "adhoc.command.title"));
t.setDataSourceTemplate(getProperty(properties, "adhoc.command.ds"));
t.setGraphLineTemplate(getProperty(properties,
"adhoc.command.graphline"));
return t;
}
/**
* @param type
* - a PrefabGraphType in which graphs
* found in 'properties' will be stored
* @param properties
* - a properties object, usually loaded from a File or
* InputStream, with graph definitions in it
* @return A list of the graphs found. THIS LIST MAY CONTAIN NULL ENTRIES, one for each
* graph in a multi-graph file that failed to load (e.g. missing properties). Other than
* logging an error, this is the only way this method indicates a problem with just one graph
* The only cause for a real exception is if there's neither a "reports" nor a "report.id"
* property, which cannot be recovered from
*/
private List<PrefabGraph> loadPrefabGraphDefinitions(
PrefabGraphTypeDao type, Properties properties) {
Assert.notNull(properties, "properties argument cannot be null");
List<PrefabGraph> result = new ArrayList<PrefabGraph>();
String listString = properties.getProperty(DEFAULT_GRAPH_LIST_KEY); // Optional
String[] list;
if (listString != null) {
list = BundleLists.parseBundleList(listString);
} else {
// A report-per-file properties file; just use the report.id
// At this stage, if there was no "reports", then there *must* be
// a report.id, otherwise we're pooched
list = new String[1];
try {
list[0] = getProperty(properties, "report.id");
} catch (DataAccessResourceFailureException e) {
// Special case; if this exception is thrown, then report.id
// was missing
// But, we need to be more clear in the report (no report.id
// *or* "reports" property). However, we shouldn't throw
// an exception, because that would break loading of all
// graphs if just one file was broken
throw new DataAccessResourceFailureException("Properties must "
+ "contain a 'report.id' property "
+ "or a 'reports' property");
}
}
for (String name : list) {
try {
PrefabGraph graph = makePrefabGraph(name, properties,
type.getNextOrdering());
result.add(graph);
} catch (DataAccessResourceFailureException e) {
log().error("Failed to load report '" + name + "' because:",
e);
result.add(null); //Add a null, indicating a broken graph
}
}
return result;
}
private PrefabGraph makePrefabGraph(String name, Properties props,
int order) {
Assert.notNull(name, "name argument cannot be null");
Assert.notNull(props, "props argument cannot be null");
String key = name; // Default to the name, for
if (props.getProperty("report.id") != null) {
key = null; // A report-per-file properties file
}
String title = getReportProperty(props, key, "name", true);
String command = getReportProperty(props, key, "command", true);
String columnString = getReportProperty(props, key, "columns", true);
String[] columns = BundleLists.parseBundleList(columnString);
String externalValuesString = getReportProperty(props, key,
"externalValues",
false);
String[] externalValues;
if (externalValuesString == null) {
externalValues = new String[0];
} else {
externalValues = BundleLists.parseBundleList(externalValuesString);
}
String[] propertiesValues;
String propertiesValuesString = getReportProperty(props, key,
"propertiesValues",
false);
if (propertiesValuesString == null) {
propertiesValues = new String[0];
} else {
propertiesValues = BundleLists.parseBundleList(propertiesValuesString);
}
// can be null
String[] types;
String typesString = getReportProperty(props, key, "type", false);
if (typesString == null) {
types = new String[0];
} else {
types = BundleLists.parseBundleList(typesString);
}
// can be null
String description = getReportProperty(props, key, "description",
false);
/*
* TODO: Right now a "width" and "height" property is required in
* order to get zoom to work properly on non-standard sized graphs. A
* more elegant solution would be to parse the command string and look
* for --width and --height and set the following two variables
* automagically, without having to rely on a configuration file.
*/
Integer graphWidth = getIntegerReportProperty(props, key, "width",
false);
Integer graphHeight = getIntegerReportProperty(props, key, "height",
false);
String suppressString = getReportProperty(props, key, "suppress",
false);
String[] suppress = (suppressString == null) ? new String[0]
: BundleLists.parseBundleList(suppressString);
return new PrefabGraph(name, title, columns, command, externalValues,
propertiesValues, order, types, description,
graphWidth, graphHeight, suppress);
}
private String getProperty(Properties props, String name) {
String property = props.getProperty(name);
if (property == null) {
throw new DataAccessResourceFailureException("Properties must "
+ "contain \'" + name + "\' property");
}
return property;
}
// A null key means we are loading from a report-per-file properties file
private String getReportProperty(Properties props, String key,
String suffix, boolean required) {
String propertyName;
String graphName;
if (key != null) {
propertyName = "report." + key + "." + suffix;
graphName = key;
} else {
propertyName = "report." + suffix;
// It's lightly evil to know this from this method, but we can be
// confident that report.id will exist
graphName = props.getProperty("report.id");
}
String property = props.getProperty(propertyName);
if (property == null && required == true) {
throw new DataAccessResourceFailureException("Properties for "
+ "report '" + graphName + "' must contain \'"
+ propertyName + "\' property");
}
return property;
}
private Integer getIntegerReportProperty(Properties props, String key,
String suffix, boolean required) {
String value = getReportProperty(props, key, suffix, required);
if (value == null) {
return null;
}
try {
return new Integer(value);
} catch (NumberFormatException e) {
throw new DataAccessResourceFailureException(
"Property value for '"
+ suffix
+ "' on report '"
+ key
+ "' must be an integer. '"
+ value
+ "' is not a valid value");
}
}
private ThreadCategory log() {
return ThreadCategory.getInstance(PropertiesGraphDao.class);
}
private class PrefabGraphTypeCallback implements
FileReloadCallback<PrefabGraphTypeDao> {
public PrefabGraphTypeDao reload(PrefabGraphTypeDao object,
Resource resource) {
try {
return createPrefabGraphType(object.getName(), resource);
} catch (Throwable e) {
log().error("Could not reload configuration '" + resource
+ "'; nested exception: " + e, e);
return null;
}
}
}
private class PrefabGraphCallback implements
FileReloadCallback<PrefabGraph> {
private PrefabGraphTypeDao m_type;
public PrefabGraphCallback(PrefabGraphTypeDao type) {
m_type = type;
}
public PrefabGraph reload(PrefabGraph graph, Resource resource) {
try {
String graphName = graph.getName();
Properties props = new Properties();
props.load(resource.getInputStream());
List<PrefabGraph> reloadedGraphs = loadPrefabGraphDefinitions(m_type,
props);
PrefabGraph result = null;
for (PrefabGraph reloadedGraph : reloadedGraphs) {
//The reloadedGraphs may contain nulls; see loadPrefabGraphDefinitions for reasons
if(reloadedGraph != null) {
if (reloadedGraph.getName().equals(graphName)) {
result = reloadedGraph;
}
m_type.addPrefabGraph(new FileReloadContainer<PrefabGraph>(
reloadedGraph,
resource,
this));
}
}
return result;
} catch (Throwable e) {
log().error("Could not reload configuration '" + resource
+ "'; nested exception: " + e, e);
return null;
}
}
}
/**
* <p>
* getAllPrefabGraphs
* </p>
*
* @return a {@link java.util.List} object.
*/
public List<PrefabGraph> getAllPrefabGraphs() {
List<PrefabGraph> graphs = new ArrayList<PrefabGraph>();
for (FileReloadContainer<PrefabGraphTypeDao> container : m_types.values()) {
PrefabGraphTypeDao type = container.getObject();
this.rescanIncludeDirectory(type);
Map<String, FileReloadContainer<PrefabGraph>> map = type.getReportMap();
for (FileReloadContainer<PrefabGraph> graphContainer : map.values()) {
graphs.add(graphContainer.getObject());
}
}
return graphs;
}
/** {@inheritDoc} */
public PrefabGraph getPrefabGraph(String name) {
for (FileReloadContainer<PrefabGraphTypeDao> container : m_types.values()) {
PrefabGraphTypeDao type = container.getObject();
this.rescanIncludeDirectory(type);
PrefabGraph graph = type.getQuery(name);
if (graph != null) {
return graph;
}
}
throw new ObjectRetrievalFailureException(PrefabGraph.class, name,
"Could not find prefabricated graph report with name '"
+ name + "'", null);
}
/** {@inheritDoc} */
public PrefabGraph[] getPrefabGraphsForResource(OnmsResource resource) {
if (resource == null) {
log().warn("returning empty graph list for resource because it is null");
return new PrefabGraph[0];
}
Set<OnmsAttribute> attributes = resource.getAttributes();
// Check if there are no attributes
if (attributes.size() == 0) {
log().debug("returning empty graph list for resource " + resource
+ " because its attribute list is empty");
return new PrefabGraph[0];
}
Set<String> availableRrdAttributes = resource.getRrdGraphAttributes().keySet();
Set<String> availableStringAttributes = resource.getStringPropertyAttributes().keySet();
Set<String> availableExternalAttributes = resource.getExternalValueAttributes().keySet();
// Check if there are no RRD attributes
if (availableRrdAttributes.size() == 0) {
log().debug("returning empty graph list for resource " + resource
+ " because it has no RRD attributes");
return new PrefabGraph[0];
}
String resourceType = resource.getResourceType().getName();
Map<String, PrefabGraph> returnList = new LinkedHashMap<String, PrefabGraph>();
for (PrefabGraph query : getAllPrefabGraphs()) {
if (resourceType != null && !query.hasMatchingType(resourceType)) {
if (log().isDebugEnabled()) {
log().debug("skipping "
+ query.getName()
+ " because its types \""
+ StringUtils.arrayToDelimitedString(query.getTypes(),
", ")
+ "\" does not match resourceType \""
+ resourceType + "\"");
}
continue;
}
if (!verifyAttributesExist(query, "RRD",
Arrays.asList(query.getColumns()),
availableRrdAttributes)) {
continue;
}
if (!verifyAttributesExist(query,
"string property",
Arrays.asList(query.getPropertiesValues()),
availableStringAttributes)) {
continue;
}
if (!verifyAttributesExist(query,
"external value",
Arrays.asList(query.getExternalValues()),
availableExternalAttributes)) {
continue;
}
if (log().isDebugEnabled()) {
log().debug("adding " + query.getName() + " to query list");
}
returnList.put(query.getName(), query);
}
if (log().isDebugEnabled()) {
ArrayList<String> nameList = new ArrayList<String>(
returnList.size());
for (PrefabGraph graph : returnList.values()) {
nameList.add(graph.getName());
}
log().debug("found "
+ nameList.size()
+ " prefabricated graphs for resource "
+ resource
+ ": "
+ StringUtils.collectionToDelimitedString(nameList,
", "));
}
Set<String> suppressReports = new HashSet<String>();
for (Entry<String, PrefabGraph> entry : returnList.entrySet()) {
suppressReports.addAll(Arrays.asList(entry.getValue().getSuppress()));
}
suppressReports.retainAll(returnList.keySet());
if (suppressReports.size() > 0 && log().isDebugEnabled()) {
log().debug("suppressing "
+ suppressReports.size()
+ " prefabricated graphs for resource "
+ resource
+ ": "
+ StringUtils.collectionToDelimitedString(suppressReports,
", "));
}
for (String suppressReport : suppressReports) {
returnList.remove(suppressReport);
}
return returnList.values().toArray(new PrefabGraph[returnList.size()]);
}
private boolean verifyAttributesExist(PrefabGraph query, String type,
List<String> requiredList, Set<String> availableRrdAttributes) {
if (availableRrdAttributes.containsAll(requiredList)) {
return true;
} else {
if (log().isDebugEnabled()) {
String name = query.getName();
log().debug("not adding "
+ name
+ " to prefab graph list because the required list of "
+ type
+ " attributes ("
+ StringUtils.collectionToDelimitedString(requiredList,
", ")
+ ") is not in the list of "
+ type
+ " attributes on the resource ("
+ StringUtils.collectionToDelimitedString(availableRrdAttributes,
", ")
+ ")");
}
return false;
}
}
private class AdhocGraphTypeCallback implements
FileReloadCallback<AdhocGraphType> {
public AdhocGraphType reload(AdhocGraphType object, Resource resource) {
InputStream in = null;
try {
in = resource.getInputStream();
return createAdhocGraphType(object.getName(), in);
} catch (Throwable e) {
log().error("Could not reload configuration from '"
+ resource + "'; nested exception: " + e,
e);
return null;
} finally {
IOUtils.closeQuietly(in);
}
}
}
/**
* <p>
* afterPropertiesSet
* </p>
*
* @throws java.io.IOException
* if any.
*/
@Override
public void afterPropertiesSet() throws IOException {
Assert.notNull(m_prefabConfigs,
"property prefabConfigs must be set to a non-null value");
Assert.notNull(m_adhocConfigs,
"property adhocConfigs must be set to a non-null value");
initPrefab();
initAdhoc();
}
/**
* <p>
* getAdhocConfigs
* </p>
*
* @return a {@link java.util.Map} object.
*/
public Map<String, Resource> getAdhocConfigs() {
return Collections.unmodifiableMap(m_adhocConfigs);
}
/**
* <p>
* setAdhocConfigs
* </p>
*
* @param adhocConfigs
* a {@link java.util.Map} object.
*/
public void setAdhocConfigs(Map<String, Resource> adhocConfigs) {
m_adhocConfigs = adhocConfigs;
}
/**
* <p>
* getPrefabConfigs
* </p>
*
* @return a {@link java.util.Map} object.
*/
public Map<String, Resource> getPrefabConfigs() {
return Collections.unmodifiableMap(m_prefabConfigs);
}
/**
* <p>
* setPrefabConfigs
* </p>
*
* @param prefabConfigs
* a {@link java.util.Map} object.
*/
public void setPrefabConfigs(Map<String, Resource> prefabConfigs) {
m_prefabConfigs = prefabConfigs;
}
/**
* An package internal subclass of PrefabGraphType, intended for use only
* within this Dao. This object has knowledge of FileReloadContainers,
* rescan intervals, and the original resources used to load the types.
* (i.e. info that shouldn't be in the PrefabGraphType itself, which is
* a model object, in opennms-model)
* It stores the actual graphs that belong to the PrefabGraphType, in said
* FileReloadContainers; all access to graph objects is via this Dao anyway
*/
class PrefabGraphTypeDao extends PrefabGraphType {
private Map<String, FileReloadContainer<PrefabGraph>> m_reportMap;
//The ordering to use for the next graph added to this type, to ensure sortability
private int m_ordering;
//The time-stamp when the include directory was last scanned.
//Like FileReloadContainers. this doesn't belong in the model
//The value is maintained by the PropertiesGraphDao itself, not this object
private long m_lastIncludeScan;
// A call-back object for this type, which will reload a file, and
// re-add the
// new graph to this PrefabGraphTypeDao instance
private PrefabGraphCallback m_callback;
// A set of files that were malformed last time they were read by scanIncludeDirectory
// and their previous time-stamps
private Map<File, Long> m_malformedFiles;
/**
* The resource that is the include directory
* Stored because otherwise we'll have to recalculate it from the root resource
* repeatedly (when rescanning).
*/
private Resource m_includeDirectoryResource;
public PrefabGraphTypeDao() {
m_reportMap = new HashMap<String, FileReloadContainer<PrefabGraph>>();
m_ordering = 0;
m_callback = new PrefabGraphCallback(this);
m_malformedFiles = new HashMap<File, Long>();
}
public void setIncludeDirectoryResource(Resource includeDirectoryResource) {
m_includeDirectoryResource = includeDirectoryResource;
}
public Resource getIncludeDirectoryResource() {
return m_includeDirectoryResource;
}
public FileReloadCallback<PrefabGraph> getCallback() {
return m_callback;
}
/**
* Adds the specified graph to the internal map, replacing any
* previous ones with the same name (name being the name from the
* graph object itself)
*
* @param graph
*/
public void addPrefabGraph(FileReloadContainer<PrefabGraph> graph) {
m_reportMap.put(graph.getObject().getName(), graph);
}
/**
* <p>
* getReportMap
* </p>
* A map of graphs, keyed by their name. The returned value is
* readonly (unmodifiable)
*
* @return a {@link java.util.Map} object.
*/
public Map<String, FileReloadContainer<PrefabGraph>> getReportMap() {
return Collections.unmodifiableMap(m_reportMap);
}
/**
* <p>
* getQuery
* </p>
*
* @param queryName
* a {@link java.lang.String} object.
* @return a {@link org.opennms.netmgt.model.PrefabGraph} object.
*/
public PrefabGraph getQuery(String queryName) {
FileReloadContainer<PrefabGraph> container = m_reportMap.get(queryName);
if (container == null) {
return null;
}
return container.getObject();
}
public int getNextOrdering() {
return m_ordering++;
}
public long getLastIncludeScan() {
return m_lastIncludeScan;
}
public void setLastIncludeScan(long lastIncludeScan) {
m_lastIncludeScan = lastIncludeScan;
}
/**
* Returns the malformed files map.
* DO NOT EDIT THIS MAP DIRECTLY (well, it probably doesn't matter, but it's bad form old chap)
* @return
*/
public Map<File, Long> getMalformedFiles() {
return m_malformedFiles;
}
/**
* Add a malformed file to the list for this type; it's last modified time will be
* stored as the Long value in the map, allowing callers to later check if it's been modified
* since being noted as malformed
* @param malformedFile
*/
public void addMalformedFile(File malformedFile) {
m_malformedFiles.put(malformedFile, malformedFile.lastModified());
}
/**
* Remove a malformed file from the malformed files list, presumably because it loaded correctly
* and is no longer malformed
*
* @param malformedFile
*/
public void removeMalformedFile(File malformedFile) {
m_malformedFiles.remove(malformedFile);
}
}
}