001: /*******************************************************************************
002: * Licensed to the Apache Software Foundation (ASF) under one
003: * or more contributor license agreements. See the NOTICE file
004: * distributed with this work for additional information
005: * regarding copyright ownership. The ASF licenses this file
006: * to you under the Apache License, Version 2.0 (the
007: * "License"); you may not use this file except in compliance
008: * with the License. You may obtain a copy of the License at
009: *
010: * http://www.apache.org/licenses/LICENSE-2.0
011: *
012: * Unless required by applicable law or agreed to in writing,
013: * software distributed under the License is distributed on an
014: * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015: * KIND, either express or implied. See the License for the
016: * specific language governing permissions and limitations
017: * under the License.
018: *******************************************************************************/package org.ofbiz.entity.util;
019:
020: import java.io.ByteArrayInputStream;
021: import java.io.IOException;
022: import java.io.InputStream;
023: import java.io.InputStreamReader;
024: import java.io.Reader;
025: import java.io.StringWriter;
026: import java.net.URL;
027: import java.util.ArrayList;
028: import java.util.List;
029: import java.util.Map;
030:
031: import freemarker.ext.beans.BeansWrapper;
032: import freemarker.ext.dom.NodeModel;
033: import freemarker.template.Configuration;
034: import freemarker.template.Template;
035: import freemarker.template.TemplateException;
036: import freemarker.template.TemplateHashModel;
037: import javolution.lang.Text;
038: import javolution.util.FastMap;
039: import javolution.xml.sax.Attributes;
040: import javolution.xml.sax.RealtimeParser;
041: import org.w3c.dom.Document;
042: import org.w3c.dom.Element;
043: import org.w3c.dom.Node;
044: import org.xml.sax.ErrorHandler;
045: import org.xml.sax.SAXException;
046:
047: import org.ofbiz.base.util.Base64;
048: import org.ofbiz.base.util.Debug;
049: import org.ofbiz.base.util.UtilURL;
050: import org.ofbiz.base.util.UtilXml;
051: import org.ofbiz.entity.GenericDelegator;
052: import org.ofbiz.entity.GenericEntityException;
053: import org.ofbiz.entity.GenericValue;
054: import org.ofbiz.entity.eca.EntityEcaHandler;
055: import org.ofbiz.entity.model.ModelEntity;
056: import org.ofbiz.entity.model.ModelField;
057: import org.ofbiz.entity.transaction.GenericTransactionException;
058: import org.ofbiz.entity.transaction.TransactionUtil;
059:
060: /**
061: * SAX XML Parser Content Handler for Entity Engine XML files
062: */
063: public class EntitySaxReader implements
064: javolution.xml.sax.ContentHandler, ErrorHandler {
065:
066: public static final String module = EntitySaxReader.class.getName();
067: public static final int DEFAULT_TX_TIMEOUT = 7200;
068:
069: protected org.xml.sax.Locator locator;
070: protected GenericDelegator delegator;
071: protected EntityEcaHandler ecaHandler = null;
072: protected GenericValue currentValue = null;
073: protected CharSequence currentFieldName = null;
074: protected CharSequence currentFieldValue = null;
075: protected long numberRead = 0;
076:
077: protected int valuesPerWrite = 100;
078: protected int valuesPerMessage = 1000;
079: protected int transactionTimeout = 7200;
080: protected boolean useTryInsertMethod = false;
081: protected boolean maintainTxStamps = false;
082: protected boolean createDummyFks = false;
083: protected boolean doCacheClear = true;
084: protected boolean disableEeca = false;
085:
086: protected List valuesToWrite = new ArrayList(valuesPerWrite);
087:
088: protected boolean isParseForTemplate = false;
089: protected CharSequence templatePath = null;
090: protected Node rootNodeForTemplate = null;
091: protected Node currentNodeForTemplate = null;
092: protected Document documentForTemplate = null;
093:
094: protected EntitySaxReader() {
095: }
096:
097: public EntitySaxReader(GenericDelegator delegator,
098: int transactionTimeout) {
099: // clone the delegator right off so there is no chance of making change to the initial object
100: this .delegator = delegator.cloneDelegator();
101: this .transactionTimeout = transactionTimeout;
102: }
103:
104: public EntitySaxReader(GenericDelegator delegator) {
105: this (delegator, DEFAULT_TX_TIMEOUT);
106: }
107:
108: public int getValuesPerWrite() {
109: return this .valuesPerWrite;
110: }
111:
112: public void setValuesPerWrite(int valuesPerWrite) {
113: this .valuesPerWrite = valuesPerWrite;
114: }
115:
116: public int getValuesPerMessage() {
117: return this .valuesPerMessage;
118: }
119:
120: public void setValuesPerMessage(int valuesPerMessage) {
121: this .valuesPerMessage = valuesPerMessage;
122: }
123:
124: public int getTransactionTimeout() {
125: return this .transactionTimeout;
126: }
127:
128: public void setUseTryInsertMethod(boolean value) {
129: this .useTryInsertMethod = value;
130: }
131:
132: public void setTransactionTimeout(int transactionTimeout)
133: throws GenericTransactionException {
134: if (this .transactionTimeout != transactionTimeout) {
135: TransactionUtil.setTransactionTimeout(transactionTimeout);
136: this .transactionTimeout = transactionTimeout;
137: }
138: }
139:
140: public boolean getMaintainTxStamps() {
141: return this .maintainTxStamps;
142: }
143:
144: public void setMaintainTxStamps(boolean maintainTxStamps) {
145: this .maintainTxStamps = maintainTxStamps;
146: }
147:
148: public boolean getCreateDummyFks() {
149: return this .createDummyFks;
150: }
151:
152: public void setCreateDummyFks(boolean createDummyFks) {
153: this .createDummyFks = createDummyFks;
154: }
155:
156: public boolean getDoCacheClear() {
157: return this .doCacheClear;
158: }
159:
160: public void setDoCacheClear(boolean doCacheClear) {
161: this .doCacheClear = doCacheClear;
162: }
163:
164: public boolean getDisableEeca() {
165: return this .disableEeca;
166: }
167:
168: public void setDisableEeca(boolean disableEeca) {
169: this .disableEeca = disableEeca;
170: if (disableEeca) {
171: if (this .ecaHandler == null) {
172: this .ecaHandler = delegator.getEntityEcaHandler();
173: }
174: this .delegator.setEntityEcaHandler(null);
175: } else {
176: if (ecaHandler != null) {
177: this .delegator.setEntityEcaHandler(ecaHandler);
178: }
179: }
180: }
181:
182: public long parse(String content) throws SAXException,
183: java.io.IOException {
184: if (content == null) {
185: Debug.logWarning("content was null, doing nothing", module);
186: return 0;
187: }
188: ByteArrayInputStream bis = new ByteArrayInputStream(content
189: .getBytes());
190:
191: return this .parse(bis, "Internal Content");
192: }
193:
194: public long parse(URL location) throws SAXException,
195: java.io.IOException {
196: if (location == null) {
197: Debug.logWarning("location URL was null, doing nothing",
198: module);
199: return 0;
200: }
201: Debug.logImportant("Beginning import from URL: "
202: + location.toExternalForm(), module);
203: return this .parse(location.openStream(), location.toString());
204: }
205:
206: public long parse(InputStream is, String docDescription)
207: throws SAXException, java.io.IOException {
208:
209: /* NOTE: this method is not used because it doesn't work with various parsers...
210: String orgXmlSaxDriver = System.getProperty("org.xml.sax.driver");
211: if (UtilValidate.isEmpty(orgXmlSaxDriver)) orgXmlSaxDriver = "org.apache.xerces.parsers.SAXParser";
212: XMLReader reader = XMLReaderFactory.createXMLReader(orgXmlSaxDriver);
213: */
214:
215: /* This code is for a standard SAXParser and XMLReader like xerces or such; for speed we are using the Javolution reader
216: XMLReader reader = null;
217:
218: try {
219: SAXParserFactory parserFactory = SAXParserFactory.newInstance();
220: SAXParser parser = parserFactory.newSAXParser();
221:
222: reader = parser.getXMLReader();
223: } catch (javax.xml.parsers.ParserConfigurationException e) {
224: Debug.logError(e, "Failed to get a SAX XML parser", module);
225: throw new IllegalStateException("Failed to get a SAX XML parser");
226: }
227: */
228:
229: RealtimeParser parser = new RealtimeParser(16384);
230:
231: parser.setContentHandler(this );
232: parser.setErrorHandler(this );
233: // LocalResolver lr = new UtilXml.LocalResolver(new DefaultHandler());
234: // reader.setEntityResolver(lr);
235:
236: numberRead = 0;
237: try {
238: boolean beganTransaction = false;
239: if (transactionTimeout > -1) {
240: beganTransaction = TransactionUtil
241: .begin(transactionTimeout);
242: Debug.logImportant("Transaction Timeout set to "
243: + transactionTimeout / 3600 + " hours ("
244: + transactionTimeout + " seconds)", module);
245: }
246: try {
247: parser.parse(is);
248: // make sure all of the values to write got written...
249: if (valuesToWrite.size() > 0) {
250: writeValues(valuesToWrite);
251: valuesToWrite.clear();
252: }
253: TransactionUtil.commit(beganTransaction);
254: } catch (Exception e) {
255: String errMsg = "An error occurred saving the data, rolling back transaction ("
256: + beganTransaction + ")";
257: Debug.logError(e, errMsg, module);
258: TransactionUtil.rollback(beganTransaction, errMsg, e);
259: throw new SAXException(
260: "A transaction error occurred reading data", e);
261: }
262: } catch (GenericTransactionException e) {
263: throw new SAXException(
264: "A transaction error occurred reading data", e);
265: }
266: Debug.logImportant("Finished " + numberRead + " values from "
267: + docDescription, module);
268: return numberRead;
269: }
270:
271: protected void writeValues(List valuesToWrite)
272: throws GenericEntityException {
273: delegator.storeAll(valuesToWrite, doCacheClear, createDummyFks);
274: }
275:
276: public void characters(char[] values, int offset, int count)
277: throws org.xml.sax.SAXException {
278: if (isParseForTemplate) {
279: // if null, don't worry about it
280: if (this .currentNodeForTemplate != null) {
281: Node newNode = this .documentForTemplate
282: .createTextNode(new String(values, offset,
283: count));
284: this .currentNodeForTemplate.appendChild(newNode);
285: }
286: return;
287: }
288:
289: if (currentValue != null && currentFieldName != null) {
290: Text value = Text.valueOf(values, offset, count);
291:
292: // Debug.logInfo("characters: value=" + value, module);
293: if (currentFieldValue == null) {
294: currentFieldValue = value;
295: } else {
296: currentFieldValue = Text.valueOf(currentFieldValue)
297: .concat(value);
298: }
299: }
300: }
301:
302: public void endDocument() throws org.xml.sax.SAXException {
303: }
304:
305: public void endElement(CharSequence namespaceURI,
306: CharSequence localName, CharSequence fullName)
307: throws org.xml.sax.SAXException {
308: if (Debug.verboseOn())
309: Debug.logVerbose("endElement: localName=" + localName
310: + ", fullName=" + fullName + ", numberRead="
311: + numberRead, module);
312: String fullNameString = fullName.toString();
313: if ("entity-engine-xml".equals(fullNameString)) {
314: return;
315: }
316: if ("entity-engine-transform-xml".equals(fullNameString)) {
317: // transform file & parse it, then return
318: URL templateUrl = UtilURL.fromResource(templatePath
319: .toString());
320:
321: if (templateUrl == null) {
322: throw new SAXException(
323: "Could not find transform template with resource path: "
324: + templatePath);
325: } else {
326: try {
327: Reader templateReader = new InputStreamReader(
328: templateUrl.openStream());
329:
330: StringWriter outWriter = new StringWriter();
331: Configuration config = new Configuration();
332: config.setObjectWrapper(BeansWrapper
333: .getDefaultInstance());
334: config.setSetting("datetime_format",
335: "yyyy-MM-dd HH:mm:ss.SSS");
336:
337: Template template = new Template("FMImportFilter",
338: templateReader, config);
339: NodeModel nodeModel = NodeModel
340: .wrap(this .rootNodeForTemplate);
341:
342: Map context = FastMap.newInstance();
343: BeansWrapper wrapper = BeansWrapper
344: .getDefaultInstance();
345: TemplateHashModel staticModels = wrapper
346: .getStaticModels();
347: context.put("Static", staticModels);
348:
349: context.put("doc", nodeModel);
350: template.process(context, outWriter);
351: String s = outWriter.toString();
352: if (Debug.verboseOn())
353: Debug.logVerbose("transformed xml: " + s,
354: module);
355:
356: EntitySaxReader reader = new EntitySaxReader(
357: delegator);
358: reader
359: .setUseTryInsertMethod(this .useTryInsertMethod);
360: try {
361: reader
362: .setTransactionTimeout(this .transactionTimeout);
363: } catch (GenericTransactionException e1) {
364: // couldn't set tx timeout, shouldn't be a big deal
365: }
366:
367: numberRead += reader.parse(s);
368: } catch (TemplateException e) {
369: throw new SAXException("Error storing value", e);
370: } catch (IOException e) {
371: throw new SAXException("Error storing value", e);
372: }
373: }
374:
375: return;
376: }
377:
378: if (isParseForTemplate) {
379: this .currentNodeForTemplate = this .currentNodeForTemplate
380: .getParentNode();
381: return;
382: }
383:
384: if (currentValue != null) {
385: if (currentFieldName != null) {
386: if (currentFieldValue != null
387: && currentFieldValue.length() > 0) {
388: if (currentValue.getModelEntity().isField(
389: currentFieldName.toString())) {
390: ModelEntity modelEntity = currentValue
391: .getModelEntity();
392: ModelField modelField = modelEntity
393: .getField(currentFieldName.toString());
394: String type = modelField.getType();
395: if (type != null && type.equals("blob")) {
396: byte strData[] = new byte[currentFieldValue
397: .length()];
398: strData = currentFieldValue.toString()
399: .getBytes();
400: byte binData[] = new byte[currentFieldValue
401: .length()];
402: binData = Base64.base64Decode(strData);
403: currentValue.setBytes(currentFieldName
404: .toString(), binData);
405: } else {
406: currentValue.setString(currentFieldName
407: .toString(), currentFieldValue
408: .toString());
409: }
410: } else {
411: Debug.logWarning(
412: "Ignoring invalid field name ["
413: + currentFieldName
414: + "] found for the entity: "
415: + currentValue.getEntityName()
416: + " with value="
417: + currentFieldValue, module);
418: }
419: currentFieldValue = null;
420: }
421: currentFieldName = null;
422: } else {
423: // before we write currentValue check to see if PK is there, if not and it is one field, generate it from a sequence using the entity name
424: if (!currentValue.containsPrimaryKey()) {
425: if (currentValue.getModelEntity().getPksSize() == 1) {
426: ModelField modelField = currentValue
427: .getModelEntity().getOnlyPk();
428: String newSeq = delegator
429: .getNextSeqId(currentValue
430: .getEntityName());
431: currentValue.setString(modelField.getName(),
432: newSeq);
433: } else {
434: throw new SAXException(
435: "Cannot store value with incomplete primary key with more than 1 primary key field: "
436: + currentValue);
437: }
438: }
439:
440: try {
441: if (useTryInsertMethod) {
442: // this technique is faster for data sets where most, if not all, values do not already exist in the database
443: try {
444: currentValue.create();
445: } catch (GenericEntityException e1) {
446: // create failed, try a store, if that fails too we have a real error and the catch outside of this should handle it
447: currentValue.store();
448: }
449: } else {
450: valuesToWrite.add(currentValue);
451: if (valuesToWrite.size() >= valuesPerWrite) {
452: writeValues(valuesToWrite);
453: valuesToWrite.clear();
454: }
455: }
456: numberRead++;
457: if ((numberRead % valuesPerMessage) == 0) {
458: Debug.logImportant("Another "
459: + valuesPerMessage
460: + " values imported: now up to "
461: + numberRead, module);
462: }
463: currentValue = null;
464: } catch (GenericEntityException e) {
465: String errMsg = "Error storing value";
466: Debug.logError(e, errMsg, module);
467: throw new SAXException(errMsg, e);
468: }
469: }
470: }
471: }
472:
473: public void endPrefixMapping(CharSequence prefix)
474: throws org.xml.sax.SAXException {
475: }
476:
477: public void ignorableWhitespace(char[] values, int offset, int count)
478: throws org.xml.sax.SAXException {
479: // String value = new String(values, offset, count);
480: // Debug.logInfo("ignorableWhitespace: value=" + value, module);
481: }
482:
483: public void processingInstruction(CharSequence target,
484: CharSequence instruction) throws org.xml.sax.SAXException {
485: }
486:
487: public void setDocumentLocator(org.xml.sax.Locator locator) {
488: this .locator = locator;
489: }
490:
491: public void skippedEntity(CharSequence name)
492: throws org.xml.sax.SAXException {
493: }
494:
495: public void startDocument() throws org.xml.sax.SAXException {
496: }
497:
498: public void startElement(CharSequence namepsaceURI,
499: CharSequence localName, CharSequence fullName,
500: Attributes attributes) throws org.xml.sax.SAXException {
501: if (Debug.verboseOn())
502: Debug.logVerbose("startElement: localName=" + localName
503: + ", fullName=" + fullName + ", attributes="
504: + attributes, module);
505: String fullNameString = fullName.toString();
506: if ("entity-engine-xml".equals(fullNameString)) {
507: // check the maintain-timestamp flag
508: CharSequence maintainTx = attributes
509: .getValue("maintain-timestamps");
510: if (maintainTx != null) {
511: this .setMaintainTxStamps("true"
512: .equalsIgnoreCase(maintainTx.toString()));
513: }
514:
515: // check the do-cache-clear flag
516: CharSequence doCacheClear = attributes
517: .getValue("do-cache-clear");
518: if (doCacheClear != null) {
519: this .setDoCacheClear("true"
520: .equalsIgnoreCase(doCacheClear.toString()));
521: }
522:
523: // check the disable-eeca flag
524: CharSequence ecaDisable = attributes
525: .getValue("disable-eeca");
526: if (ecaDisable != null) {
527: this .setDisableEeca("true".equalsIgnoreCase(ecaDisable
528: .toString()));
529: }
530:
531: // check the use-dummy-fk flag
532: CharSequence dummyFk = attributes
533: .getValue("create-dummy-fk");
534: if (dummyFk != null) {
535: this .setCreateDummyFks("true".equalsIgnoreCase(dummyFk
536: .toString()));
537: }
538:
539: return;
540: }
541:
542: if ("entity-engine-transform-xml".equals(fullNameString)) {
543: templatePath = attributes.getValue("template");
544: isParseForTemplate = true;
545: documentForTemplate = UtilXml.makeEmptyXmlDocument();
546: return;
547: }
548:
549: if (isParseForTemplate) {
550: Element newElement = this .documentForTemplate
551: .createElement(fullNameString);
552: int length = attributes.getLength();
553: for (int i = 0; i < length; i++) {
554: CharSequence name = attributes.getLocalName(i);
555: CharSequence value = attributes.getValue(i);
556:
557: if (name == null || name.length() == 0) {
558: name = attributes.getQName(i);
559: }
560: newElement.setAttribute(name.toString(), value
561: .toString());
562: }
563:
564: if (this .currentNodeForTemplate == null) {
565: this .currentNodeForTemplate = newElement;
566: this .rootNodeForTemplate = newElement;
567: } else {
568: this .currentNodeForTemplate.appendChild(newElement);
569: this .currentNodeForTemplate = newElement;
570: }
571: return;
572: }
573:
574: if (currentValue != null) {
575: // we have a nested value/CDATA element
576: currentFieldName = fullName;
577: } else {
578: String entityName = fullNameString;
579:
580: // if a dash or colon is in the tag name, grab what is after it
581: if (entityName.indexOf('-') > 0) {
582: entityName = entityName.substring(entityName
583: .indexOf('-') + 1);
584: }
585: if (entityName.indexOf(':') > 0) {
586: entityName = entityName.substring(entityName
587: .indexOf(':') + 1);
588: }
589:
590: try {
591: currentValue = delegator.makeValue(entityName, null);
592: // TODO: do we really want this? it makes it so none of the values imported have create/update timestamps set
593: // DEJ 10/16/04 I think they should all be stamped, so commenting this out
594: // JAZ 12/10/04 I think it should be specified when creating the reader
595: if (this .maintainTxStamps) {
596: currentValue.setIsFromEntitySync(true);
597: }
598: } catch (Exception e) {
599: Debug.logError(e, module);
600: }
601:
602: if (currentValue != null) {
603: int length = attributes.getLength();
604:
605: for (int i = 0; i < length; i++) {
606: CharSequence name = attributes.getLocalName(i);
607: CharSequence value = attributes.getValue(i);
608:
609: if (name == null || name.length() == 0) {
610: name = attributes.getQName(i);
611: }
612: try {
613: // treat empty strings as nulls
614: if (value != null && value.length() > 0) {
615: if (currentValue.getModelEntity().isField(
616: name.toString())) {
617: currentValue.setString(name.toString(),
618: value.toString());
619: } else {
620: Debug
621: .logWarning(
622: "Ignoring invalid field name ["
623: + name
624: + "] found for the entity: "
625: + currentValue
626: .getEntityName()
627: + " with value="
628: + value, module);
629: }
630: }
631: } catch (Exception e) {
632: Debug.logWarning(e, "Could not set field "
633: + entityName + "." + name
634: + " to the value " + value, module);
635: }
636: }
637: }
638: }
639: }
640:
641: //public void startPrefixMapping(String prefix, String uri) throws org.xml.sax.SAXException {}
642: public void startPrefixMapping(CharSequence arg0, CharSequence arg1)
643: throws SAXException {
644: }
645:
646: // ======== ErrorHandler interface implementations ========
647:
648: public void error(org.xml.sax.SAXParseException exception)
649: throws org.xml.sax.SAXException {
650: Debug.logWarning(exception, "Error reading XML on line "
651: + exception.getLineNumber() + ", column "
652: + exception.getColumnNumber(), module);
653: }
654:
655: public void fatalError(org.xml.sax.SAXParseException exception)
656: throws org.xml.sax.SAXException {
657: Debug.logError(exception, "Fatal Error reading XML on line "
658: + exception.getLineNumber() + ", column "
659: + exception.getColumnNumber(), module);
660: throw new SAXException("Fatal Error reading XML on line "
661: + exception.getLineNumber() + ", column "
662: + exception.getColumnNumber(), exception);
663: }
664:
665: public void warning(org.xml.sax.SAXParseException exception)
666: throws org.xml.sax.SAXException {
667: Debug.logWarning(exception, "Warning reading XML on line "
668: + exception.getLineNumber() + ", column "
669: + exception.getColumnNumber(), module);
670: }
671: }
|