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.xml.applicationdata;
011:
012: import java.io.*;
013: import java.util.*;
014:
015: import org.mmbase.module.core.*;
016: import org.mmbase.module.corebuilders.*;
017: import org.mmbase.util.XMLContextDepthReader;
018: import org.mmbase.util.logging.*;
019: import org.mmbase.util.xml.ApplicationReader;
020:
021: /**
022: * This class is used to write (export) a selection of nodes to xml format.
023: * The nodes to export are read from a XML context file, which specifies the
024: * startnode and depth to which to parse.
025: * The current version of this class combines a number of methods which we want to split - or at least share -
026: * with a seperate class for handling contexts.
027: * Note that because of it's static nature, no object instance need be made (in fact, none CAN be made) of this class.<br />
028: *
029: * @since MMBase-1.8
030: * @author Daniel Ockeloen
031: * @author Jacco de Groot
032: * @author Pierre van Rooden
033: * @version $Id: ContextDepthDataWriter.java,v 1.5 2007/02/25 17:56:58 nklasens Exp $
034: */
035: public class ContextDepthDataWriter {
036:
037: /**
038: * Logging instance
039: */
040: private static Logger log = Logging
041: .getLoggerInstance(ContextDepthDataWriter.class.getName());
042:
043: /**
044: * Writes an application's nodes, according to that application's contexts, to a path.
045: * The files written are stored in a subdirectory (named after the application), and contain the datasource (xml) files for
046: * both datanodes and relation nodes.
047: * @param app A <code>ApplicationReader</code> initialised to read the application's description (xml) file
048: * This object is used to retrieve what builder and relations are needed, and in which files data should be stored.
049: * @param capp A <code>XMLContextDepthReader</code> initialised to read the application's context file
050: * This object is used to retrieve information regarding search depth and starting nodes for
051: * the search tree whoch determines what nodes are part of this application.
052: * @param targetpath The path where to save the application
053: * @param mmb Reference to the MMbase processormodule. Used to retrieve the nodes to write.
054: * @param logger Storage for messages which can be displayed to the user.
055: * @return Returns true if succesful, false if no valid depth or startnode could be found
056: * Failure of the export itself is not detected, though may be visible in the messages returned.
057: * @throws IOException if one or more files could not be written
058: */
059: public static boolean writeContext(ApplicationReader app,
060: XMLContextDepthReader capp, String targetpath, MMBase mmb,
061: Logger logger) {
062: // First determine the startnodes, following the specs in the current context reader.
063: int startnode = getStartNode(capp, mmb);
064: if (startnode == -1) {
065: return false;
066: }
067: // get the depth from the current context reader
068: int depth = capp.getDepth();
069: if (depth == -1) {
070: return false;
071: }
072: // get valid builders to filter
073: HashSet<Integer> fb = getFilterBuilders(
074: app.getNeededBuilders(), mmb.getTypeDef());
075:
076: // the trick is to get all nodes until depth x and filter them
077: HashSet<Integer> relnodes = new HashSet<Integer>();
078: HashSet<Integer> nodes = new HashSet<Integer>();
079: getSubNodes(startnode, depth, fb, nodes, relnodes, mmb);
080:
081: logger.info("Context found : " + nodes.size()
082: + " nodes in application, " + relnodes.size()
083: + " relations.");
084:
085: // create the dir for the Data & resource files
086: File file = new File(targetpath + "/" + app.getName());
087: file.mkdirs();
088: // write DataSources
089: writeDataSources(app, nodes, targetpath, mmb, logger);
090: // write relationSources
091: writeRelationSources(app, relnodes, targetpath, mmb, logger);
092:
093: return true;
094: }
095:
096: /**
097: * Writes the required datasources to their corresponding xml files by calling writeNodes()
098: * @param app The ApplicationReader object, which is used to retrieve what datasources to write (and to what file).
099: * @param nodes The nodes that are part of the application. Those that are of a type compatible with the datasources are exported.
100: * @param targetpath Path where the xml files are written
101: * @param mmb MMBase object used to retrieve builder information
102: * @param logger Used to store messages that can be showmn to the user
103: */
104: static void writeDataSources(ApplicationReader app,
105: HashSet<Integer> nodes, String targetpath, MMBase mmb,
106: Logger logger) {
107: writeNodes(app, nodes, targetpath, mmb, logger, false);
108: }
109:
110: /**
111: * Writes the required relation sources to their corresponding xml files by calling writeNodes()
112: * @param app The ApplicationReader object, which is used to retrieve what relationsources to write (and to what file).
113: * @param nodes The relation nodes that are part of the application. Those that are of a type compatible with the relationsources are exported.
114: * @param targetpath Path where the xml files are written
115: * @param mmb MMBase object used to retrieve builder information
116: * @param logger Used to store messages that can be showmn to the user
117: */
118: static void writeRelationSources(ApplicationReader app,
119: HashSet<Integer> nodes, String targetpath, MMBase mmb,
120: Logger logger) {
121: writeNodes(app, nodes, targetpath, mmb, logger, true);
122: }
123:
124: /**
125: * Writes the nodes to their corresponding xml files
126: * @param app The ApplicationReader object, which is used to retrieve what sources to write (and to what file).
127: * @param nodes The nodes that are part of the application. Those that are of a type compatible with the sources are exported.
128: * @param targetpath Path where the xml files are written
129: * @param mmb MMBase object used to retrieve builder information
130: * @param logger Used to store messages that can be showmn to the user
131: * @param isRelation Indicates whether the nodes to write are data (false) or relation (true) nodes
132: */
133: static void writeNodes(ApplicationReader app,
134: HashSet<Integer> nodes, String targetpath, MMBase mmb,
135: Logger logger, boolean isRelation) {
136:
137: //before we write the data first sort the list
138: //so that node fields that point to the same node type
139: //have more chance to exist. A example of this is the community
140: //where the message nodes contain a thread nodefield
141: //upon creation there first must exist a thread message
142: //so the "thread message" will have a lower number
143: List<Integer> list = new Vector<Integer>();
144: list.addAll(nodes);
145: Collections.sort(list, new Comparator<Integer>() {
146: public int compare(Integer o1, Integer o2) {
147: return o1.compareTo(o2);
148: }
149: });
150: // Retrieve an enumeration of sources to write
151: // The list of sources retrieved is dependent on whether the nodes to write are data or relation nodes
152: Iterator<Map<String, String>> res;
153: if (isRelation) {
154: res = app.getRelationSources().iterator();
155: } else {
156: res = app.getDataSources().iterator();
157: }
158: // determine target path subdirectory
159: String subtargetpath = targetpath + "/" + app.getName() + "/";
160:
161: // create a list of writer objects for the nodes
162: Hashtable<String, NodeWriter> nodeWriters = new Hashtable<String, NodeWriter>();
163: while (res.hasNext()) {
164: Map<String, String> bset = res.next(); // retrieve source builder name
165: String name = bset.get("builder");
166:
167: // Create nodewriter for this builder
168: NodeWriter nw = new NodeWriter(mmb, logger, subtargetpath,
169: name, isRelation);
170: // and store in table
171: nodeWriters.put(name, nw);
172: }
173: MMObjectBuilder bul = mmb.getMMObject("typedef"); // get Typedef object
174: int nrofnodes = 0; // set total nodes to export to zero (is this used?).
175: // Store all the nodes that apply using their corresponding NodeWriter object
176: for (Integer integer : list) {
177: // retrieve the node to export
178: int nr = integer.intValue();
179: MMObjectNode node = bul.getNode(nr);
180: String name = node.getName();
181: NodeWriter nodeWriter = nodeWriters.get(name);
182: // export the node if the writer was found
183: if (nodeWriter != null) {
184: nodeWriter.write(node);
185: nrofnodes++;
186: }
187: // if null, the node was specified as being part of the application, but should not (for some reason) be exported
188: // note that this plays havoc with the relations!
189: // better solution (not implemented): create Writers 'on the fly' if necessary, and export
190: // everything, even if no datasource is given (should not be too tough), but this also means changing the context file.
191: }
192:
193: // close the files.
194: for (Enumeration<String> e = nodeWriters.keys(); e
195: .hasMoreElements();) {
196: String name = e.nextElement();
197: NodeWriter nodeWriter;
198: nodeWriter = nodeWriters.get(name);
199: nodeWriter.done();
200: }
201: }
202:
203: /**
204: * Determines the number of the node referenced by another node.
205: * @param nodeNumber number of the referencing node
206: * @param relationNode node from the relationtable containing the relation data
207: * @returns An <code>int</code> value for the number of the node referenced
208: */
209: static int getRelatedNode(int nodeNumber, MMObjectNode relationNode) {
210: int snumber = relationNode.getIntValue("snumber"); // referenced node is either source
211: if (snumber == nodeNumber) {
212: return relationNode.getIntValue("dnumber"); // or destination
213: } else {
214: return snumber;
215: }
216: }
217:
218: /**
219: * Searches the MMBase cloud, colelcting all nodes (and corresponmding relation nodes) that belong to a specific
220: * type, and which can be traced up to a certain depth of nodes to a starting node.
221: *
222: * @param startnodenr the number of the node to start with
223: * @param maxdeoth the maximum depth a tree is traversed. A depth of 0 or less means only the sdtartnode is added.
224: * A depth of one includes all teh nodes refernced by the startnode, etc.
225: * Relation nodes are not counted when determining 'depth'.
226: * @param fb a <code>HashSet</code> containing the set of types that are allowed for export
227: * @param nodesdoneSet A <code>HashSet</code> which holds all nodes that are already 'done' or 'almost done'. this set is expanded in the method
228: * nodes already in this set are skipped (optimization). After return, the set has been expanded
229: * with all nodes found while traversing the cloud
230: * @param mmb MMBase object used to retrieve builder information
231: */
232:
233: static void getSubNodes(int startnodenr, int maxdepth,
234: HashSet<Integer> fb, HashSet<Integer> nodesdoneSet,
235: HashSet<Integer> relationnodesSet, MMBase mmb) {
236: HashSet<Integer> nodesSet_current = null; // holds all nodes not yet 'done' that are on the current level
237: HashSet<Integer> nodesSet_next = new HashSet<Integer>(); // holds all nodes not yet 'done' that are on the next level
238: InsRel bul = mmb.getInsRel(); // builder for collecting relations. should be changed to MMRelations later on!
239: Integer type = bul.getNodeType(startnodenr); // retrieve node type (new method in MMObjectBuiilder)
240: if (!fb.contains(type)) { // exit if the type of this node conflicts.
241: // essentially, no nodes are added. This can only occur if the context of
242: // an application specified an invalid node.
243: return;
244: }
245: nodesSet_next.add(startnodenr); // add the very first node to the set...
246: // For each depth of the tree, traverse the nodes on that depth
247: for (int curdepth = 1; curdepth <= maxdepth; curdepth++) {
248: nodesSet_current = nodesSet_next; // use the next level of nodes to tarverse
249: nodesSet_next = new HashSet<Integer>(); // and create a new holder for the nodes one level deeper
250:
251: // since the nodes on this level are 'almost done', and therefor should be skipped
252: // when referenced in the next layer, add the current set to the set of nodes that are 'done'
253: //
254: nodesdoneSet.addAll(nodesSet_current);
255: // iterate through the current level
256: for (Iterator<Integer> curlist = nodesSet_current
257: .iterator(); curlist.hasNext();) {
258: // get the next node's number
259: Integer this nodenr = curlist.next();
260: // Iterate through all the relations of a node
261: // determining relations has to be adapted when using MMRelations!
262: for (Iterator<MMObjectNode> rel = bul
263: .getRelationsVector(this nodenr.intValue())
264: .iterator(); rel.hasNext();) {
265: // get the relation node and node number
266: MMObjectNode relnode = rel.next();
267: Integer relnumber = relnode.getIntValue("number");
268: // check whether to add the referenced node
269: // and the relation between this node and the referenced one.
270: // if relation is in pool, save trouble and do not traverse further
271: if (!relationnodesSet.contains(relnumber)) {
272: // determine node referenced
273: int nodenumber = getRelatedNode(this nodenr
274: .intValue(), relnode);
275: // check type of referenced node
276: type = bul.getNodeType(nodenumber);
277: if (fb.contains(type)) { // good node? then proceed
278: // add the relation node
279: relationnodesSet.add(relnumber);
280: // if the node has been 'done', don't add it!
281: Integer nodeNumber = nodenumber;
282: if (!nodesdoneSet.contains(nodeNumber)) {
283: // because we use a set, no double nodes will be added (cool, uh?)
284: nodesSet_next.add(nodeNumber);
285: }
286: }
287: }
288: }
289: }
290: }
291: // add the last retrieved set to the set of nodes that are 'done'
292: nodesdoneSet.addAll(nodesSet_next);
293: return;
294: }
295:
296: /**
297: * Retrieves the builders used for filtering the nodes for this application
298: * @param filter Vector containign all the buildernames that are part of this application
299: * Note that being part of an application does not mean that they are exported!
300: * @param bul reference to the TypeDef builder, used for rertrieving builder types
301: * @return a <code>HashSet</code>, containing the types (Integer) of all builders part of this application.
302: */
303: static HashSet<Integer> getFilterBuilders(
304: List<Map<String, String>> filter, TypeDef bul) {
305: HashSet<Integer> resultset = new HashSet<Integer>();
306: for (Map<String, String> bset : filter) {
307: String name = bset.get("name");
308: int value = bul.getIntValue(name);
309: if (value != -1) {
310: resultset.add(value);
311: } else {
312: log
313: .error("XMLContextDepthWriter -> can't get intvalue for : "
314: + name);
315: }
316: }
317: return resultset;
318: }
319:
320: /**
321: * Retrieves the number of the startnode referenced by the context configuration file..
322: * Returns always only one node (should be changed?)
323: * @param capp XMLContextDepthReader object for retrieving data from the context
324: * @param mmb reference to the MMBase object, used for retrieving aliases and builders
325: * @return An <code>integer</code>, the number of the startnode if succesful, -1 otherwise.
326: */
327: static int getStartNode(XMLContextDepthReader capp, MMBase mmb) {
328: // first check for an alias
329: String alias = capp.getStartAlias();
330: if (alias != null) {
331: // if so, get the node associated with that alias
332: OAlias bul = (OAlias) mmb.getMMObject("oalias");
333: int number = bul.getNumber(alias);
334: if (number == -1)
335: log
336: .error("Invalid Start Node Alias please make sure its valid");
337: return number;
338: } else {
339: // otherwise, get a builder and the where clause to run on that builder
340: String builder = capp.getStartBuilder();
341: String where = capp.getStartWhere();
342: // retrieve the actual builder
343: MMObjectBuilder bul = mmb.getMMObject(builder);
344: if (bul != null) {
345: // find the nodes that match
346: Enumeration<MMObjectNode> results = bul.search(where);
347: // check if there are any nodes
348: if (results.hasMoreElements()) {
349: // then return the first node found.
350: MMObjectNode node = results.nextElement();
351: return node.getIntValue("number");
352: }
353: } else {
354: log.error("ContextDepthWriter-> can't find builder ("
355: + builder + ")");
356: }
357: }
358: log
359: .error("Invalid Start Node please fix your 'where' settings or use a alias");
360: return -1;
361: }
362:
363: /**
364: * Saves a string value to a file.
365: * @param filename Name of the file to save.
366: * @param value string to store in the file
367: * @return True if succesfull, false if an error occurred.
368: */
369: static boolean saveFile(String filename, String value) {
370: File sfile = new File(filename);
371: try {
372: DataOutputStream scan = new DataOutputStream(
373: new FileOutputStream(sfile));
374: scan.writeBytes(value);
375: scan.flush();
376: scan.close();
377: } catch (Exception e) {
378: log.error(e);
379: log.error(Logging.stackTrace(e));
380: return false;
381: }
382: return true;
383: }
384:
385: /**
386: * Saves an array of byte to a file.
387: * @param filename Name of the file to save.
388: * @param value array to stiore in the file
389: * @return True if succesfull, false if an error occurred.
390: */
391: static boolean saveFile(String filename, byte[] value) {
392: File sfile = new File(filename);
393: try {
394: DataOutputStream scan = new DataOutputStream(
395: new FileOutputStream(sfile));
396: scan.write(value);
397: scan.flush();
398: scan.close();
399: } catch (Exception e) {
400: log.error(e);
401: log.error(Logging.stackTrace(e));
402: return false;
403: }
404: return true;
405: }
406:
407: /**
408: * Writes the context file, based on what was supplied by the application
409: * @param capp XMLContextDepthReader providing original context data
410: * @param filename Name of the xml file to save.
411: * @return always true
412: */
413: public static boolean writeContextXML(XMLContextDepthReader capp,
414: String filename) {
415: String body = "<contextdepth>\n";
416: String alias = capp.getStartAlias();
417: if (alias != null) {
418: body += "\t<startnode alias=\"" + alias + "\" />\n";
419: } else {
420: body += "\t<startnode>\n";
421: body += "\t\t<builder>" + capp.getStartBuilder()
422: + "</builder>\n";
423: body += "\t\t<where>" + capp.getStartWhere() + "</where>\n";
424: body += "\t</startnode>\n\n";
425: }
426: body += "\t<depth>" + capp.getDepth() + "</depth>\n";
427: body += "</contextdepth>\n";
428: saveFile(filename, body);
429: return true;
430: }
431:
432: }
|