001: /*
002:
003: This software is OSI Certified Open Source Software.
004: OSI Certified is a certification mark of the Open Source Initiative.
005:
006: The license (Mozilla version 1.0) can be read at the MMBase site.
007: See http://www.MMBase.org/license
008:
009: */
010: package org.mmbase.util.functions;
011:
012: import org.mmbase.util.logging.Logger;
013: import org.mmbase.util.logging.Logging;
014: import org.mmbase.util.xml.DocumentReader;
015: import org.mmbase.util.*;
016:
017: import java.io.*;
018: import java.util.*;
019: import org.xml.sax.InputSource;
020: import org.w3c.dom.*;
021: import java.net.*;
022:
023: /**
024: * A utility class for maintaining and querying functionsets.
025: * A set function belongs to a certain namespace of functions ('sets'), and therefore is identified by
026: * two strings: The name of the 'set' and the name of the function.
027: * <br />
028: * Function sets can be defined in the functions/functionsets.xml configuration file.
029: * <br />
030: * This class implements a number of static methods for maintaining {@link FunctionSet} objects,
031: * and filling these with {@link FunctionSet} objects that match the namespace.
032: * It also implements a {@link #getFunction} method for obtaining a function from such a set.
033: *
034: * @author Daniël Ockeloen
035: * @author Michiel Meeuwissen
036: * @since MMBase-1.8
037: * @version $Id: FunctionSets.java,v 1.31 2007/11/25 18:25:49 nklasens Exp $
038: */
039: public class FunctionSets {
040:
041: public static final String DTD_FUNCTIONSET_1_0 = "functionset_1_0.dtd";
042: public static final String DTD_FUNCTIONSETS_1_0 = "functionsets_1_0.dtd";
043:
044: public static final String PUBLIC_ID_FUNCTIONSET_1_0 = "-//MMBase//DTD functionset config 1.0//EN";
045: public static final String PUBLIC_ID_FUNCTIONSETS_1_0 = "-//MMBase//DTD functionsets config 1.0//EN";
046:
047: private static final Logger log = Logging
048: .getLoggerInstance(FunctionSets.class);
049:
050: private static final Map<String, FunctionSet> functionSets = new HashMap<String, FunctionSet>();
051:
052: static {
053: XMLEntityResolver.registerPublicID(PUBLIC_ID_FUNCTIONSET_1_0,
054: DTD_FUNCTIONSET_1_0, FunctionSets.class);
055: XMLEntityResolver.registerPublicID(PUBLIC_ID_FUNCTIONSETS_1_0,
056: DTD_FUNCTIONSETS_1_0, FunctionSets.class);
057: }
058:
059: /**
060: * Returns the {@link Function} with the given function name, and which exists in the set with the given set name.
061: * If this is the first call, or if the set does not exist in the cache, the cache
062: * is refreshed by reading the functionset.xml configuration file.
063: * @param setName the name of the function set
064: * @param functionName the name of the function
065: * @return the {@link Function}, or <code>nulll</code> if either the fucntion or set is not defined
066: */
067: public static Function<?> getFunction(String setName,
068: String functionName) {
069: FunctionSet set = getFunctionSet(setName);
070: if (set != null) {
071: Function<?> fun = set.getFunction(functionName);
072: if (fun != null) {
073: return fun;
074: } else {
075: log.warn("No function with name : " + functionName
076: + " in set : " + setName
077: + ", functions available: " + set);
078: }
079: } else {
080: log.warn("No functionset with name : " + setName);
081: }
082: return null;
083: }
084:
085: static {
086: ResourceLoader functionLoader = ResourceLoader
087: .getConfigurationRoot().getChildResourceLoader(
088: "functions");
089: // read the XML
090: try {
091: ResourceWatcher watcher = new ResourceWatcher(
092: functionLoader) {
093: public void onChange(String resource) {
094: functionSets.clear();
095: clear();
096: add(resource);
097: readSets(this );
098: }
099: };
100: watcher.start();
101: watcher.onChange("functionsets.xml");
102: } catch (Throwable t) {
103: log.error(t.getClass().getName() + ": "
104: + Logging.stackTrace(t));
105: }
106:
107: }
108:
109: /**
110: * Returns the {@link FunctionSet} with the given set name.
111: * If this is the first call, or if the set does not exist in the cache, the cache
112: * is refreshed by reading the functionset.xml configuration file.
113: * configuration file.
114: * @param setName the name of the function set
115: * @return the {@link FunctionSet}, or <code>null</code> if the set is not defined
116: */
117: public static FunctionSet getFunctionSet(String setName) {
118: return functionSets.get(setName);
119: }
120:
121: /**
122: * @since MMBase-1.9
123: */
124: public static Map<String, FunctionSet> getFunctionSets() {
125: return Collections.unmodifiableMap(functionSets);
126: }
127:
128: /**
129: * Reads the current function set from the functionsets.xml configuration file.
130: * The read sets are added to the functionset cache.
131: * @todo It makes FunctionSet's now using a sub-XML. It would be possible to create a complete function-set by reflection.
132: */
133:
134: private static void readSets(ResourceWatcher watcher) {
135:
136: List<URL> resources = watcher.getResourceLoader()
137: .getResourceList("functionsets.xml");
138: log.service("Using " + resources);
139: ListIterator<URL> i = resources.listIterator();
140: while (i.hasNext())
141: i.next();
142: while (i.hasPrevious()) {
143: try {
144: URL u = i.previous();
145: log.service("Reading " + u);
146: URLConnection con = u.openConnection();
147: if (con.getDoInput()) {
148: InputSource source = new InputSource(con
149: .getInputStream());
150: DocumentReader reader = new DocumentReader(source,
151: FunctionSets.class);
152:
153: for (Element n : reader.getChildElements(
154: "functionsets", "functionset")) {
155: String setName = n.getAttribute("name");
156: if (functionSets.containsKey(setName)) {
157: log.warn("The function-set '" + setName
158: + "' did exist already");
159: }
160: String setResource = n.getAttribute("resource");
161: if (setResource.equals(""))
162: setResource = n.getAttribute("file"); // deprecated, it's not necessarily a file
163: watcher.add(setResource);
164: decodeFunctionSet(watcher.getResourceLoader(),
165: setResource, setName);
166: }
167: }
168: } catch (Exception e) {
169: log.error(e);
170: }
171: }
172: }
173:
174: /**
175: * Reads a 'sub' xml (a functionset XML) referred to by functionsets.xml.
176: * The read set is added to the functionset cache.
177: * @param
178: * @param
179: */
180: private static void decodeFunctionSet(ResourceLoader loader,
181: String setResource, String setName) throws IOException {
182: DocumentReader reader = new DocumentReader(loader
183: .getInputSource(setResource), FunctionSets.class);
184:
185: String setDescription = reader
186: .getElementValue("functionset.description");
187:
188: FunctionSet functionSet = new FunctionSet(setName,
189: setDescription);
190: functionSets.put(setName, functionSet);
191:
192: for (Element element : reader.getChildElements("functionset",
193: "function")) {
194: String functionName = reader.getElementAttributeValue(
195: element, "name");
196: if (functionName != null) {
197:
198: Element a = reader.getElementByPath(element,
199: "function.type");
200:
201: String type = reader.getElementValue(a); // 'class' or 'instance'
202:
203: a = reader.getElementByPath(element,
204: "function.description");
205: String description = reader.getElementValue(a);
206:
207: a = reader.getElementByPath(element, "function.class");
208: String className = reader.getElementValue(a);
209:
210: a = reader.getElementByPath(element, "function.method");
211: String methodName = reader.getElementValue(a);
212:
213: // read the return types and values
214: a = reader.getElementByPath(element, "function.return");
215: ReturnType returnType = null;
216: if (a != null) {
217: String returnTypeClassName = reader
218: .getElementAttributeValue(a, "type");
219: if (returnTypeClassName != null) {
220: try {
221: returnType = new ReturnType(
222: Parameter
223: .getClassForName(returnTypeClassName),
224: "");
225: } catch (Exception e) {
226: log.warn("Cannot determine return type : "
227: + returnTypeClassName
228: + ", will auto-detect");
229: }
230: }
231: }
232:
233: /* obtaining field definitions for a result Node... useful ??
234:
235: for (Element return_element: reader.getChildElements(a, "field")) {
236: String returnFieldName = reader.getElementAttributeValue(return_element, "name");
237: String returnFieldValueType = reader.getElementAttributeValue(return_element, "type");
238: String returnFieldDescription = reader.getElementAttributeValue(return_element, "description");
239: // not implemented (yet) :
240: // FunctionReturnValue r=new FunctionReturnValue(returnname,returnvaluetype);
241: // fun.addReturnValue(returnname,r);
242: // r.setDescription(description);
243: }
244: */
245:
246: // read the parameters
247: Parameter<?>[] parameters = Parameter
248: .readArrayFromXml(element);
249: for (Parameter param : parameters) {
250: if (param.getClass().isPrimitive()
251: && param.getDefaultValue() == null) {
252: // that would give enigmatic IllegalArgumentExceptions, so fix that.
253: param.setDefaultValue(Casting.toType(param
254: .getClass(), -1));
255: log
256: .debug("Primitive parameter '"
257: + param
258: + "' had default value null, which is impossible for primitive types. Setting to "
259: + param.getDefaultValue());
260: }
261: }
262:
263: try {
264: Class functionClass;
265: try {
266: functionClass = Class.forName(className);
267: } catch (Exception e) {
268: throw new RuntimeException(
269: "Can't create an application function class : "
270: + className + " "
271: + e.getMessage(), e);
272: }
273: SetFunction fun = new SetFunction(functionName,
274: parameters, returnType, functionClass,
275: methodName, SetFunction.Type.valueOf(type
276: .toUpperCase()));
277: fun.setDescription(description);
278: functionSet.addFunction(fun);
279: } catch (Exception e) {
280: log.error(e.getMessage(), e);
281: }
282: }
283: }
284: }
285:
286: }
|