001: package jfb.tools.activitymgr.core.util;
002:
003: import java.io.IOException;
004: import java.io.OutputStream;
005:
006: import jfb.tools.activitymgr.core.DbException;
007: import jfb.tools.activitymgr.core.DbTransaction;
008: import jfb.tools.activitymgr.core.ModelException;
009: import jfb.tools.activitymgr.core.ModelMgr;
010: import jfb.tools.activitymgr.core.beans.Collaborator;
011: import jfb.tools.activitymgr.core.beans.Contribution;
012: import jfb.tools.activitymgr.core.beans.Duration;
013: import jfb.tools.activitymgr.core.beans.Task;
014:
015: import org.apache.log4j.Logger;
016: import org.xml.sax.Attributes;
017: import org.xml.sax.ContentHandler;
018: import org.xml.sax.EntityResolver;
019: import org.xml.sax.ErrorHandler;
020: import org.xml.sax.InputSource;
021: import org.xml.sax.Locator;
022: import org.xml.sax.SAXException;
023: import org.xml.sax.SAXParseException;
024:
025: /**
026: * Classe offrant des services de manipulation ou de génération
027: * de documents XML.
028: */
029: public class XmlHelper implements EntityResolver, ErrorHandler,
030: ContentHandler {
031:
032: /**
033: * Interface de création des objets en base de données.
034: */
035: public static interface ModelMgrDelegate {
036:
037: /**
038: * Crée une durée dans un contexte de transaction.
039: * @param tx le contexte de transaction.
040: * @param duration la durée à créer.
041: * @return la durée créée.
042: * @throws DbException levé en cas d'incident technique d'accès à la base.
043: * @throws ModelException levé dans la cas ou la durée existe déjà.
044: * @see jfb.tools.activitymgr.core.ModelMgr#createDuration(Duration)
045: */
046: public Duration createDuration(DbTransaction tx,
047: Duration duration) throws ModelException, DbException;
048:
049: /**
050: * Crée un collaborateur dans un contexte de transaction.
051: * @param tx le contexte de transaction.
052: * @param collaborator le collaborateur à créer.
053: * @return le collaborateur après création.
054: * @throws DbException levé en cas d'incident technique d'accès à la base.
055: * @throws ModelException levé dans la cas ou la tache de destination ne peut recevoir de sous-tache.
056: * @see jfb.tools.activitymgr.core.ModelMgr#createCollaborator(Collaborator)
057: */
058: public Collaborator createCollaborator(DbTransaction tx,
059: Collaborator collaborator) throws DbException,
060: ModelException;
061:
062: /**
063: * Crée une nouvelle tache dans un contexte de transaction.
064: * @param tx le contexte de transaction.
065: * @param parentTask la tache parent de destination.
066: * @param task la tache à créer.
067: * @return la tache crée.
068: * @throws DbException levé en cas d'incident technique d'accès à la base.
069: * @throws ModelException levé dans la cas ou la tache de destination ne peut recevoir de sous-tache.
070: * @see jfb.tools.activitymgr.core.ModelMgr#createTask(Task, Task)
071: */
072: public Task createTask(DbTransaction tx, Task parentTask,
073: Task task) throws DbException, ModelException;
074:
075: /**
076: * Crée une contribution dans un contexte de transaction.
077: * @param tx le contexte de transaction.
078: * @param contribution la contribution à créer.
079: * @return la contribution après création.
080: * @throws DbException levé en cas d'incident technique d'accès à la base.
081: * @throws ModelException levé dans la cas ou la tache de destination ne peut recevoir de contribution.
082: * @see jfb.tools.activitymgr.core.ModelMgr#createCollaborator(Collaborator)
083: */
084: public Contribution createContribution(DbTransaction tx,
085: Contribution contribution) throws DbException,
086: ModelException;
087:
088: /**
089: * Retourne la tache associée à un chemin construit à partir de
090: * codes de taches.
091: * @param tx le contexte de transaction.
092: * @param codePath le chemin à base de code.
093: * @return la tache trouvée.
094: * @throws DbException levé en cas d'incident technique avec la base de données.
095: * @throws ModelException levé dans le cas ou le chemin de tache est inconnu.
096: */
097: public Task getTaskByCodePath(DbTransaction tx,
098: final String codePath) throws DbException,
099: ModelException;
100:
101: /**
102: * Retourne le collabirateur dont le login est spécifié dans un contexte
103: * de transaction.
104: * @param tx le contexte de transaction.
105: * @param login l'identifiant de connexion du collaborateur recherché.
106: * @return le collaborateur dont l'identifiant de connexion est spécifié.
107: * @throws DbException levé en cas d'incident technique d'accès à la base.
108: */
109: public Collaborator getCollaborator(DbTransaction tx,
110: String login) throws DbException;
111:
112: }
113:
114: /** Logger */
115: private static Logger log = Logger.getLogger(XmlHelper.class);
116:
117: /** Constantes */
118: public static final String MODEL_NODE = "model";
119: public static final String DURATIONS_NODE = "durations";
120: public static final String DURATION_NODE = "duration";
121: public static final String COLLABORATORS_NODE = "collaborators";
122: public static final String COLLABORATOR_NODE = "collaborator";
123: public static final String TASKS_NODE = "tasks";
124: public static final String TASK_NODE = "task";
125: public static final String CONTRIBUTIONS_NODE = "contributions";
126: public static final String CONTRIBUTION_NODE = "contribution";
127: public static final String LOGIN_NODE = "login";
128: public static final String FIRST_NAME_NODE = "first-name";
129: public static final String LAST_NAME_NODE = "last-name";
130: public static final String IS_ACTIVE_NODE = "is-active";
131: public static final String YEAR_ATTRIBUTE = "year";
132: public static final String MONTH_ATTRIBUTE = "month";
133: public static final String DAY_ATTRIBUTE = "day";
134: public static final String DURATION_ATTRIBUTE = "duration";
135: public static final String CONTRIBUTOR_REF_NODE = "contributor-ref";
136: public static final String TASK_REF_NODE = "task-ref";
137: public static final String PATH_NODE = "path";
138: public static final String NAME_NODE = "name";
139: public static final String BUDGET_NODE = "budget";
140: public static final String INITIALLY_CONSUMED_NODE = "initially-consumed";
141: public static final String TODO_NODE = "todo";
142: public static final String COMMENT_NODE = "comment";
143: public static final String VALUE_NODE = "value";
144:
145: /** Contexte de transaction */
146: private DbTransaction transaction;
147:
148: /** Gestionnaire du modèle */
149: private ModelMgrDelegate modelMgrDelegate;
150:
151: /** Sauvegarde des objets en cours de chargement */
152: private Duration currentDuration;
153: private Collaborator currentCollaborator;
154: private Task currentParentTask;
155: private Task currentTask;
156: private Contribution currentContribution;
157: private StringBuffer currentText = new StringBuffer();
158:
159: /** Pointeur de location dans le fichier XML parsé */
160: private Locator locator;
161:
162: /**
163: * Constructeur par défaut.
164: * @param modelMgrDelegate délégué du gestionnaire de modèle.
165: * @param tx contexte de transaction.
166: */
167: public XmlHelper(ModelMgrDelegate modelMgrDelegate, DbTransaction tx) {
168: this .modelMgrDelegate = modelMgrDelegate;
169: this .transaction = tx;
170: }
171:
172: /** EntityResolver interface methods */
173:
174: /* (non-Javadoc)
175: * @see org.xml.sax.EntityResolver#resolveEntity(java.lang.String, java.lang.String)
176: */
177: public InputSource resolveEntity(String publicId, String systemId)
178: throws SAXException, IOException {
179: log.debug("resolveEntity(" + publicId + ", " + systemId + ")");
180: return new InputSource(ModelMgr.class
181: .getResourceAsStream("activitymgr.dtd"));
182: }
183:
184: /** ErrorHandler interface methods */
185:
186: /* (non-Javadoc)
187: * @see org.xml.sax.ErrorHandler#error(org.xml.sax.SAXParseException)
188: */
189: public void error(SAXParseException e) throws SAXParseException {
190: log.error("SAX error line : " + e.getLineNumber()
191: + " column : " + e.getColumnNumber(), e);
192: throw e;
193: }
194:
195: /* (non-Javadoc)
196: * @see org.xml.sax.ErrorHandler#fatalError(org.xml.sax.SAXParseException)
197: */
198: public void fatalError(SAXParseException e)
199: throws SAXParseException {
200: error(e);
201: }
202:
203: /* (non-Javadoc)
204: * @see org.xml.sax.ErrorHandler#warning(org.xml.sax.SAXParseException)
205: */
206: public void warning(SAXParseException e) throws SAXParseException {
207: error(e);
208: }
209:
210: /** ContentHandlet interface methods */
211:
212: /* (non-Javadoc)
213: * @see org.xml.sax.ContentHandler#startElement(java.lang.String, java.lang.String, java.lang.String, org.xml.sax.Attributes)
214: */
215: public void startElement(String namespaceURI, String localName,
216: String qName, Attributes atts) throws SAXException {
217: log.debug("start(" + qName + ")");
218: if (MODEL_NODE.equals(qName) || DURATIONS_NODE.equals(qName)
219: || VALUE_NODE.equals(qName)
220: || COLLABORATORS_NODE.equals(qName)
221: || LOGIN_NODE.equals(qName)
222: || FIRST_NAME_NODE.equals(qName)
223: || LAST_NAME_NODE.equals(qName)
224: || TASKS_NODE.equals(qName) || PATH_NODE.equals(qName)
225: || NAME_NODE.equals(qName)
226: || IS_ACTIVE_NODE.equals(qName)
227: || BUDGET_NODE.equals(qName)
228: || INITIALLY_CONSUMED_NODE.equals(qName)
229: || TODO_NODE.equals(qName)
230: || COMMENT_NODE.equals(qName)
231: || CONTRIBUTIONS_NODE.equals(qName)
232: || CONTRIBUTOR_REF_NODE.equals(qName)
233: || TASK_REF_NODE.equals(qName)) {
234: // Do nothing...
235: } else if (DURATION_NODE.equals(qName)) {
236: currentDuration = new Duration();
237: } else if (COLLABORATOR_NODE.equals(qName)) {
238: currentCollaborator = new Collaborator();
239: } else if (TASK_NODE.equals(qName)) {
240: currentTask = new Task();
241: } else if (CONTRIBUTION_NODE.equals(qName)) {
242: currentContribution = new Contribution();
243: currentContribution.setYear((int) getNumAttrValue(atts,
244: YEAR_ATTRIBUTE));
245: currentContribution.setMonth((int) getNumAttrValue(atts,
246: MONTH_ATTRIBUTE));
247: currentContribution.setDay((int) getNumAttrValue(atts,
248: DAY_ATTRIBUTE));
249: currentContribution.setDurationId(getNumAttrValue(atts,
250: DURATION_ATTRIBUTE));
251: } else {
252: error(new SAXParseException("Unexpected node '" + qName
253: + "'", locator));
254: }
255: }
256:
257: /**
258: * Retourne la valeur d'un attribut numérique ou lève un exception si
259: * l'attribut n'existe pas ou qu'il n'a pas un format correct.
260: * @param atts la liste des attributs.
261: * @param qName le nom de l'attribut.
262: * @return la valeur de l'attribut.
263: * @throws SAXParseException levé en cas d'absence ou de mauvais format de l'attribut.
264: */
265: private long getNumAttrValue(Attributes atts, String qName)
266: throws SAXParseException {
267: String value = atts.getValue(qName);
268: if (value == null)
269: error(new SAXParseException("Missing attribute value for '"
270: + qName + "'", locator));
271: // Parsing
272: long result = -1;
273: try {
274: result = Long.parseLong(value);
275: } catch (NumberFormatException e) {
276: error(new SAXParseException("Invalid attribute value '"
277: + value + "' for '" + qName + "'", locator));
278: }
279: // Retour du résultat
280: return result;
281: }
282:
283: /* (non-Javadoc)
284: * @see org.xml.sax.ContentHandler#endElement(java.lang.String, java.lang.String, java.lang.String)
285: */
286: public void endElement(String namespaceURI, String localName,
287: String qName) throws SAXException {
288: log.debug("end(" + qName + ")");
289: try {
290: String textToSave = currentText.toString();
291: // Réinitialisation de la sauvegarde du texte courant
292: currentText.setLength(0);
293: if (MODEL_NODE.equals(qName)
294: || DURATIONS_NODE.equals(qName)
295: || COLLABORATORS_NODE.equals(qName)
296: || TASKS_NODE.equals(qName)
297: || CONTRIBUTIONS_NODE.equals(qName)) {
298: // Do nothing...
299: } else if (DURATION_NODE.equals(qName)) {
300: Duration durationToCreate = currentDuration;
301: currentDuration = null;
302: modelMgrDelegate.createDuration(transaction,
303: durationToCreate);
304: } else if (COLLABORATOR_NODE.equals(qName)) {
305: Collaborator collaboratorToCreate = currentCollaborator;
306: currentCollaborator = null;
307: modelMgrDelegate.createCollaborator(transaction,
308: collaboratorToCreate);
309: } else if (LOGIN_NODE.equals(qName)) {
310: currentCollaborator.setLogin(textToSave);
311: } else if (FIRST_NAME_NODE.equals(qName)) {
312: currentCollaborator.setFirstName(textToSave);
313: } else if (LAST_NAME_NODE.equals(qName)) {
314: currentCollaborator.setLastName(textToSave);
315: } else if (IS_ACTIVE_NODE.equals(qName)) {
316: boolean isActive = "true".equalsIgnoreCase(textToSave);
317: if (currentCollaborator != null)
318: currentCollaborator.setIsActive(isActive);
319: else
320: currentDuration.setIsActive(isActive);
321: } else if (VALUE_NODE.equals(qName)) {
322: currentDuration.setId(Long.parseLong(textToSave));
323: } else if (TASK_NODE.equals(qName)) {
324: Task taskToCreate = currentTask;
325: Task parentOfTaskToCreate = currentParentTask;
326: currentTask = null;
327: currentParentTask = null;
328: modelMgrDelegate.createTask(transaction,
329: parentOfTaskToCreate, taskToCreate);
330: } else if (PATH_NODE.equals(qName)) {
331: log.debug("textToSave='" + textToSave + "'");
332: String parentPath = textToSave.substring(0, textToSave
333: .lastIndexOf('/'));
334: log.debug("parentPath='" + parentPath + "'");
335: currentParentTask = "".equals(parentPath) ? null
336: : modelMgrDelegate.getTaskByCodePath(
337: transaction, parentPath);
338: String taskCode = textToSave.substring(parentPath
339: .length() + 1);
340: currentTask.setCode(taskCode);
341: } else if (NAME_NODE.equals(qName)) {
342: currentTask.setName(textToSave);
343: } else if (BUDGET_NODE.equals(qName)) {
344: currentTask.setBudget(Long.parseLong(textToSave));
345: } else if (INITIALLY_CONSUMED_NODE.equals(qName)) {
346: currentTask.setInitiallyConsumed(Long
347: .parseLong(textToSave));
348: } else if (TODO_NODE.equals(qName)) {
349: currentTask.setTodo(Long.parseLong(textToSave));
350: } else if (COMMENT_NODE.equals(qName)) {
351: String comment = textToSave != null ? textToSave.trim()
352: : "";
353: if ("".equals(comment))
354: comment = null;
355: currentTask.setComment(comment);
356: } else if (CONTRIBUTION_NODE.equals(qName)) {
357: Contribution contributionToCreate = currentContribution;
358: currentContribution = null;
359: modelMgrDelegate.createContribution(transaction,
360: contributionToCreate);
361: } else if (CONTRIBUTOR_REF_NODE.equals(qName)) {
362: Collaborator collaborator = modelMgrDelegate
363: .getCollaborator(transaction, textToSave);
364: currentContribution.setContributorId(collaborator
365: .getId());
366: } else if (TASK_REF_NODE.equals(qName)) {
367: Task task = modelMgrDelegate.getTaskByCodePath(
368: transaction, textToSave);
369: currentContribution.setTaskId(task.getId());
370: } else {
371: error(new SAXParseException("Unexpected node '" + qName
372: + "'", locator));
373: }
374: } catch (NumberFormatException e) {
375: log.error("Number format error", e);
376: error(new SAXParseException("Number format error : "
377: + e.getMessage(), locator, e));
378: } catch (ModelException e) {
379: log.error("Model violation", e);
380: error(new SAXParseException("Model violation : "
381: + e.getMessage(), locator, e));
382: } catch (DbException e) {
383: log.error("Unexpected database access error", e);
384: throw new SAXException("Unexpected database access error",
385: e);
386: }
387: }
388:
389: /* (non-Javadoc)
390: * @see org.xml.sax.ContentHandler#endDocument()
391: */
392: public void endDocument() throws SAXException {
393: log.debug("endDocument()");
394: }
395:
396: /* (non-Javadoc)
397: * @see org.xml.sax.ContentHandler#startDocument()
398: */
399: public void startDocument() throws SAXException {
400: log.debug("startDocument()");
401: }
402:
403: /* (non-Javadoc)
404: * @see org.xml.sax.ContentHandler#characters(char[], int, int)
405: */
406: public void characters(char[] ch, int start, int length)
407: throws SAXException {
408: String chars = new String(ch, start, length);
409: log.debug("characters(" + chars + ")");
410: currentText.append(chars);
411: }
412:
413: /* (non-Javadoc)
414: * @see org.xml.sax.ContentHandler#ignorableWhitespace(char[], int, int)
415: */
416: public void ignorableWhitespace(char[] ch, int start, int length)
417: throws SAXException {
418: log.debug("ignorableWhitespace("
419: + new String(ch, start, length) + ")");
420: }
421:
422: /* (non-Javadoc)
423: * @see org.xml.sax.ContentHandler#endPrefixMapping(java.lang.String)
424: */
425: public void endPrefixMapping(String prefix) throws SAXException {
426: saxFeatureNotImplemented("endPrefixMapping");
427: }
428:
429: /* (non-Javadoc)
430: * @see org.xml.sax.ContentHandler#skippedEntity(java.lang.String)
431: */
432: public void skippedEntity(String name) throws SAXException {
433: saxFeatureNotImplemented("skippedEntity");
434: }
435:
436: /* (non-Javadoc)
437: * @see org.xml.sax.ContentHandler#setDocumentLocator(org.xml.sax.Locator)
438: */
439: public void setDocumentLocator(Locator locator) {
440: log.debug("setDocumentLocator(" + locator + ")");
441: this .locator = locator;
442: }
443:
444: /* (non-Javadoc)
445: * @see org.xml.sax.ContentHandler#processingInstruction(java.lang.String, java.lang.String)
446: */
447: public void processingInstruction(String target, String data)
448: throws SAXException {
449: saxFeatureNotImplemented("processingInstruction");
450: }
451:
452: /* (non-Javadoc)
453: * @see org.xml.sax.ContentHandler#startPrefixMapping(java.lang.String, java.lang.String)
454: */
455: public void startPrefixMapping(String prefix, String uri)
456: throws SAXException {
457: saxFeatureNotImplemented("startPrefixMapping");
458: }
459:
460: /**
461: * Lève une exception indiquant qu'une fontionnalité n'est pas implémentée pour ActivityManager
462: * @param featureName nom de la fonctionnalité
463: * @throws SAXException levée dans tous les cas.
464: */
465: private void saxFeatureNotImplemented(String featureName)
466: throws SAXException {
467: error(new SAXParseException(
468: "SAX feature not implemented for ActivityManager ('"
469: + featureName + "')", locator));
470: }
471:
472: /** Static XML serialization methods */
473:
474: /**
475: * Commence un noeud XML dans le flux d'écriture.
476: * @param indent l'indentation.
477: * @param out le flux d'écriture.
478: * @param name le nom du noeud XML.
479: * @throws IOException levé en cas d'incident I/O lors de l'écriture sur le flux de sortie.
480: */
481: public static void startXmlNode(OutputStream out, String indent,
482: String name) throws IOException {
483: print(out, indent);
484: out.write('<');
485: print(out, name);
486: out.write('>');
487: out.write('\n');
488: }
489:
490: /**
491: * Termine un noeud XML dans le flux d'écriture avec une indentation.
492: * @param out le flux d'écriture.
493: * @param indent l'indentation.
494: * @param name le nom du noeud XML.
495: * @throws IOException levé en cas d'incident I/O lors de l'écriture sur le flux de sortie.
496: */
497: public static void endXmlNode(OutputStream out, String indent,
498: String name) throws IOException {
499: print(out, indent);
500: endXmlNode(out, name);
501: }
502:
503: /**
504: * Termine un noeud XML dans le flux d'écriture.
505: * @param out le flux d'écriture.
506: * @param name le nom du noeud XML.
507: * @throws IOException levé en cas d'incident I/O lors de l'écriture sur le flux de sortie.
508: */
509: public static void endXmlNode(OutputStream out, String name)
510: throws IOException {
511: out.write('<');
512: out.write('/');
513: print(out, name);
514: out.write('>');
515: out.write('\n');
516: }
517:
518: /**
519: * Ecrit un noeud XML dans le flux d'écriture.
520: * @param indent l'indentation.
521: * @param out le flux d'écriture.
522: * @param name le nom du noeud XML.
523: * @param value la valeur du noeud XML.
524: * @throws IOException levé en cas d'incident I/O lors de l'écriture sur le flux de sortie.
525: */
526: public static void printTextNode(OutputStream out, String indent,
527: String name, String value) throws IOException {
528: print(out, indent);
529: out.write('<');
530: print(out, name);
531: out.write('>');
532: printTextValue(out, value);
533: endXmlNode(out, name);
534: }
535:
536: /**
537: * Ecrit un attribut de noeud XML dans le flux d'écriture.
538: * @param out le flux d'écriture.
539: * @param name le nom de l'attribut XML.
540: * @param value la valeur de l'attribut XML.
541: * @throws IOException levé en cas d'incident I/O lors de l'écriture sur le flux de sortie.
542: */
543: public static void printTextAttribute(OutputStream out,
544: String name, String value) throws IOException {
545: out.write(' ');
546: print(out, name);
547: print(out, "=\"");
548: printTextValue(out, value);
549: print(out, "\"");
550: }
551:
552: /**
553: * Ecrit une chaîne de caractères dans le flux de sortie en remplaçant les
554: * caractères spéciaux.
555: * @param out le flux de sortie.
556: * @param str la chaîne de caractères.
557: * @throws IOException levé en cas d'incident lors de l'écriture des données sur le flux.
558: */
559: public static void printTextValue(OutputStream out, String str)
560: throws IOException {
561: str = str.replaceAll("&", "&");
562: str = str.replaceAll(">", ">");
563: str = str.replaceAll("<", "<");
564: print(out, str);
565: }
566:
567: /**
568: * Ecrit une chaîne de caractères dans le flux de sortie.
569: * @param out le flux de sortie.
570: * @param str la chaîne de caractères.
571: * @throws IOException levé en cas d'incident lors de l'écriture des données sur le flux.
572: */
573: public static void print(OutputStream out, String str)
574: throws IOException {
575: out.write(str.getBytes("UTF-8"));
576: }
577:
578: /**
579: * Ecrit une chaîne de caractères dans le flux de sortie.
580: * @param out le flux de sortie.
581: * @param s la chaîne de caractères.
582: * @throws IOException levé en cas d'incident lors de l'écriture des données sur le flux.
583: */
584: public static void println(OutputStream out, String s)
585: throws IOException {
586: print(out, s);
587: out.write('\n');
588: }
589:
590: }
|