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.services.util;
20  
21  import org.apache.log4j.Logger;
22  import org.itracker.core.resources.ITrackerResources;
23  import org.itracker.model.*;
24  import org.itracker.services.exceptions.ImportExportException;
25  import org.xml.sax.InputSource;
26  import org.xml.sax.XMLReader;
27  import org.xml.sax.helpers.XMLReaderFactory;
28  
29  import javax.xml.bind.JAXBContext;
30  import javax.xml.bind.Marshaller;
31  import javax.xml.bind.Unmarshaller;
32  import java.io.OutputStream;
33  import java.io.StringReader;
34  import java.util.HashMap;
35  import java.util.Iterator;
36  import java.util.List;
37  
38  
39  /**
40   * 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.
41   * <p/>
42   * This class provides functionality needed to import and export issues and their associated
43   * data as XML.  This xml provides all the data necessary to import the issues into another
44   * instance of ITracker or some other issue tracking tool.
45   */
46  public class ImportExportUtilities implements ImportExportTags {
47  
48      private static final Logger logger = Logger.getLogger(ImportExportUtilities.class);
49      public static final int IMPORT_STAT_NEW = 0;
50      public static final int IMPORT_STAT_REUSED = 1;
51  
52      public static final int IMPORT_STAT_USERS = 0;
53      public static final int IMPORT_STAT_PROJECTS = 1;
54      public static final int IMPORT_STAT_ISSUES = 2;
55      public static final int IMPORT_STAT_STATUSES = 3;
56      public static final int IMPORT_STAT_SEVERITIES = 4;
57      public static final int IMPORT_STAT_RESOLUTIONS = 5;
58      public static final int IMPORT_STAT_FIELDS = 6;
59  
60      public ImportExportUtilities() {
61      }
62  
63  
64      /**
65       * Takes an XML file matching the ITracker import/export DTD and returns an array
66       * of AbstractBean objects.  The array will contain all of the projects, components
67       * versions, users, custom fields, and issues contained in the XML.
68       *
69       * @param xml an xml string to import
70       * @throws ImportExportException thrown if the xml can not be parsed into the appropriate objects
71       */
72      public static AbstractEntity[] importIssues(String xml) throws ImportExportException {
73          AbstractEntity[] abstractBeans = new AbstractEntity[0];
74  
75          try {
76              logger.debug("Starting XML data import.");
77  
78              XMLReader reader = XMLReaderFactory.createXMLReader();
79              ImportHandler handler = new ImportHandler();
80              reader.setContentHandler(handler);
81              reader.setErrorHandler(handler);
82              reader.parse(new InputSource(new StringReader(xml)));
83              abstractBeans = handler.getModels();
84  
85              logger.debug("Imported a total of " + abstractBeans.length + " beans.");
86          } catch (Exception e) {
87              logger.debug("Exception.", e);
88              throw new ImportExportException(e.getMessage());
89          }
90  
91          return abstractBeans;
92      }
93  
94  
95      public static AbstractEntity importXml(InputSource is) throws Exception {
96          // unmarshal from is
97          JAXBContext jc = JAXBContext.newInstance("ch.dope");
98          Unmarshaller u = jc.createUnmarshaller();
99          AbstractEntity o = (AbstractEntity) u.unmarshal(is);
100         return o;
101     }
102 
103     public static void export(AbstractEntity o, OutputStream os) throws Exception {
104         JAXBContext jc = JAXBContext.newInstance("ch.dope");
105 
106 
107         // marshal to System.out
108         Marshaller m = jc.createMarshaller();
109         m.marshal(o, System.out);
110     }
111 
112     /**
113      * Takes an array of IssueModels and exports them as XML suitable for import into another
114      * instance of ITracker, or another issue tracking tool.
115      *
116      * @param issues an array of Issue objects to export
117      * @throws ImportExportException thrown if the array of issues can not be exported
118      */
119     public static String exportIssues(List<Issue> issues, SystemConfiguration config) throws ImportExportException {
120         StringBuffer buf = new StringBuffer();
121         HashMap<String, Project> projects = new HashMap<String, Project>();
122         HashMap<String, User> users = new HashMap<String, User>();
123         // HashSet customFields = new HashSet();
124 
125         if (issues == null || issues.size() == 0) {
126             throw new ImportExportException("The issue list was null or zero length.");
127         }
128         buf.append("<" + TAG_ISSUES + ">\n");
129         for (int i = 0; i < issues.size(); i++) {
130             if (!projects.containsKey(issues.get(i).getProject().getId().toString())) {
131                 logger.debug("Adding new project " + issues.get(i).getProject().getId() + " to export.");
132                 projects.put(issues.get(i).getProject().getId().toString(), issues.get(i).getProject());
133             }
134 
135             if (issues.get(i).getCreator() != null && !users.containsKey(issues.get(i).getCreator().getId().toString())) {
136                 logger.debug("Adding new user " + issues.get(i).getCreator().getId() + " to export.");
137                 users.put(issues.get(i).getCreator().getId().toString(), issues.get(i).getCreator());
138             }
139             if (issues.get(i).getOwner() != null && !users.containsKey(issues.get(i).getOwner().getId().toString())) {
140                 logger.debug("Adding new user " + issues.get(i).getOwner().getId() + " to export.");
141                 users.put(issues.get(i).getOwner().getId().toString(), issues.get(i).getOwner());
142             }
143 
144             List<IssueHistory> history = issues.get(i).getHistory();
145             for (int j = 0; j < history.size(); j++) {
146                 if (history.get(j) != null && history.get(j).getUser() != null && !users.containsKey(history.get(j).getUser().getId().toString())) {
147                     logger.debug("Adding new user " + history.get(j).getUser().getId() + " to export.");
148                     users.put(history.get(j).getUser().getId().toString(), history.get(j).getUser());
149                 }
150             }
151 
152             List<IssueAttachment> attachments = issues.get(i).getAttachments();
153             for (int j = 0; j < attachments.size(); j++) {
154                 if (attachments.get(j) != null && attachments.get(j).getUser() != null && !users.containsKey(attachments.get(j).getUser().getId().toString())) {
155                     logger.debug("Adding new user " + attachments.get(j).getUser().getId() + " to export.");
156                     users.put(attachments.get(j).getUser().getId().toString(), attachments.get(j).getUser());
157                 }
158             }
159 
160             buf.append(exportModel((AbstractEntity) issues.get(i)));
161         }
162         buf.append("</" + TAG_ISSUES + ">\n");
163         buf.append("</" + TAG_ROOT + ">\n");
164 
165 
166         buf.insert(0, "</" + TAG_PROJECTS + ">\n");
167         for (Iterator<String> iter = projects.keySet().iterator(); iter.hasNext(); ) {
168             Project project = (Project) projects.get((String) iter.next());
169             for (int i = 0; i < project.getOwners().size(); i++) {
170                 users.put(project.getOwners().get(i).getId().toString(), project.getOwners().get(i));
171             }
172             buf.insert(0, exportModel((AbstractEntity) project));
173         }
174         buf.insert(0, "<" + TAG_PROJECTS + ">\n");
175 
176         buf.insert(0, "</" + TAG_USERS + ">\n");
177         for (Iterator<String> iter = users.keySet().iterator(); iter.hasNext(); ) {
178             buf.insert(0, exportModel((AbstractEntity) users.get((String) iter.next())));
179         }
180         buf.insert(0, "<" + TAG_USERS + ">\n");
181 
182         if (config != null) {
183             buf.insert(0, "</" + TAG_CONFIGURATION + ">\n");
184             buf.insert(0, getConfigurationXML(config));
185             buf.insert(0, "<" + TAG_CONFIGURATION + ">\n");
186         }
187 
188         buf.insert(0, "<" + TAG_ROOT + ">\n");
189 
190         return buf.toString();
191     }
192 
193     /**
194      * Returns the appropriate XML block for a given model.
195      *
196      * @param model a model that extends AbstractBean
197      * @throws ImportExportException thrown if the given model can not be exported
198      */
199     public static String exportModel(AbstractEntity abstractBean) throws ImportExportException {
200         if (abstractBean == null) {
201             throw new ImportExportException("The bean to export was null.");
202         } else if (abstractBean instanceof Issue) {
203             return getIssueXML((Issue) abstractBean);
204         } else if (abstractBean instanceof Project) {
205             return getProjectXML((Project) abstractBean);
206         } else if (abstractBean instanceof User) {
207             return getUserXML((User) abstractBean);
208         } else {
209             throw new ImportExportException("This bean type can not be exported.");
210         }
211     }
212 
213     /**
214      * Generates an XML block that encapsulates an issue for import or export.  This
215      * function will not generate the XML for other models needed for a complete import
216      * or export.
217      *
218      * @param issue an Issue to generate the XML for
219      * @return a String containing the XML for the issue
220      */
221     public static String getIssueXML(Issue issue) {
222         if (issue == null) {
223             return "";
224         }
225 
226         StringBuffer buf = new StringBuffer();
227 
228         buf.append("<" + TAG_ISSUE + " " + ATTR_ID + "=\"" + TAG_ISSUE + issue.getId() + "\" " + ATTR_SYSTEMID + "=\"" + issue.getId() + "\">\n");
229         buf.append("  <" + TAG_ISSUE_PROJECT + "><![CDATA[" + TAG_PROJECT + issue.getProject().getId() + "]]></" + TAG_ISSUE_PROJECT + ">\n");
230         buf.append("  <" + TAG_ISSUE_DESCRIPTION + "><![CDATA[" + ITrackerResources.escapeUnicodeString(issue.getDescription(), false) + "]]></" + TAG_ISSUE_DESCRIPTION + ">\n");
231         buf.append("  <" + TAG_ISSUE_SEVERITY + ">" + issue.getSeverity() + "</" + TAG_ISSUE_SEVERITY + ">\n");
232         buf.append("  <" + TAG_ISSUE_STATUS + ">" + issue.getStatus() + "</" + TAG_ISSUE_STATUS + ">\n");
233         buf.append("  <" + TAG_ISSUE_RESOLUTION + "><![CDATA[" + ITrackerResources.escapeUnicodeString(issue.getResolution(), false) + "]]></" + TAG_ISSUE_RESOLUTION + ">\n");
234         if (issue.getTargetVersion() != null) {
235             buf.append("  <" + TAG_TARGET_VERSION_ID + ">" + TAG_VERSION + issue.getTargetVersion().getId() + "</" + TAG_TARGET_VERSION_ID + ">\n");
236         }
237         buf.append("  <" + TAG_CREATE_DATE + ">" + DATE_FORMATTER.format(issue.getCreateDate()) + "</" + TAG_CREATE_DATE + ">\n");
238         buf.append("  <" + TAG_LAST_MODIFIED + ">" + DATE_FORMATTER.format(issue.getLastModifiedDate()) + "</" + TAG_LAST_MODIFIED + ">\n");
239         buf.append("  <" + TAG_CREATOR + ">" + TAG_USER + issue.getCreator().getId() + "</" + TAG_CREATOR + ">\n");
240         if (issue.getOwner() != null) {
241             buf.append("  <" + TAG_OWNER + ">" + TAG_USER + issue.getOwner().getId() + "</" + TAG_OWNER + ">\n");
242         }
243         if (issue.getComponents().size() > 0) {
244             buf.append("  <" + TAG_ISSUE_COMPONENTS + ">\n");
245             for (int i = 0; i < issue.getComponents().size(); i++) {
246                 if (issue.getComponents().get(i) != null) {
247                     buf.append("    <" + TAG_COMPONENT_ID + ">" + TAG_COMPONENT + issue.getComponents().get(i).getId() + "</" + TAG_COMPONENT_ID + ">\n");
248                 }
249             }
250             buf.append("  </" + TAG_ISSUE_COMPONENTS + ">\n");
251         }
252         if (issue.getVersions().size() > 0) {
253             buf.append("  <" + TAG_ISSUE_VERSIONS + ">\n");
254             for (int i = 0; i < issue.getVersions().size(); i++) {
255                 if (issue.getVersions().get(i) != null) {
256                     buf.append("    <" + TAG_VERSION_ID + ">" + TAG_VERSION + issue.getVersions().get(i).getId() + "</" + TAG_VERSION_ID + ">\n");
257                 }
258             }
259             buf.append("  </" + TAG_ISSUE_VERSIONS + ">\n");
260         }
261         if (issue.getFields().size() > 0) {
262             buf.append("  <" + TAG_ISSUE_FIELDS + ">\n");
263             for (int i = 0; i < issue.getFields().size(); i++) {
264                 if (issue.getFields().get(i) != null) {
265                     buf.append("    <" + TAG_ISSUE_FIELD + " " + ATTR_ID + "=\"" + TAG_CUSTOM_FIELD + issue.getFields().get(i).getCustomField().getId() + "\"><![CDATA[" + issue.getFields().get(i).getValue(EXPORT_LOCALE) + "]]></" + TAG_ISSUE_FIELD + ">\n");
266                 }
267             }
268             buf.append("  </" + TAG_ISSUE_FIELDS + ">\n");
269         }
270         if (issue.getAttachments().size() > 0) {
271             buf.append("  <" + TAG_ISSUE_ATTACHMENTS + ">\n");
272             for (int i = 0; i < issue.getAttachments().size(); i++) {
273                 if (issue.getAttachments().get(i) != null) {
274                     buf.append("    <" + TAG_ISSUE_ATTACHMENT + ">");
275                     buf.append("      <" + TAG_ISSUE_ATTACHMENT_DESCRIPTION + "><![CDATA[" + ITrackerResources.escapeUnicodeString(issue.getAttachments().get(i).getDescription(), false) + "]]></" + TAG_ISSUE_ATTACHMENT_DESCRIPTION + ">\n");
276                     buf.append("      <" + TAG_ISSUE_ATTACHMENT_FILENAME + "><![CDATA[" + ITrackerResources.escapeUnicodeString(issue.getAttachments().get(i).getFileName(), false) + "]]></" + TAG_ISSUE_ATTACHMENT_FILENAME + ">\n");
277                     buf.append("      <" + TAG_ISSUE_ATTACHMENT_ORIGFILE + "><![CDATA[" + ITrackerResources.escapeUnicodeString(issue.getAttachments().get(i).getOriginalFileName(), false) + "]]></" + TAG_ISSUE_ATTACHMENT_ORIGFILE + ">\n");
278                     buf.append("      <" + TAG_ISSUE_ATTACHMENT_SIZE + "><![CDATA[" + issue.getAttachments().get(i).getSize() + "]]></" + TAG_ISSUE_ATTACHMENT_SIZE + ">\n");
279                     buf.append("      <" + TAG_ISSUE_ATTACHMENT_TYPE + "><![CDATA[" + issue.getAttachments().get(i).getType() + "]]></" + TAG_ISSUE_ATTACHMENT_TYPE + ">\n");
280                     buf.append("      <" + TAG_ISSUE_ATTACHMENT_CREATOR + "><![CDATA[" + TAG_USER + issue.getAttachments().get(i).getUser().getId() + "]]></" + TAG_ISSUE_ATTACHMENT_CREATOR + ">\n");
281                     buf.append("    </" + TAG_ISSUE_ATTACHMENT + ">\n");
282                 }
283             }
284             buf.append("  </" + TAG_ISSUE_ATTACHMENTS + ">\n");
285         }
286         if (issue.getHistory().size() > 0) {
287             buf.append("  <" + TAG_ISSUE_HISTORY + ">\n");
288             for (int i = 0; i < issue.getHistory().size(); i++) {
289                 if (issue.getHistory().get(i) != null) {
290                     buf.append("    <" + TAG_HISTORY_ENTRY + " ");
291                     buf.append(ATTR_CREATOR_ID + "=\"" + TAG_USER + issue.getHistory().get(i).getUser().getId() + "\" ");
292                     buf.append(ATTR_DATE + "=\"" + DATE_FORMATTER.format(issue.getHistory().get(i).getCreateDate()) + "\" ");
293                     buf.append(ATTR_STATUS + "=\"" + issue.getHistory().get(i).getStatus() + "\">");
294                     buf.append("<![CDATA[" + ITrackerResources.escapeUnicodeString(issue.getHistory().get(i).getDescription(), false) + "]]>");
295                     buf.append("</" + TAG_HISTORY_ENTRY + ">\n");
296                 }
297             }
298             buf.append("  </" + TAG_ISSUE_HISTORY + ">\n");
299         }
300         buf.append("</" + TAG_ISSUE + ">\n");
301 
302         return buf.toString();
303     }
304 
305     /**
306      * Generates an XML block that encapsulates a project for import or export.  This
307      * function will not generate the XML for other models needed for a complete import
308      * or export.
309      *
310      * @param project a Project to generate the XML for
311      * @return a String containing the XML for the project
312      */
313     public static String getProjectXML(Project project) {
314         if (project == null) {
315             return "";
316         }
317 
318         StringBuffer buf = new StringBuffer();
319 
320         buf.append("<" + TAG_PROJECT + " " + ATTR_ID + "=\"" + TAG_PROJECT + project.getId() + "\" " + ATTR_SYSTEMID + "=\"" + project.getId() + "\">\n");
321         buf.append("  <" + TAG_PROJECT_NAME + "><![CDATA[" + ITrackerResources.escapeUnicodeString(project.getName(), false) + "]]></" + TAG_PROJECT_NAME + ">\n");
322         buf.append("  <" + TAG_PROJECT_DESCRIPTION + "><![CDATA[" + ITrackerResources.escapeUnicodeString(project.getDescription(), false) + "]]></" + TAG_PROJECT_DESCRIPTION + ">\n");
323         buf.append("  <" + TAG_PROJECT_STATUS + ">" + ProjectUtilities.getStatusName(project.getStatus(), EXPORT_LOCALE) + "</" + TAG_PROJECT_STATUS + ">\n");
324         buf.append("  <" + TAG_PROJECT_OPTIONS + ">" + project.getOptions() + "</" + TAG_PROJECT_OPTIONS + ">\n");
325 
326         if (project.getCustomFields().size() > 0) {
327             buf.append("  <" + TAG_PROJECT_FIELDS + ">\n");
328             for (int i = 0; i < project.getCustomFields().size(); i++) {
329                 if (project.getCustomFields().get(i) != null) {
330                     buf.append("    <" + TAG_PROJECT_FIELD_ID + ">" + TAG_CUSTOM_FIELD + project.getCustomFields().get(i).getId() + "</" + TAG_PROJECT_FIELD_ID + ">\n");
331                 }
332             }
333             buf.append("  </" + TAG_PROJECT_FIELDS + ">\n");
334         }
335         if (project.getOwners().size() > 0) {
336             buf.append("  <" + TAG_PROJECT_OWNERS + ">\n");
337             for (int i = 0; i < project.getOwners().size(); i++) {
338                 if (project.getOwners().get(i) != null) {
339                     buf.append("    <" + TAG_PROJECT_OWNER_ID + ">" + TAG_USER + project.getOwners().get(i).getId() + "</" + TAG_PROJECT_OWNER_ID + ">\n");
340                 }
341             }
342             buf.append("  </" + TAG_PROJECT_OWNERS + ">\n");
343         }
344         if (project.getComponents().size() > 0) {
345             buf.append("  <" + TAG_COMPONENTS + ">\n");
346             for (int i = 0; i < project.getComponents().size(); i++) {
347                 if (project.getComponents().get(i) != null) {
348                     buf.append("    <" + TAG_COMPONENT + " " + ATTR_ID + "=\"" + TAG_COMPONENT + project.getComponents().get(i).getId() + "\" " + ATTR_SYSTEMID + "=\"" + project.getComponents().get(i).getId() + "\">\n");
349                     buf.append("      <" + TAG_COMPONENT_NAME + "><![CDATA[" + ITrackerResources.escapeUnicodeString(project.getComponents().get(i).getName(), false) + "]]></" + TAG_COMPONENT_NAME + ">\n");
350                     buf.append("      <" + TAG_COMPONENT_DESCRIPTION + "><![CDATA[" + ITrackerResources.escapeUnicodeString(project.getComponents().get(i).getDescription(), false) + "]]></" + TAG_COMPONENT_DESCRIPTION + ">\n");
351                     buf.append("    </" + TAG_COMPONENT + ">\n");
352                 }
353             }
354             buf.append("  </" + TAG_COMPONENTS + ">\n");
355         }
356         if (project.getVersions().size() > 0) {
357             buf.append("  <" + TAG_VERSIONS + ">\n");
358             for (int i = 0; i < project.getVersions().size(); i++) {
359                 if (project.getVersions().get(i) != null) {
360                     buf.append("    <" + TAG_VERSION + " " + ATTR_ID + "=\"" + TAG_VERSION + project.getVersions().get(i).getId() + "\" " + ATTR_SYSTEMID + "=\"" + project.getVersions().get(i).getId() + "\">\n");
361                     buf.append("      <" + TAG_VERSION_NUMBER + "><![CDATA[" + ITrackerResources.escapeUnicodeString(project.getVersions().get(i).getNumber(), false) + "]]></" + TAG_VERSION_NUMBER + ">\n");
362                     buf.append("      <" + TAG_VERSION_DESCRIPTION + "><![CDATA[" + ITrackerResources.escapeUnicodeString(project.getVersions().get(i).getDescription(), false) + "]]></" + TAG_VERSION_DESCRIPTION + ">\n");
363                     buf.append("    </" + TAG_VERSION + ">\n");
364                 }
365             }
366             buf.append("  </" + TAG_VERSIONS + ">\n");
367         }
368         buf.append("</" + TAG_PROJECT + ">\n");
369 
370         return buf.toString();
371     }
372 
373     /**
374      * Generates an XML block that encapsulates a user for import or export.  This
375      * function will not generate the XML for other models needed for a complete import
376      * or export.
377      *
378      * @param user a User to generate the XML for
379      * @return a String containing the XML for the user
380      */
381     public static String getUserXML(User user) {
382         if (user == null) {
383             return "";
384         }
385 
386         StringBuffer buf = new StringBuffer();
387 
388         buf.append("<" + TAG_USER + " " + ATTR_ID + "=\"" + TAG_USER + user.getId() + "\" " + ATTR_SYSTEMID + "=\"" + user.getId() + "\">\n");
389         buf.append("  <" + TAG_LOGIN + "><![CDATA[" + ITrackerResources.escapeUnicodeString(user.getLogin(), false) + "]]></" + TAG_LOGIN + ">\n");
390         buf.append("  <" + TAG_FIRST_NAME + "><![CDATA[" + ITrackerResources.escapeUnicodeString(user.getFirstName(), false) + "]]></" + TAG_FIRST_NAME + ">\n");
391         buf.append("  <" + TAG_LAST_NAME + "><![CDATA[" + ITrackerResources.escapeUnicodeString(user.getLastName(), false) + "]]></" + TAG_LAST_NAME + ">\n");
392         buf.append("  <" + TAG_EMAIL + "><![CDATA[" + ITrackerResources.escapeUnicodeString(user.getEmail(), false) + "]]></" + TAG_EMAIL + ">\n");
393         buf.append("  <" + TAG_USER_STATUS + ">" + UserUtilities.getStatusName(user.getStatus(), EXPORT_LOCALE) + "</" + TAG_USER_STATUS + ">\n");
394         buf.append("  <" + TAG_SUPER_USER + ">" + user.isSuperUser() + "</" + TAG_SUPER_USER + ">\n");
395         buf.append("</" + TAG_USER + ">\n");
396 
397         return buf.toString();
398     }
399 
400     /**
401      * Generates an XML block that encapsulates the system configuration for import or export.
402      * This function will not generate the XML for other models needed for a complete import
403      * or export.
404      *
405      * @param user a SystemConfiguration to generate the XML for
406      * @return a String containing the XML for the configuration
407      */
408     public static String getConfigurationXML(SystemConfiguration config) {
409         if (config == null) {
410             return "";
411         }
412 
413         StringBuffer buf = new StringBuffer();
414         buf.append("  <" + TAG_CONFIGURATION_VERSION + "><![CDATA[" + config.getVersion() + "]]></" + TAG_CONFIGURATION_VERSION + ">\n");
415         buf.append("  <" + TAG_CUSTOM_FIELDS + ">\n");
416         for (int i = 0; i < config.getCustomFields().size(); i++) {
417             buf.append("    <" + TAG_CUSTOM_FIELD + " " + ATTR_ID + "=\"" + TAG_CUSTOM_FIELD + config.getCustomFields().get(i).getId() + "\" " + ATTR_SYSTEMID + "=\"" + config.getCustomFields().get(i).getId() + "\">\n");
418             buf.append("      <" + TAG_CUSTOM_FIELD_LABEL + "><![CDATA[" + ITrackerResources.escapeUnicodeString(CustomFieldUtilities.getCustomFieldName(config.getCustomFields().get(i).getId()), false) + "]]></" + TAG_CUSTOM_FIELD_LABEL + ">\n");
419             buf.append("      <" + TAG_CUSTOM_FIELD_TYPE + "><![CDATA[" + config.getCustomFields().get(i).getFieldType() + "]]></" + TAG_CUSTOM_FIELD_TYPE + ">\n");
420             buf.append("      <" + TAG_CUSTOM_FIELD_REQUIRED + "><![CDATA[" + config.getCustomFields().get(i).isRequired() + "]]></" + TAG_CUSTOM_FIELD_REQUIRED + ">\n");
421             buf.append("      <" + TAG_CUSTOM_FIELD_DATEFORMAT + "><![CDATA[" + ITrackerResources.escapeUnicodeString(config.getCustomFields().get(i).getDateFormat(), false) + "]]></" + TAG_CUSTOM_FIELD_DATEFORMAT + ">\n");
422             buf.append("      <" + TAG_CUSTOM_FIELD_SORTOPTIONS + "><![CDATA[" + config.getCustomFields().get(i).isSortOptionsByName() + "]]></" + TAG_CUSTOM_FIELD_SORTOPTIONS + ">\n");
423             if (config.getCustomFields().get(i).getFieldType() == CustomField.Type.LIST) {
424                 List<CustomFieldValue> options = config.getCustomFields().get(i).getOptions();
425                 for (int j = 0; j < options.size(); j++) {
426                     buf.append("      <" + TAG_CUSTOM_FIELD_OPTION + " " + ATTR_VALUE + "=\"" + ITrackerResources.escapeUnicodeString(options.get(j).getValue(), false) + "\"><![CDATA[" + ITrackerResources.escapeUnicodeString(CustomFieldUtilities.getCustomFieldOptionName(options.get(j), null), false) + "]]></" + TAG_CUSTOM_FIELD_OPTION + ">\n");
427                 }
428             }
429             buf.append("    </" + TAG_CUSTOM_FIELD + ">\n");
430         }
431         buf.append("  </" + TAG_CUSTOM_FIELDS + ">\n");
432         buf.append("  <" + TAG_RESOLUTIONS + ">\n");
433         for (int i = 0; i < config.getResolutions().size(); i++) {
434             buf.append("  <" + TAG_RESOLUTION + " " + ATTR_VALUE + "=\"" + config.getResolutions().get(i).getValue() + "\" " + ATTR_ORDER + "=\"" + config.getResolutions().get(i).getOrder() + "\">");
435             buf.append("<![CDATA[" + ITrackerResources.escapeUnicodeString(config.getResolutions().get(i).getName(), false) + "]]>");
436             buf.append("</" + TAG_RESOLUTION + ">\n");
437         }
438         buf.append("  </" + TAG_RESOLUTIONS + ">\n");
439         buf.append("  <" + TAG_SEVERITIES + ">\n");
440         for (int i = 0; i < config.getSeverities().size(); i++) {
441             buf.append("  <" + TAG_SEVERITY + " " + ATTR_VALUE + "=\"" + config.getSeverities().get(i).getValue() + "\" " + ATTR_ORDER + "=\"" + config.getSeverities().get(i).getOrder() + "\">");
442             buf.append("<![CDATA[" + ITrackerResources.escapeUnicodeString(config.getSeverities().get(i).getName(), false) + "]]>");
443             buf.append("</" + TAG_SEVERITY + ">\n");
444         }
445         buf.append("  </" + TAG_SEVERITIES + ">\n");
446         buf.append("  <" + TAG_STATUSES + ">\n");
447         for (int i = 0; i < config.getStatuses().size(); i++) {
448             buf.append("  <" + TAG_STATUS + " " + ATTR_VALUE + "=\"" + config.getStatuses().get(i).getValue() + "\" " + ATTR_ORDER + "=\"" + config.getStatuses().get(i).getOrder() + "\">");
449             buf.append("<![CDATA[" + ITrackerResources.escapeUnicodeString(config.getStatuses().get(i).getName(), false) + "]]>");
450             buf.append("</" + TAG_STATUS + ">\n");
451         }
452         buf.append("  </" + TAG_STATUSES + ">\n");
453 
454         return buf.toString();
455     }
456 }