View Javadoc
1   /*
2    * This software was designed and created by Jason Carroll.
3    * Copyright (c) 2002, 2003, 2004 Jason Carroll.
4    * The author can be reached at jcarroll@cowsultants.com
5    * ITracker website: http://www.cowsultants.com
6    * ITracker forums: http://www.cowsultants.com/phpBB/index.php
7    *
8    * This program is free software; you can redistribute it and/or modify
9    * it only under the terms of the GNU General Public License as published by
10   * the Free Software Foundation; either version 2 of the License, or
11   * (at your option) any later version.
12   *
13   * This program is distributed in the hope that it will be useful,
14   * but WITHOUT ANY WARRANTY; without even the implied warranty of
15   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16   * GNU General Public License for more details.
17   */
18  
19  package org.itracker.web.util;
20  
21  import org.apache.commons.lang.StringUtils;
22  import org.dom4j.DocumentFactory;
23  import org.dom4j.Element;
24  import org.dom4j.io.OutputFormat;
25  import org.dom4j.io.XMLWriter;
26  import org.itracker.ImportExportException;
27  import org.itracker.core.resources.ITrackerResources;
28  import org.itracker.model.*;
29  import org.itracker.model.util.CustomFieldUtilities;
30  import org.itracker.model.util.ProjectUtilities;
31  import org.slf4j.Logger;
32  import org.slf4j.LoggerFactory;
33  import org.xml.sax.InputSource;
34  import org.xml.sax.SAXException;
35  import org.xml.sax.XMLReader;
36  import org.xml.sax.helpers.XMLReaderFactory;
37  
38  import javax.servlet.ServletException;
39  import javax.servlet.http.HttpServletRequest;
40  import javax.servlet.http.HttpServletResponse;
41  import javax.xml.bind.JAXBContext;
42  import javax.xml.bind.Marshaller;
43  import javax.xml.bind.Unmarshaller;
44  import java.io.*;
45  import java.util.HashMap;
46  import java.util.Iterator;
47  import java.util.List;
48  import java.util.Properties;
49  
50  
51  /**
52   * FIXME: This is not XML, this is string concatenating/parsing. Use proper SAX Handler or remove this unsave code. see java.xml.parsers for more information.
53   * <p/>
54   * This class provides functionality needed to import and export issues and their associated
55   * data as XML.  This xml provides all the data necessary to import the issues into another
56   * instance of ITracker or some other issue tracking tool.
57   */
58  public class ImportExportUtilities implements ImportExportTags {
59  
60      private static final Logger logger = LoggerFactory.getLogger(ImportExportUtilities.class);
61      public static final int IMPORT_STAT_NEW = 0;
62      public static final int IMPORT_STAT_REUSED = 1;
63  
64      public static final int IMPORT_STAT_USERS = 0;
65      public static final int IMPORT_STAT_PROJECTS = 1;
66      public static final int IMPORT_STAT_ISSUES = 2;
67      public static final int IMPORT_STAT_STATUSES = 3;
68      public static final int IMPORT_STAT_SEVERITIES = 4;
69      public static final int IMPORT_STAT_RESOLUTIONS = 5;
70      public static final int IMPORT_STAT_FIELDS = 6;
71  
72      public ImportExportUtilities() {
73      }
74  
75  
76      private static DocumentFactory getDocumentFactory() {
77          return DocumentFactory.getInstance();
78      }
79      /**
80       * Takes an XML file matching the ITracker import/export DTD and returns an array
81       * of AbstractBean objects.  The array will contain all of the projects, components
82       * versions, users, custom fields, and issues contained in the XML.
83       *
84       * @param xmlReader an xml reader to import
85       * @throws ImportExportException thrown if the xml can not be parsed into the appropriate objects
86       */
87      public static AbstractEntity[] importIssues(Reader xmlReader) throws ImportExportException {
88          AbstractEntity[] abstractBeans;
89  
90          try {
91              logger.debug("Starting XML data import.");
92  
93              XMLReader reader = XMLReaderFactory.createXMLReader();
94              ImportHandlerhtml#ImportHandler">ImportHandler handler = new ImportHandler();
95              reader.setContentHandler(handler);
96              reader.setErrorHandler(handler);
97              reader.parse(new InputSource(xmlReader));
98              abstractBeans = handler.getModels();
99  
100             logger.debug("Imported a total of " + abstractBeans.length + " beans.");
101         } catch (Exception e) {
102             logger.error("Exception.", e);
103             throw new ImportExportException(e.getMessage());
104         }
105 
106         return abstractBeans;
107     }
108 
109 
110     /**
111      * export the issues to an XML and write it to the response.
112      * @param issues
113      * @param config
114      * @param request
115      * @param response
116      * @return  if <code>true</code> the export was sucessful.
117      * @throws ServletException
118      * @throws IOException
119      */
120     public static boolean exportIssues(List<Issue> issues, SystemConfiguration config, HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
121 
122 
123         response.setContentType("text/xml; charset=UTF-8");
124         response.setHeader("Content-Disposition", "attachment; filename=\"issue_export.xml\"");
125 
126         XMLWriter writer = new XMLWriter(response.getOutputStream(), OutputFormat.createCompactFormat());
127 
128         try {
129             // TODO instead to have a string returned, it should directly serialize the
130             // export to the response-writer.
131             ImportExportUtilities.exportIssues(writer, issues, config);
132 
133 
134         } catch (ImportExportException iee) {
135             logger.error("Error exporting issue data. Message: " + iee.getMessage(), iee);
136             return false;
137         } finally {
138             if (null != writer) {
139                 writer.flush();
140                 writer.close();
141             }
142         }
143 
144         return true;
145     }
146 
147     public static AbstractEntity importXml(InputSource is) throws Exception {
148         // TODO unmarshal from is
149         JAXBContext jc = JAXBContext.newInstance("org.itracker");
150         Unmarshaller u = jc.createUnmarshaller();
151         AbstractEntity o = (AbstractEntity) u.unmarshal(is);
152         return o;
153     }
154 
155     public static void export(AbstractEntity o, OutputStream os) throws Exception {
156         // TODO marshal to System.out
157         JAXBContext jc = JAXBContext.newInstance("org.itracker");
158         Marshaller m = jc.createMarshaller();
159         m.marshal(o, System.out);
160     }
161 
162     public static void exportIssues(XMLWriter writer, List<Issue> issues, SystemConfiguration config) throws ImportExportException {
163         Element elRoot = getDocumentFactory().createElement(TAG_ROOT);
164         try {
165             writer.startDocument();
166             writer.writeOpen(elRoot);
167 
168             exportConfigModels(writer, config);
169             exportIssuesModels(writer, issues);
170 
171             writer.writeClose(elRoot);
172             writer.endDocument();
173         } catch (SAXException e) {
174             throw new ImportExportException(e.getMessage(), ImportExportException.TYPE_UNKNOWN);
175         } catch (IOException e) {
176             throw new ImportExportException("Problem writing export stream. "
177                     + e.getMessage(), ImportExportException.TYPE_UNKNOWN);
178         }
179     }
180 
181     public static void exportConfigModels(XMLWriter writer, SystemConfiguration config) throws IOException {
182 
183         if (config != null) {
184 
185             Element elConfigs = getDocumentFactory().createElement(TAG_CONFIGURATION);
186             writer.writeOpen(elConfigs);
187             getConfigurationXML(writer, config);
188             writer.writeClose(elConfigs);
189         }
190     }
191     public static void exportIssuesModels(XMLWriter writer, List<Issue> issues) throws IOException {
192         HashMap<String, Project> projects = new HashMap<>();
193         HashMap<String, User> users = new HashMap<>();
194 
195         if (issues == null || issues.size() == 0) {
196             throw new IllegalArgumentException("The issue list was null or zero length.");
197         }
198 
199         // initialize dataset
200         for (Issue issue : issues) {
201             if (!projects.containsKey(issue.getProject().getId().toString())) {
202                 if (logger.isDebugEnabled())
203                 logger.debug("Adding new project " + issue.getProject().getId() + " to export.");
204                 projects.put(issue.getProject().getId().toString(), issue.getProject());
205             }
206 
207             if (issue.getCreator() != null && !users.containsKey(issue.getCreator().getId().toString())) {
208                 if (logger.isDebugEnabled())
209                 logger.debug("Adding new user " + issue.getCreator().getId() + " to export.");
210                 users.put(issue.getCreator().getId().toString(), issue.getCreator());
211             }
212             if (issue.getOwner() != null && !users.containsKey(issue.getOwner().getId().toString())) {
213                 if (logger.isDebugEnabled())
214                 logger.debug("Adding new user " + issue.getOwner().getId() + " to export.");
215                 users.put(issue.getOwner().getId().toString(), issue.getOwner());
216             }
217 
218             List<IssueHistory> history = issue.getHistory();
219             for (IssueHistory aHistory : history) {
220                 if (aHistory != null && aHistory.getUser() != null && !users.containsKey(aHistory.getUser().getId().toString())) {
221                     if (logger.isDebugEnabled())
222                         logger.debug("Adding new user " + aHistory.getUser().getId() + " to export.");
223                     users.put(aHistory.getUser().getId().toString(), aHistory.getUser());
224                 }
225             }
226 
227             List<IssueAttachment> attachments = issue.getAttachments();
228             for (IssueAttachment attachment : attachments) {
229                 if (attachment != null && attachment.getUser() != null && !users.containsKey(attachment.getUser().getId().toString())) {
230                     if (logger.isDebugEnabled())
231                         logger.debug("Adding new user " + attachment.getUser().getId() + " to export.");
232                     users.put(attachment.getUser().getId().toString(), attachment.getUser());
233                 }
234             }
235         }
236 
237 
238         for (String s : projects.keySet()) {
239             Project project = projects.get(s);
240             for (User o:project.getOwners()) {
241                 users.put(o.getId().toString(), o);
242             }
243         }
244 
245         Element elUsers = getDocumentFactory().createElement(TAG_USERS);
246         writer.writeOpen(elUsers);
247         for (User u: users.values()) {
248             exportModel(writer, u);
249         }
250         writer.writeClose(elUsers);
251 
252         Element elProjects = getDocumentFactory().createElement(TAG_PROJECTS);
253         writer.writeOpen(elProjects);
254         for (Project s : projects.values()) {
255             exportModel(writer, s);
256         }
257         writer.writeClose(elProjects);
258 
259         Element elIssues = getDocumentFactory().createElement(TAG_ISSUES);
260         writer.writeOpen(elIssues);
261         for (Issue issue: issues) {
262             exportModel(writer, issue);
263         }
264         writer.writeClose(elIssues);
265 
266     }
267     /**
268      * Takes an array of IssueModels and exports them as XML suitable for import into another
269      * instance of ITracker, or another issue tracking tool.
270      *
271      * @param issues an array of Issue objects to export
272      * @throws ImportExportException thrown if the array of issues can not be exported
273      */
274     public static String exportIssues(List<Issue> issues, SystemConfiguration config) throws ImportExportException {
275         StringBuffer buf = new StringBuffer();
276         HashMap<String, Project> projects = new HashMap<String, Project>();
277         HashMap<String, User> users = new HashMap<String, User>();
278 
279         if (issues == null || issues.size() == 0) {
280             throw new ImportExportException("The issue list was null or zero length.");
281         }
282         buf.append("<" + TAG_ISSUES + ">\n");
283         for (int i = 0; i < issues.size(); i++) {
284             if (!projects.containsKey(issues.get(i).getProject().getId().toString())) {
285                 logger.debug("Adding new project " + issues.get(i).getProject().getId() + " to export.");
286                 projects.put(issues.get(i).getProject().getId().toString(), issues.get(i).getProject());
287             }
288 
289             if (issues.get(i).getCreator() != null && !users.containsKey(issues.get(i).getCreator().getId().toString())) {
290                 logger.debug("Adding new user " + issues.get(i).getCreator().getId() + " to export.");
291                 users.put(issues.get(i).getCreator().getId().toString(), issues.get(i).getCreator());
292             }
293             if (issues.get(i).getOwner() != null && !users.containsKey(issues.get(i).getOwner().getId().toString())) {
294                 logger.debug("Adding new user " + issues.get(i).getOwner().getId() + " to export.");
295                 users.put(issues.get(i).getOwner().getId().toString(), issues.get(i).getOwner());
296             }
297 
298             List<IssueHistory> history = issues.get(i).getHistory();
299             for (int j = 0; j < history.size(); j++) {
300                 if (history.get(j) != null && history.get(j).getUser() != null && !users.containsKey(history.get(j).getUser().getId().toString())) {
301                     logger.debug("Adding new user " + history.get(j).getUser().getId() + " to export.");
302                     users.put(history.get(j).getUser().getId().toString(), history.get(j).getUser());
303                 }
304             }
305 
306             List<IssueAttachment> attachments = issues.get(i).getAttachments();
307             for (int j = 0; j < attachments.size(); j++) {
308                 if (attachments.get(j) != null && attachments.get(j).getUser() != null && !users.containsKey(attachments.get(j).getUser().getId().toString())) {
309                     logger.debug("Adding new user " + attachments.get(j).getUser().getId() + " to export.");
310                     users.put(attachments.get(j).getUser().getId().toString(), attachments.get(j).getUser());
311                 }
312             }
313 
314             buf.append(exportModel((AbstractEntity) issues.get(i)));
315         }
316         buf.append("</" + TAG_ISSUES + ">\n");
317         buf.append("</" + TAG_ROOT + ">\n");
318 
319 
320         buf.insert(0, "</" + TAG_PROJECTS + ">\n");
321         for (Iterator<String> iter = projects.keySet().iterator(); iter.hasNext(); ) {
322             Project project = (Project) projects.get((String) iter.next());
323             for (int i = 0; i < project.getOwners().size(); i++) {
324                 users.put(project.getOwners().get(i).getId().toString(), project.getOwners().get(i));
325             }
326             buf.insert(0, exportModel((AbstractEntity) project));
327         }
328         buf.insert(0, "<" + TAG_PROJECTS + ">\n");
329 
330         buf.insert(0, "</" + TAG_USERS + ">\n");
331         for (Iterator<String> iter = users.keySet().iterator(); iter.hasNext(); ) {
332             buf.insert(0, exportModel((AbstractEntity) users.get((String) iter.next())));
333         }
334         buf.insert(0, "<" + TAG_USERS + ">\n");
335 
336         if (config != null) {
337             buf.insert(0, "</" + TAG_CONFIGURATION + ">\n");
338             buf.insert(0, getConfigurationXML(config));
339             buf.insert(0, "<" + TAG_CONFIGURATION + ">\n");
340         }
341 
342         buf.insert(0, "<" + TAG_ROOT + ">\n");
343 
344         return buf.toString();
345     }
346 
347 
348     /**
349      * Returns the appropriate XML block for a given model.
350      *
351      * @param abstractBean a model that extends AbstractEntity
352      * @throws ImportExportException thrown if the given model can not be exported
353      */
354     public static String exportModel(AbstractEntity abstractBean) throws ImportExportException {
355 
356         ByteArrayOutputStream os = new ByteArrayOutputStream();
357         try {
358             XMLWriter writer = new XMLWriter(os);
359             exportModel(writer, abstractBean);
360             writer.close();
361             return (os.toString("utf-8"));
362         } catch (Exception e) {
363             logger.error("could not create xml string", e);
364             throw new ImportExportException(e.getMessage(), ImportExportException.TYPE_UNKNOWN);
365         }
366 
367     }
368     public static void exportModel(XMLWriter writer, AbstractEntity abstractBean) throws IOException {
369         if (abstractBean == null) {
370             throw new IllegalArgumentException("The bean to export was null.");
371         } else if (abstractBean instanceof Issue) {
372             getIssueXML(writer, (Issue) abstractBean);
373         } else if (abstractBean instanceof Project) {
374             getProjectXML(writer, (Project) abstractBean);
375         } else if (abstractBean instanceof User) {
376             getUserXML(writer, (User) abstractBean);
377         } else {
378             throw new IllegalArgumentException("This bean type can not be exported.");
379         }
380     }
381 
382     /**
383      * Write the properties to simple XML tags
384      * @param writer
385      * @param tags
386      * @throws IOException
387      */
388     private static void addPropertyTags(XMLWriter writer, Properties tags) throws IOException {
389         DocumentFactory factory = getDocumentFactory();
390         for (String tag: tags.stringPropertyNames()) {
391             Element el = factory.createElement(tag);
392             el.setText(tags.getProperty(tag));
393             writer.write(el);
394         }
395     }
396 
397     /**
398      * Write the properties to simple CDATA tags
399      * @param writer
400      * @param tags
401      * @throws IOException
402      */
403     private static void addCdataPropertyTags(XMLWriter writer, Properties tags) throws IOException {
404         DocumentFactory factory = getDocumentFactory();
405         for (String tag: tags.stringPropertyNames()) {
406             Element el = factory.createElement(tag);
407             el.add(factory.createCDATA(tags.getProperty(tag)));
408             writer.write(el);
409         }
410     }
411 
412     private static void addIdCollection(XMLWriter writer, List<? extends AbstractEntity> entities,
413                                         String elName, String itName, String idPrefix) throws IOException {
414         if (entities.size() > 0) {
415             Element elTmp;
416             final DocumentFactory factory = getDocumentFactory();
417             final Element el = factory.createElement(elName);
418             writer.writeOpen(el);
419             for (AbstractEntity c: entities) {
420                 elTmp = factory.createElement(itName);
421                 elTmp.setText(idPrefix + c.getId());
422                 writer.write(elTmp);
423             }
424             writer.writeClose(el);
425         }
426     }
427     private static void addIssueFields(XMLWriter writer, List<IssueField> entities) throws IOException {
428         if (entities.size() > 0) {
429             Element elTmp;
430             final DocumentFactory factory = getDocumentFactory();
431             final Element el = factory.createElement(TAG_ISSUE_FIELDS);
432             writer.writeOpen(el);
433             for (IssueField c: entities) {
434                 elTmp = factory.createElement(TAG_ISSUE_FIELD);
435                 elTmp.addAttribute(ATTR_ID, TAG_CUSTOM_FIELD + c.getId());
436                 elTmp.add(factory.createCDATA(c.getValue(EXPORT_LOCALE)));
437                 writer.write(elTmp);
438             }
439             writer.writeClose(el);
440         }
441     }
442 
443     private static void addIssueAttachments(XMLWriter writer, List<IssueAttachment> entities) throws IOException {
444         if (entities.size() > 0) {
445             Element elTmp;
446             final DocumentFactory factory = getDocumentFactory();
447             final Element el = factory.createElement(TAG_ISSUE_ATTACHMENTS);
448             writer.writeOpen(el);
449             for (IssueAttachment c: entities) {
450                 elTmp = factory.createElement(TAG_ISSUE_ATTACHMENT);
451                 writer.writeOpen(elTmp);
452                 Properties pTmp = new Properties();
453                 pTmp.setProperty(TAG_ISSUE_ATTACHMENT_DESCRIPTION, ITrackerResources.escapeUnicodeString(c.getDescription(), false));
454                 pTmp.setProperty(TAG_ISSUE_ATTACHMENT_FILENAME, ITrackerResources.escapeUnicodeString(c.getFileName(), false));
455                 pTmp.setProperty(TAG_ISSUE_ATTACHMENT_ORIGFILE, ITrackerResources.escapeUnicodeString(c.getOriginalFileName(), false));
456                 addCdataPropertyTags(writer, pTmp);
457                 pTmp.clear();
458 
459                 pTmp.setProperty(TAG_ISSUE_ATTACHMENT_SIZE, String.valueOf(c.getSize()));
460                 pTmp.setProperty(TAG_ISSUE_ATTACHMENT_TYPE, StringUtils.defaultString(c.getType(), "application/octet-stream"));
461                 pTmp.setProperty(TAG_ISSUE_ATTACHMENT_CREATOR, TAG_USER + c.getUser().getId());
462                 addPropertyTags(writer, pTmp);
463                 writer.writeClose(elTmp);
464             }
465             writer.writeClose(el);
466         }
467     }
468 
469     private static void addIssueHistory(XMLWriter writer, List<IssueHistory> entities) throws IOException {
470         if (entities.size() > 0) {
471             Element elTmp;
472             final DocumentFactory factory = getDocumentFactory();
473             final Element el = factory.createElement(TAG_ISSUE_HISTORY);
474             writer.writeOpen(el);
475             for (IssueHistory c: entities) {
476                 elTmp = factory.createElement(TAG_HISTORY_ENTRY);
477                 elTmp.addAttribute(ATTR_CREATOR_ID, TAG_USER + c.getUser().getId());
478                 elTmp.addAttribute(ATTR_DATE, DATE_FORMATTER.format(c.getCreateDate()));
479                 elTmp.addAttribute(ATTR_STATUS, String.valueOf(c.getStatus()));
480 
481                 writer.writeOpen(elTmp);
482                 writer.write(factory.createCDATA(ITrackerResources.escapeUnicodeString(c.getDescription(), false)));
483                 writer.writeClose(elTmp);
484             }
485             writer.writeClose(el);
486         }
487     }
488 
489     public static void getIssueXML(XMLWriter writer, Issue issue) throws IOException {
490         Element elIssue = getDocumentFactory().createElement(TAG_ISSUE);
491         elIssue.addAttribute(ATTR_ID, TAG_ISSUE+issue.getId());
492         elIssue.addAttribute(ATTR_SYSTEMID, String.valueOf(issue.getId()));
493 
494         writer.writeOpen(elIssue);
495 
496         final Properties tags = new Properties();
497         final Properties ctags = new Properties();
498 
499         tags.setProperty(TAG_ISSUE_PROJECT, TAG_PROJECT + issue.getProject().getId());
500         ctags.setProperty(TAG_ISSUE_DESCRIPTION, ITrackerResources.escapeUnicodeString(issue.getDescription(), false));
501         tags.setProperty(TAG_ISSUE_SEVERITY, String.valueOf(issue.getSeverity()));
502         tags.setProperty(TAG_ISSUE_STATUS, String.valueOf(issue.getStatus()));
503         tags.setProperty(TAG_ISSUE_RESOLUTION, StringUtils.defaultString(issue.getResolution()));
504         if (issue.getTargetVersion() != null) {
505             tags.setProperty(TAG_TARGET_VERSION_ID, TAG_VERSION + issue.getTargetVersion().getId());
506         }
507         tags.setProperty(TAG_CREATE_DATE, DATE_FORMATTER.format(issue.getCreateDate()));
508         tags.setProperty(TAG_LAST_MODIFIED, DATE_FORMATTER.format(issue.getLastModifiedDate()));
509         tags.setProperty(TAG_CREATOR, TAG_USER + issue.getCreator().getId());
510         if (issue.getOwner() != null) {
511             tags.setProperty(TAG_OWNER, TAG_USER + issue.getOwner().getId());
512         }
513 
514         addPropertyTags(writer, tags);
515         addCdataPropertyTags(writer, ctags);
516 
517         addIdCollection(writer, issue.getComponents(), TAG_ISSUE_COMPONENTS, TAG_COMPONENT_ID, TAG_COMPONENT);
518         addIdCollection(writer, issue.getVersions(), TAG_ISSUE_VERSIONS, TAG_VERSION_ID, TAG_VERSION);
519 
520         addIssueFields(writer, issue.getFields());
521         addIssueAttachments(writer, issue.getAttachments());
522         addIssueHistory(writer, issue.getHistory());
523 
524         writer.writeClose(elIssue);
525     }
526     /**
527      * Generates an XML block that encapsulates an issue for import or export.  This
528      * function will not generate the XML for other models needed for a complete import
529      * or export.
530      *
531      * @param issue an Issue to generate the XML for
532      * @return a String containing the XML for the issue
533      */
534     public static String getIssueXML(Issue issue) {
535         if (issue == null) {
536             return "";
537         }
538         ByteArrayOutputStream os = new ByteArrayOutputStream();
539         try {
540             XMLWriter writer = new XMLWriter(os);
541             getIssueXML(writer, issue);
542             writer.close();
543             return (os.toString("utf-8"));
544         } catch (Exception e) {
545             logger.error("could not create xml string", e);
546             return "";
547         }
548     }
549 
550 
551     private static void addProjectComponents(XMLWriter writer, List<Component> entities) throws IOException {
552         if (entities.size() > 0) {
553             Element elTmp;
554             final DocumentFactory factory = getDocumentFactory();
555             final Element el = factory.createElement(TAG_COMPONENTS);
556             writer.writeOpen(el);
557             for (Component c : entities) {
558                 elTmp = factory.createElement(TAG_COMPONENT);
559 
560                 elTmp.addAttribute(ATTR_ID, TAG_CUSTOM_FIELD + c.getId());
561                 elTmp.addAttribute(ATTR_SYSTEMID, String.valueOf(c.getId()));
562 
563                 writer.writeOpen(elTmp);
564                 Properties tags = new Properties();
565                 tags.setProperty(TAG_COMPONENT_NAME, ITrackerResources.escapeUnicodeString(c.getName(), false));
566                 tags.setProperty(TAG_COMPONENT_DESCRIPTION, ITrackerResources.escapeUnicodeString(c.getDescription(), false));
567                 addCdataPropertyTags(writer, tags);
568                 writer.writeClose(elTmp);
569             }
570             writer.writeClose(el);
571         }
572     }
573 
574     private static void addProjectVersions(XMLWriter writer, List<Version> entities) throws IOException {
575         if (entities.size() > 0) {
576             Element elTmp;
577             final DocumentFactory factory = getDocumentFactory();
578             final Element el = factory.createElement(TAG_VERSIONS);
579             writer.writeOpen(el);
580             for (Version c : entities) {
581                 elTmp = factory.createElement(TAG_VERSION);
582 
583                 elTmp.addAttribute(ATTR_ID, TAG_VERSION + c.getId());
584                 elTmp.addAttribute(ATTR_SYSTEMID, String.valueOf(c.getId()));
585 
586                 writer.writeOpen(elTmp);
587                 Properties tags = new Properties();
588                 tags.setProperty(TAG_VERSION_NUMBER, ITrackerResources.escapeUnicodeString(c.getNumber(), false));
589                 tags.setProperty(TAG_VERSION_DESCRIPTION, ITrackerResources.escapeUnicodeString(c.getDescription(), false));
590                 addCdataPropertyTags(writer, tags);
591                 writer.writeClose(elTmp);
592             }
593             writer.writeClose(el);
594         }
595     }
596     private static void getProjectXML(XMLWriter writer, Project project) throws IOException {
597         Element elProject = getDocumentFactory().createElement(TAG_PROJECT);
598         elProject.addAttribute(ATTR_ID, TAG_PROJECT + project.getId());
599         elProject.addAttribute(ATTR_SYSTEMID, String.valueOf(project.getId()));
600 
601         writer.writeOpen(elProject);
602         Properties tags = new Properties();
603         tags.setProperty(TAG_PROJECT_NAME, ITrackerResources.escapeUnicodeString(project.getName(), false));
604         tags.setProperty(TAG_PROJECT_DESCRIPTION, ITrackerResources.escapeUnicodeString(project.getDescription(), false));
605         addCdataPropertyTags(writer, tags);
606         tags.clear();
607         tags.setProperty(TAG_PROJECT_STATUS, ProjectUtilities.getStatusName(project.getStatus(), EXPORT_LOCALE));
608         tags.setProperty(TAG_PROJECT_OPTIONS, String.valueOf(project.getOptions()));
609         addPropertyTags(writer, tags);
610 
611         addIdCollection(writer, project.getCustomFields(), TAG_PROJECT_FIELDS, TAG_PROJECT_FIELD_ID, TAG_CUSTOM_FIELD);
612         addIdCollection(writer, project.getOwners(), TAG_PROJECT_OWNERS, TAG_PROJECT_OWNER_ID, TAG_USER);
613 
614         addProjectComponents(writer, project.getComponents());
615         addProjectVersions(writer, project.getVersions());
616 
617         writer.writeClose(elProject);
618 
619     }
620 
621     /**
622      * Generates an XML block that encapsulates a project for import or export.  This
623      * function will not generate the XML for other models needed for a complete import
624      * or export.
625      *
626      * @param project a Project to generate the XML for
627      * @return a String containing the XML for the project
628      */
629     public static String getProjectXML(Project project) {
630         if (project == null) {
631             return "";
632         }
633         ByteArrayOutputStream os = new ByteArrayOutputStream();
634         try {
635             XMLWriter writer = new XMLWriter(os);
636             getProjectXML(writer, project);
637             writer.close();
638             return (os.toString("utf-8"));
639         } catch (Exception e) {
640             logger.error("could not create xml string", e);
641             return "";
642         }
643     }
644 
645     public static void getUserXML(XMLWriter writer, User user) throws IOException {
646         Element elUser = getDocumentFactory().createElement(TAG_USER);
647         elUser.addAttribute(ATTR_ID, TAG_USER + user.getId());
648         elUser.addAttribute(ATTR_SYSTEMID, String.valueOf(user.getId()));
649 
650         writer.writeOpen(elUser);
651         Properties tags = new Properties();
652         tags.setProperty(TAG_LOGIN, ITrackerResources.escapeUnicodeString(user.getLogin(), false));
653         tags.setProperty(TAG_FIRST_NAME, ITrackerResources.escapeUnicodeString(user.getFirstName(), false));
654         tags.setProperty(TAG_LAST_NAME, ITrackerResources.escapeUnicodeString(user.getLastName(), false));
655         tags.setProperty(TAG_EMAIL, ITrackerResources.escapeUnicodeString(user.getEmail(), false));
656         addCdataPropertyTags(writer, tags);
657         tags.clear();
658 
659         tags.setProperty(TAG_USER_STATUS, String.valueOf(user.getStatus()));
660         tags.setProperty(TAG_SUPER_USER, String.valueOf(user.isSuperUser()));
661         addPropertyTags(writer, tags);
662 
663         writer.writeClose(elUser);
664     }
665     /**
666      * Generates an XML block that encapsulates a user for import or export.  This
667      * function will not generate the XML for other models needed for a complete import
668      * or export.
669      *
670      * @param user a User to generate the XML for
671      * @return a String containing the XML for the user
672      */
673     public static String getUserXML(User user) {
674         if (user == null) {
675             return "";
676         }
677         ByteArrayOutputStream os = new ByteArrayOutputStream();
678         try {
679             XMLWriter writer = new XMLWriter(os);
680             getUserXML(writer, user);
681             writer.close();
682             return (os.toString("utf-8"));
683         } catch (Exception e) {
684             logger.error("could not create xml string", e);
685             return "";
686         }
687 
688     }
689 
690 
691     public static void getConfigurationXML(XMLWriter writer, SystemConfiguration config) throws IOException {
692         Properties tags = new Properties();
693         tags.setProperty(TAG_CONFIGURATION_VERSION, StringUtils.defaultString(config.getVersion(), "null"));
694         addCdataPropertyTags(writer, tags);
695 
696         getCustomFieldsXML(writer, config);
697 
698         getConfigurationXML(writer, config.getResolutions(), TAG_RESOLUTIONS, TAG_RESOLUTION);
699         getConfigurationXML(writer, config.getSeverities(), TAG_SEVERITIES, TAG_SEVERITY);
700         getConfigurationXML(writer, config.getStatuses(), TAG_STATUSES, TAG_STATUS);
701 
702     }
703 
704     private static void getConfigurationXML(XMLWriter writer, List<Configuration> cs, String elName, String itName) throws IOException {
705         Element el = getDocumentFactory().createElement(elName);
706         writer.writeOpen(el);
707         for (Configuration c: cs) {
708             Element elIt = getDocumentFactory().createElement(itName);
709             elIt.addAttribute(ATTR_VALUE, c.getValue());
710             elIt.addAttribute(ATTR_ORDER, String.valueOf(c.getOrder()));
711             writer.writeOpen(elIt);
712             writer.write(getDocumentFactory().createCDATA(ITrackerResources.escapeUnicodeString(c.getName(), false)));
713             writer.writeClose(elIt);
714         }
715         writer.writeClose(el);
716     }
717 
718     private static void getCustomFieldsXML(XMLWriter writer, SystemConfiguration config) throws IOException {
719         Properties tags = new Properties();
720         Element elCustomField = getDocumentFactory().createElement(TAG_CUSTOM_FIELDS);
721         Element elTmp;
722         writer.writeOpen(elCustomField);
723         for (CustomField c: config.getCustomFields()) {
724             tags.clear();
725             tags.setProperty(TAG_CUSTOM_FIELD_LABEL, ITrackerResources.escapeUnicodeString(CustomFieldUtilities.getCustomFieldName(c.getId()), false));
726             tags.setProperty(TAG_CUSTOM_FIELD_TYPE,  String.valueOf(c.getFieldType().name()));
727             tags.setProperty(TAG_CUSTOM_FIELD_REQUIRED, String.valueOf(c.isRequired()));
728             tags.setProperty(TAG_CUSTOM_FIELD_DATEFORMAT, ITrackerResources.escapeUnicodeString(c.getDateFormat(), false));
729             tags.setProperty(TAG_CUSTOM_FIELD_SORTOPTIONS, String.valueOf(c.isSortOptionsByName()));
730             tags.setProperty(TAG_CUSTOM_FIELD_LABEL, ITrackerResources.escapeUnicodeString(CustomFieldUtilities.getCustomFieldName(c.getId()), false));
731             elTmp = getDocumentFactory().createElement(TAG_CUSTOM_FIELD);
732 
733             elTmp.addAttribute(ATTR_ID, TAG_CUSTOM_FIELD + c.getId());
734             elTmp.addAttribute(ATTR_SYSTEMID, String.valueOf(c.getId()));
735             writer.writeOpen(elTmp);
736             addCdataPropertyTags(writer, tags);
737             if (c.getFieldType() == CustomField.Type.LIST) {
738                 Element elOption;
739                 for (CustomFieldValue o: c.getOptions()) {
740                     elOption = getDocumentFactory().createElement(TAG_CUSTOM_FIELD_OPTION);
741                     elOption.addAttribute(ATTR_VALUE, ITrackerResources.escapeUnicodeString(o.getValue(), false));
742                     elOption.add(getDocumentFactory().createCDATA(ITrackerResources.escapeUnicodeString(CustomFieldUtilities.getCustomFieldOptionName(o, null), false)));
743                     writer.write(elOption);
744                 }
745             }
746             writer.writeClose(elTmp);
747         }
748 
749 
750         writer.writeClose(elCustomField);
751     }
752 
753     /**
754      * Generates an XML block that encapsulates the system configuration for import or export.
755      * This function will not generate the XML for other models needed for a complete import
756      * or export.
757      *
758      * @param config a SystemConfiguration to generate the XML for
759      * @return a String containing the XML for the configuration
760      */
761     public static String getConfigurationXML(SystemConfiguration config) {
762         if (config == null) {
763             return "";
764         }
765 
766         ByteArrayOutputStream os = new ByteArrayOutputStream();
767         try {
768             XMLWriter writer = new XMLWriter(os);
769             getConfigurationXML(writer, config);
770             writer.close();
771             return (os.toString("utf-8"));
772         } catch (Exception e) {
773             logger.error("could not create xml string", e);
774             return "";
775         }
776     }
777 }