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.bridge.Field;
016: import org.mmbase.core.CoreField;
017: import org.mmbase.module.core.*;
018: import org.mmbase.module.corebuilders.*;
019: import org.mmbase.util.logging.*;
020: import org.mmbase.util.Casting;
021: import org.mmbase.util.Encode;
022:
023: /**
024: * Utility class for writing xml files for data- and relation sources, suppied by an application export class.
025: * Does not support or export dtd information.
026: * @author Daniel Ockeleon
027: * @author Jaco de Groot
028: * @author Pierre van Rooden
029: * @version $Id: NodeWriter.java,v 1.5 2007/04/05 14:04:18 pierre Exp $
030: */
031: public class NodeWriter {
032:
033: private static final Logger log = Logging
034: .getLoggerInstance(NodeWriter.class);
035:
036: private MMBase mmb;
037: private Logger logger;
038: private String directory;
039: private String builderName;
040: private boolean isRelationNode;
041: private File file;
042: private OutputStreamWriter fw;
043: private int nrOfNodes;
044:
045: /**
046: * Constructor, opens the initial xml file and writes a header.
047: * The file opened for writing is [directory]/[buildername].xml.
048: *
049: * @param mmb MMBase object for retrieving type information
050: * @param logger place to log results.
051: * @param directory the directory to write the files to (including the
052: * trailing slash).
053: * @param buildername name of the builder to export
054: * @param isRelationNode if <code>true</code>, the source to write is a relationsource.
055: * Otherwise, a datasource is written.
056: */
057: NodeWriter(MMBase mmb, Logger logger, String directory,
058: String builderName, boolean isRelationNode) {
059: // store parameters
060: this .mmb = mmb;
061: this .logger = logger;
062: this .directory = directory;
063: this .builderName = builderName;
064: this .isRelationNode = isRelationNode;
065: // define and open the file to write
066: file = new File(directory + builderName + ".xml");
067: try {
068: log.debug("Opening " + file + " for writing.");
069: fw = new OutputStreamWriter(new FileOutputStream(file),
070: "UTF-8");
071: } catch (Exception e) {
072: logger.error("Failed opening file " + file);
073: }
074: // Write the header
075: write("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n");
076: //write("<!DOCTYPE builder PUBLIC \"-//MMBase/data//EN\" \"http://www.mmbase.org/dtd/data.dtd\">\n");
077: Calendar cal = Calendar.getInstance();
078: long htimestamp = cal.get(Calendar.YEAR) * 10000
079: + (cal.get(Calendar.MONTH) + 1) * 100
080: + cal.get(Calendar.DAY_OF_MONTH);
081: long ltimestamp = cal.get(Calendar.AM_PM) * 120000
082: + cal.get(Calendar.HOUR) * 10000
083: + cal.get(Calendar.MINUTE) * 100
084: + cal.get(Calendar.SECOND);
085: long timestamp = (htimestamp * 1000000) + ltimestamp;
086:
087: write("<" + builderName
088: + " "
089: + "exportsource=\"mmbase://"
090: + // was: mmbase://127.0.0.1/install/b1
091: mmb.getHost() + "/"
092: + mmb.getStorageManagerFactory().getCatalog() + "/"
093: + mmb.getBaseName() + "\" " + "timestamp=\""
094: + timestamp + "\">\n"); // was : 20000602143030
095: // initialize the nr of nodes written
096: nrOfNodes = 0;
097: }
098:
099: /**
100: * Writes a node (object) to the datasource file.
101: * Relationsources are stored in a slightly different format from data sources.
102: * @param node The object to store.
103: */
104: public void write(MMObjectNode node) {
105: // retrieve basic information of the node
106: int number = node.getIntValue("number");
107: String owner = node.getStringValue("owner");
108: // start writing the node
109: if (isRelationNode) {
110: // For a relationnode, the fields snumber, dnumber and rnumber are stored as
111: // named references (as snumber, rnumber, and rtype).
112: // determine the relation 'type' (use the value of sname in RelDef, or the
113: // current buildername by default)
114: String rtype = builderName;
115: int rnumber = node.getIntValue("rnumber");
116: MMObjectNode reldefnode = mmb.getRelDef().getNode(rnumber);
117: if (reldefnode != null) {
118: rtype = reldefnode.getStringValue("sname");
119: }
120: write("\t<node number=\"" + number + "\" owner=\"" + owner
121: + "\" snumber=\"" + node.getIntValue("snumber")
122: + "\" dnumber=\"" + node.getIntValue("dnumber")
123: + "\" rtype=\"" + rtype + "\"");
124: // add directionality if used
125: if (InsRel.usesdir) {
126: int dir = node.getIntValue("dir");
127: if (dir == 1) {
128: write(" dir=\"unidirectional\"");
129: } else {
130: write(" dir=\"bidirectional\"");
131: }
132: }
133: write(">\n");
134: } else {
135: // For a data node, store the alias if at all possible.
136: String tm = null;
137: for (String alias : mmb.getOAlias().getAliasList(number)) {
138: if (tm == null) {
139: tm = alias;
140: } else {
141: tm += "," + alias;
142: }
143: }
144: if (tm == null) {
145: write("\t<node number=\"" + number + "\" owner=\""
146: + owner + "\">\n");
147: } else {
148: write("\t<node number=\"" + number + "\" owner=\""
149: + owner + "\" alias=\"" + tm + "\">\n");
150: }
151: }
152: MMObjectBuilder bul = node.getBuilder();
153: Iterator<CoreField> nd = bul.getFields().iterator();
154: while (nd.hasNext()) {
155: CoreField def = nd.next();
156: if (def.inStorage()) {
157: String key = def.getName();
158: if (isRelationNode) {
159: // note that the routine below assumes
160: // fields in a relation node cannot contain binary blobs
161: //
162: if (!key.equals("number") && !key.equals("owner")
163: && !key.equals("otype")
164: && !key.equals("snumber")
165: && !key.equals("dnumber")
166: && !key.equals("rnumber")
167: && !key.equals("dir") && !def.isTemporary()) {
168: write("\t\t<" + key + ">" + node.getValue(key)
169: + "</" + key + ">\n");
170: }
171: } else {
172: //due to current tcp implementation sometimes nodeField are created
173: //those fiels always start with an underscore. If a node starts with
174: //we skip it
175: if (!def.isTemporary()) {
176: write(writeXMLField(key, node, directory, mmb));
177: }
178: }
179: }
180: }
181: // end the node
182: write("\t</node>\n\n");
183: nrOfNodes++;
184: }
185:
186: /**
187: * Writes a footer to the xml file, and closes the file.
188: */
189: public void done() {
190: // write the footer
191: write("</" + builderName + ">\n");
192: logger.info("Saving " + nrOfNodes + " " + builderName
193: + " to : " + file);
194: try {
195: log.debug("Closing file " + file);
196: fw.close();
197: } catch (Exception e) {
198: logger.error("Failed closing file " + file);
199: }
200: }
201:
202: /**
203: * Writes a string datasource file.
204: * @param s The string to store.
205: */
206: private void write(String s) {
207: try {
208: fw.write(s);
209: } catch (Exception e) {
210: logger.error("Failed writing to file " + file);
211: }
212: }
213:
214: /**
215: * Creates a description string of a field in a node for use in a datasource file.
216: * Binary data (such as images) are stored as seperate binary files, the string then contains
217: * a reference in lieu of the actual value.
218: * A number of 'special purpose' fields (number, owner, otype) are skipped and not written.
219: * Other fields are added 'in line'.
220: * @param key the fieldname to store
221: * @param node The node wose field to store
222: * @param targetpath the path where any binary files may be stored
223: * @param mmb MMBase object for retrieving type info
224: * @return A <code>String</code> descriving in xml-format the field's content (or a reference to that content)
225: */
226: private static String writeXMLField(String key, MMObjectNode node,
227: String targetpath, MMBase mmb) {
228: if (!key.equals("number") && !key.equals("owner")
229: && !key.equals("otype")) {
230: // this is a bad way of doing it imho
231: int type = node.getDBType(key);
232: String stype = mmb.getTypeDef().getValue(
233: node.getIntValue("otype"));
234: if (type == Field.TYPE_BINARY) {
235: String body = "\t\t<" + key + " file=\"" + stype + "/"
236: + node.getIntValue("number") + "." + key
237: + "\" />\n";
238: File file = new File(targetpath + stype);
239: try {
240: file.mkdirs();
241: } catch (Exception e) {
242: log.error("Can't create dir : " + targetpath
243: + stype);
244: }
245: byte[] value = node.getByteValue(key);
246: saveFile(targetpath + stype + "/"
247: + node.getIntValue("number") + "." + key, value);
248: return body;
249: } else {
250: String body = "\t\t<"
251: + key
252: + ">"
253: + Encode.encode("ESCAPE_XML", ""
254: + Casting.toString(node.getValue(key)))
255: + "</" + key + ">\n";
256: return body;
257: }
258: }
259: return "";
260: }
261:
262: /**
263: * Stores binary data in a file
264: * @param filename path of the file to store the data
265: * @param value binary data to store (byte array)
266: * @return <code>true</code> if the write was succesful, <code>false</code> if an exception occurred
267: */
268: static boolean saveFile(String filename, byte[] value) {
269: File sfile = new File(filename);
270: try {
271: DataOutputStream scan = new DataOutputStream(
272: new FileOutputStream(sfile));
273: scan.write(value);
274: scan.flush();
275: scan.close();
276: } catch (Exception e) {
277: log.error(e.toString());
278: log.error(Logging.stackTrace(e));
279: return false;
280: }
281: return true;
282: }
283: }
|