/*
* #!
* Ontopia Webed
* #-
* Copyright (C) 2001 - 2013 The Ontopia Project
* #-
* 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 net.ontopia.topicmaps.webed.impl.utils;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import net.ontopia.topicmaps.webed.impl.basic.ActionRegistryIF;
import net.ontopia.topicmaps.webed.impl.basic.ConfigurationObservableIF;
import net.ontopia.topicmaps.webed.impl.basic.ConfigurationObserverIF;
import net.ontopia.utils.OntopiaRuntimeException;
import net.ontopia.xml.DefaultXMLReaderFactory;
import net.ontopia.xml.Slf4jSaxErrorHandler;
import net.ontopia.xml.ValidatingContentHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import org.xml.sax.XMLReader;
/**
* INTERNAL: Provide easy access for reading in an action
* configuration file and generating an action registry object from
* it. Automatic observation of file changes are performed
*/
public class ActionConfigurator implements ConfigurationObservableIF {
// initialization of log facility
private static Logger log = LoggerFactory
.getLogger(ActionConfigurator.class.getName());
protected String contextPath;
protected String realPath;
protected String fileName;
protected long delay;
protected ActionRegistryIF registry;
protected Collection observers;
protected boolean logErrors = true;
/**
* Constructor which allows to specify the path of the servlet
* context and the configuration file name. No further file change
* observations are executed.
*/
public ActionConfigurator(String contextPath, String realPath, String fileName) {
this(contextPath, realPath, fileName, -1);
}
/**
* See {@link #ActionConfigurator(String, String, String)} with automatic
* observation for changes of the configuration file.
*
* @param delay - The delay in milliseconds between file change
* observations.
*/
public ActionConfigurator(String contextPath, String realPath, String fileName,
long delay) {
log.debug("ActionConfigurator initialised for '{}' delay: '{}' ms.",
fileName, delay);
this.contextPath = contextPath;
this.realPath = realPath;
this.fileName = fileName;
this.delay = delay;
this.observers = new ArrayList();
this.registry = null;
}
public void logErrors(boolean logErrors) {
this.logErrors = logErrors;
}
public void readAndWatchRegistry() {
ActionConfigWatchdog cfgdog = new ActionConfigWatchdog(this);
cfgdog.setDelay(delay);
cfgdog.start();
}
public String getFileName() {
if (realPath == null)
return fileName;
else
return realPath + File.separator + fileName;
}
public ActionRegistryIF getRegistry() {
return registry;
}
public String toString() {
StringBuilder sb = new StringBuilder(89);
sb.append("contextPath: ").append(contextPath)
.append(", realPath: ").append(realPath)
.append(", fileName: ").append(fileName)
.append(", delay: ").append(delay).append(" ms.");
return sb.toString();
}
// -----------------------------------------------------------------
// implementation of ConfigurationObservableIF interface
// -----------------------------------------------------------------
public void addObserver(ConfigurationObserverIF o) {
observers.add(o);
}
public void removeObserver(ConfigurationObserverIF o) {
observers.remove(o);
}
// -----------------------------------------------------------------
// internal methods
// ----------------------------------------------------------------
private InputStream getInputStream(String realpath, String name) throws IOException {
//System.out.println("realpath: " + realpath + ", name: " + name);
// adapted from StreamUtils.getInputStream(String)
InputStream istream;
if (name.startsWith("classpath:")) {
ClassLoader cl = Thread.currentThread().getContextClassLoader();
String resourceName = name.substring("classpath:".length());
istream = cl.getResourceAsStream(resourceName);
if (istream == null)
throw new IOException("Resource '" + resourceName + "' not found through class loader.");
log.debug("File loaded through class loader: {}", name);
} else if (name.startsWith("file:")) {
File f = new File(name.substring("file:".length()));
if (f.exists()) {
log.debug("File loaded from file system: {}", name);
istream = new FileInputStream(f);
} else
throw new IOException("File '" + f + "' not found.");
} else {
File f = (realPath == null ? null : new File(realPath, name));
if (f != null && f.exists()) {
log.debug("File loaded from file system: {}", f);
//System.out.println("file system: " + f);
istream = new FileInputStream(f);
} else {
ClassLoader cl = Thread.currentThread().getContextClassLoader();
istream = cl.getResourceAsStream(name);
//System.out.println("class loader: " + f);
if (istream != null)
log.debug("File loaded through class loader: {}", name);
}
}
//System.out.println("istream: " + istream);
return istream;
}
/**
* Reads in configuration file and try to generate a action registry
* object.
*/
public void readRegistryConfiguration() {
ActionRegistryIF freshRegistry = null;
try {
XMLReader parser = DefaultXMLReaderFactory.createXMLReader();
ActionConfigContentHandler handler = new ActionConfigContentHandler(contextPath);
InputSource src = new InputSource(new StringReader(schema));
src.setSystemId("http://www.ontopia.net/xtm-1.0/");
ValidatingContentHandler ch = new ValidatingContentHandler(handler, src,
false);
parser.setContentHandler(ch);
parser.setErrorHandler(new Slf4jSaxErrorHandler(log));
// parse the XML instance, now.
InputStream istream = getInputStream(realPath, fileName);
parser.parse(new InputSource(istream));
if (log.isDebugEnabled()) {
log.debug("Read in action configuration from {}", fileName);
log.debug("{}", handler.getRegistry());
}
freshRegistry = handler.getRegistry();
}
catch (SAXParseException e) {
if (logErrors)
log.error("Error in actions config file: " + e.toString() + " at: "+
e.getSystemId() + ":" + e.getLineNumber() + ":" +
e.getColumnNumber());
throw new OntopiaRuntimeException(e);
}
catch (SAXException se) {
throw new OntopiaRuntimeException(se);
}
catch (IOException ioe) {
throw new OntopiaRuntimeException(ioe);
}
if (freshRegistry != registry) {
this.registry = freshRegistry;
this.notifyObserversConfigurationChanged();
} else {
log.info("Action registry did not change.");
}
}
/**
* Loops through and notifies each observer if a new item was
* detected.
*/
protected void notifyObserversConfigurationChanged() {
Iterator it = observers.iterator();
while (it.hasNext()) {
ConfigurationObserverIF o = (ConfigurationObserverIF) it.next();
o.configurationChanged(registry);
}
}
private static final String schema =
"<?xml version='1.0' encoding='UTF-8'?><!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ This is the DTD defining the Action Configuration file syntax for Ontopia's Web Editor Framework. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~--><grammar xmlns:a='http://relaxng.org/ns/compatibility/annotations/1.0' xmlns='http://relaxng.org/ns/structure/1.0' datatypeLibrary='http://www.w3.org/2001/XMLSchema-datatypes'> <define name='actionConfig'> <element name='actionConfig'> <ref name='attlist.actionConfig'/> <ref name='classMap'/> <optional> <ref name='buttonMap'/> </optional> <optional> <ref name='fieldMap'/> </optional> <optional> <ref name='globalForwards'/> </optional> <optional> <ref name='globalProperties'/> </optional> <oneOrMore> <ref name='actionGroup'/> </oneOrMore> </element> </define> <define name='attlist.actionConfig' combine='interleave'> <empty/> </define> <!-- ................................................................... Global Mapping between class short name and fully qualified java class path. --> <define name='classMap'> <element name='classMap'> <ref name='attlist.classMap'/> <oneOrMore> <ref name='class'/> </oneOrMore> </element> </define> <define name='attlist.classMap' combine='interleave'> <empty/> </define> <define name='class'> <element name='class'> <ref name='attlist.class'/> <empty/> </element> </define> <define name='attlist.class' combine='interleave'> <attribute name='shortcut'> <data type='ID'/> </attribute> </define> <define name='attlist.class' combine='interleave'> <attribute name='fullname'/> </define> <!-- ................................................................... Global Mapping between image short name and image properties like location and dimension used by action buttons. --> <define name='buttonMap'> <element name='buttonMap'> <ref name='attlist.buttonMap'/> <oneOrMore> <ref name='image'/> </oneOrMore> </element> </define> <define name='attlist.buttonMap' combine='interleave'> <empty/> </define> <define name='image'> <element name='image'> <ref name='attlist.image'/> <empty/> </element> </define> <!-- name: under which the image can be retrieved --> <define name='attlist.image' combine='interleave'> <attribute name='name'> <data type='ID'/> </attribute> </define> <!-- src: Relative URL of the image location --> <define name='attlist.image' combine='interleave'> <optional> <attribute name='src'/> </optional> </define> <define name='attlist.image' combine='interleave'> <optional> <attribute name='absolutesrc'/> </optional> </define> <!-- width: image width in pixels --> <define name='attlist.image' combine='interleave'> <optional> <attribute name='width'/> </optional> </define> <!-- height: image height in pixels --> <define name='attlist.image' combine='interleave'> <optional> <attribute name='height'/> </optional> </define> <!-- border: image border in pixels (default: 0) --> <define name='attlist.image' combine='interleave'> <optional> <attribute name='border'/> </optional> </define> <!-- align: image align mode (default: 'middle') --> <define name='attlist.image' combine='interleave'> <optional> <attribute name='align'/> </optional> </define> <!-- ................................................................... Global Mapping between field short name and field properties like type and dimension used by action fields. --> <define name='fieldMap'> <element name='fieldMap'> <ref name='attlist.fieldMap'/> <oneOrMore> <ref name='field'/> </oneOrMore> </element> </define> <define name='attlist.fieldMap' combine='interleave'> <empty/> </define> <define name='field'> <element name='field'> <ref name='attlist.field'/> <empty/> </element> </define> <!-- name: under which the field can be retrieved --> <define name='attlist.field' combine='interleave'> <attribute name='name'> <data type='ID'/> </attribute> </define> <!-- type: the type of this input field (text|textarea) --> <define name='attlist.field' combine='interleave'> <attribute name='type'/> </define> <!-- maxlength: maxium length of this field (only if rows==1) --> <define name='attlist.field' combine='interleave'> <optional> <attribute name='maxlength'/> </optional> </define> <!-- columns: number of field columns --> <define name='attlist.field' combine='interleave'> <optional> <attribute name='columns'/> </optional> </define> <!-- rows: number of field rows (default: 1) --> <define name='attlist.field' combine='interleave'> <optional> <attribute name='rows'/> </optional> </define> <!-- ................................................................... Global Forward Definitions used for an action if not explicitly defined forward exists. --> <define name='globalForwards'> <element name='globalForwards'> <ref name='attlist.globalForwards'/> <oneOrMore> <ref name='forward'/> </oneOrMore> </element> </define> <define name='attlist.globalForwards' combine='interleave'> <empty/> </define> <define name='forward'> <element name='forward'> <ref name='attlist.forward'/> <zeroOrMore> <ref name='reqParam'/> </zeroOrMore> </element> </define> <define name='attlist.forward' combine='interleave'> <attribute name='name'> <data type='ID'/> </attribute> </define> <!-- path: relative URI to forward to --> <define name='attlist.forward' combine='interleave'> <attribute name='path'/> </define> <!-- type: Action response type this forward page belongs to --> <define name='attlist.forward' combine='interleave'> <optional> <attribute name='type' a:defaultValue='all'> <choice> <value>success</value> <value>failure</value> <value>all</value> </choice> </attribute> </optional> </define> <!-- frame: the response of the forward should appear --> <define name='attlist.forward' combine='interleave'> <optional> <attribute name='frame'> <choice> <value>edit</value> <value>search</value> </choice> </attribute> </optional> </define> <!-- nextAction: must be a valid action 'name' entry the action name pattern is used as a template which is processed by the specified parameter rule. --> <define name='attlist.forward' combine='interleave'> <optional> <attribute name='nextAction'> <data type='IDREF'/> </attribute> </optional> </define> <!-- paramRule: Shortcut of parameter rule class (optional). --> <define name='attlist.forward' combine='interleave'> <optional> <attribute name='paramRule'> <data type='IDREF'/> </attribute> </optional> </define> <define name='reqParam'> <element name='reqParam'> <ref name='attlist.reqParam'/> <empty/> </element> </define> <define name='attlist.reqParam' combine='interleave'> <attribute name='name'/> </define> <!-- value: if no fix value is given it will be taken from the request --> <define name='attlist.reqParam' combine='interleave'> <optional> <attribute name='value'/> </optional> </define> <!-- ................................................................... Global Property Definitions for Actions and InputFields that are method names and the related values. --> <define name='globalProperties'> <element name='globalProperties'> <ref name='attlist.globalProperties'/> <oneOrMore> <ref name='actionType'/> </oneOrMore> </element> </define> <define name='attlist.globalProperties' combine='interleave'> <empty/> </define> <define name='actionType'> <element name='actionType'> <ref name='attlist.actionType'/> <oneOrMore> <ref name='actionProp'/> </oneOrMore> </element> </define> <!-- class: must be a valid class 'shortcut' entry --> <define name='attlist.actionType' combine='interleave'> <attribute name='class'> <data type='IDREF'/> </attribute> </define> <define name='actionProp'> <element name='actionProp'> <ref name='attlist.actionProp'/> <empty/> </element> </define> <!-- name: correlates to java bean method-name in the specified class/interface --> <define name='attlist.actionProp' combine='interleave'> <attribute name='name'/> </define> <!-- value: the value with which the method will be invoked --> <define name='attlist.actionProp' combine='interleave'> <attribute name='value'/> </define> <!-- ................................................................... Action Group for covering the modification actions on topic map objects (like for example changing the topic type or removing a subject indicator from a topic object). Note that actions/inputFields will be executed in the order they are given in the file. --> <define name='actionGroup'> <element name='actionGroup'> <ref name='attlist.actionGroup'/> <zeroOrMore> <choice> <ref name='inputField'/> <ref name='action'/> </choice> </zeroOrMore> <zeroOrMore> <ref name='forward'/> </zeroOrMore> <ref name='forwardRules'/> </element> </define> <define name='attlist.actionGroup' combine='interleave'> <attribute name='name'> <data type='ID'/> </attribute> </define> <define name='inputField'> <element name='inputField'> <ref name='attlist.inputField'/> <empty/> </element> </define> <!-- name: under which this input element can be used by the tags, must be unique inside the same action group --> <define name='attlist.inputField' combine='interleave'> <attribute name='name'/> </define> <!-- class: must be a valid class 'shortcut' entry --> <define name='attlist.inputField' combine='interleave'> <attribute name='class'> <data type='IDREF'/> </attribute> </define> <define name='action'> <element name='action'> <ref name='attlist.action'/> <empty/> </element> </define> <!-- name: under which this action can be triggered by forms, must be unique inside the same action group --> <define name='attlist.action' combine='interleave'> <attribute name='name'> <data type='NMTOKEN'/> </attribute> </define> <!-- class: must be a valid class 'shortcut' entry --> <define name='attlist.action' combine='interleave'> <attribute name='class'> <data type='IDREF'/> </attribute> </define> <!-- exclusive: whether the action is exclusive or not --> <define name='attlist.action' combine='interleave'> <optional> <attribute name='exclusive'> <choice> <value>true</value> <value>false</value> </choice> </attribute> </optional> </define> <define name='forwardRules'> <element name='forwardRules'> <ref name='attlist.forwardRules'/> <ref name='forwardDefault'/> <optional> <ref name='forwardLocked'/> </optional> <zeroOrMore> <ref name='forwardRule'/> </zeroOrMore> </element> </define> <define name='attlist.forwardRules' combine='interleave'> <empty/> </define> <define name='forwardDefault'> <element name='forwardDefault'> <ref name='attlist.forwardDefault'/> <zeroOrMore> <ref name='reqParam'/> </zeroOrMore> </element> </define> <!-- There are two ways of specifying the default forward either by referencing to an existing forward definition or directly by giving the path by an URI --> <!-- forward: must be a valid forward 'name' entry --> <define name='attlist.forwardDefault' combine='interleave'> <optional> <attribute name='forward'> <data type='IDREF'/> </attribute> </optional> </define> <!-- path: relative URI to forward to, because this is a convenience- shortcut, so no differentiation between success and failure page can be made --> <define name='attlist.forwardDefault' combine='interleave'> <optional> <attribute name='path'/> </optional> </define> <define name='forwardLocked'> <element name='forwardLocked'> <ref name='attlist.forwardLocked'/> <empty/> </element> </define> <!-- path: relative URI to forward to in case a lock is encountered --> <define name='attlist.forwardLocked' combine='interleave'> <attribute name='path'/> </define> <!-- frame: the response of the forward should appear --> <define name='attlist.forwardLocked' combine='interleave'> <optional> <attribute name='frame'> <choice> <value>edit</value> <value>search</value> </choice> </attribute> </optional> </define> <define name='forwardRule'> <element name='forwardRule'> <ref name='attlist.forwardRule'/> <empty/> </element> </define> <!-- action: must be a valid action 'name' entry --> <define name='attlist.forwardRule' combine='interleave'> <attribute name='action'> <data type='NMTOKEN'/> </attribute> </define> <!-- forward: must be a valid forward 'name' entry --> <define name='attlist.forwardRule' combine='interleave'> <attribute name='forward'> <data type='IDREF'/> </attribute> </define> <start> <choice> <ref name='actionConfig'/> </choice> </start></grammar>";
}