View Javadoc

1   package org.apache.maven.continuum.xmlrpc.backup;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *   http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import com.sampullara.cli.Args;
23  import com.sampullara.cli.Argument;
24  import org.apache.continuum.xmlrpc.release.ContinuumReleaseResult;
25  import org.apache.continuum.xmlrpc.repository.DirectoryPurgeConfiguration;
26  import org.apache.continuum.xmlrpc.repository.LocalRepository;
27  import org.apache.continuum.xmlrpc.repository.RepositoryPurgeConfiguration;
28  import org.apache.log4j.BasicConfigurator;
29  import org.apache.log4j.Level;
30  import org.apache.log4j.Logger;
31  import org.apache.maven.continuum.xmlrpc.client.ContinuumXmlRpcClient;
32  import org.apache.maven.continuum.xmlrpc.project.BuildDefinition;
33  import org.apache.maven.continuum.xmlrpc.project.BuildDefinitionTemplate;
34  import org.apache.maven.continuum.xmlrpc.project.BuildResult;
35  import org.apache.maven.continuum.xmlrpc.project.BuildResultSummary;
36  import org.apache.maven.continuum.xmlrpc.project.Project;
37  import org.apache.maven.continuum.xmlrpc.project.ProjectDependency;
38  import org.apache.maven.continuum.xmlrpc.project.ProjectDeveloper;
39  import org.apache.maven.continuum.xmlrpc.project.ProjectGroup;
40  import org.apache.maven.continuum.xmlrpc.project.ProjectNotifier;
41  import org.apache.maven.continuum.xmlrpc.project.ProjectSummary;
42  import org.apache.maven.continuum.xmlrpc.project.Schedule;
43  import org.apache.maven.continuum.xmlrpc.scm.ChangeFile;
44  import org.apache.maven.continuum.xmlrpc.scm.ChangeSet;
45  import org.apache.maven.continuum.xmlrpc.scm.ScmResult;
46  import org.apache.maven.continuum.xmlrpc.system.Installation;
47  import org.apache.maven.continuum.xmlrpc.system.Profile;
48  
49  import java.io.File;
50  import java.io.FileWriter;
51  import java.io.IOException;
52  import java.io.PrintWriter;
53  import java.lang.reflect.Field;
54  import java.net.URL;
55  import java.util.ArrayList;
56  import java.util.Arrays;
57  import java.util.List;
58  import java.util.Map;
59  import java.util.Properties;
60  import java.util.Set;
61  
62  public class Backup
63  {
64      private static final Logger LOGGER = Logger.getLogger( Backup.class );
65  
66      private static ContinuumXmlRpcClient client;
67  
68      private static int indent = 0;
69  
70      private static PrintWriter writer;
71  
72      public static void main( String[] args )
73          throws Exception
74      {
75          Commands command = new Commands();
76  
77          try
78          {
79              Args.parse( command, args );
80  
81              if ( command.help )
82              {
83                  Args.usage( command );
84                  return;
85              }
86              if ( command.version )
87              {
88                  System.out.println( "continuum-xmlrpc-backup version: " + getVersion() );
89                  return;
90              }
91              if ( command.url == null )
92              {
93                  System.out.println( "You must specified the Continuum XMLRPC URL" );
94                  Args.usage( command );
95                  return;
96              }
97              if ( command.username == null )
98              {
99                  System.out.println( "You must specified the Continuum username" );
100                 Args.usage( command );
101                 return;
102             }
103             if ( command.password == null )
104             {
105                 System.out.println( "You must specified the Continuum password" );
106                 Args.usage( command );
107                 return;
108             }
109         }
110         catch ( IllegalArgumentException e )
111         {
112             System.err.println( e.getMessage() );
113             Args.usage( command );
114             return;
115         }
116 
117         BasicConfigurator.configure();
118         if ( command.debug )
119         {
120             Logger.getRootLogger().setLevel( Level.DEBUG );
121         }
122         else
123         {
124             Logger.getRootLogger().setLevel( Level.INFO );
125         }
126 
127         LOGGER.info( "Connection to " + command.url + "with username '" + command.username + "'..." );
128         client = new ContinuumXmlRpcClient( command.url, command.username, command.password );
129         LOGGER.info( "connected" );
130 
131         File out = command.outputFile;
132         if ( out == null )
133         {
134             out = new File( "backup/builds.xml" );
135         }
136         out.getParentFile().mkdirs();
137 
138         if ( !command.overwrite && out.exists() )
139         {
140             System.err.println( out.getAbsolutePath() +
141                                     " already exists and will not be overwritten unless the -overwrite flag is used." );
142             Args.usage( command );
143             return;
144         }
145 
146         writer = new PrintWriter( new FileWriter( out ) );
147 
148         writer.println( "<?xml version='1.0' encoding='UTF-8'?>" );
149         startTag( "continuumDatabase", true );
150         backupSystemConfiguration();
151         backupAllSchedules();
152         backupAllInstallations();
153         backupAllProfiles();
154         backupAllBuildDefinitionTemplates();
155         backupAllProjectGroup();
156         backupAllLocalRepositories();
157         backupAllRepositoryPurgeConfigurations();
158         backupAllDirectoryPurgeConfigurations();
159         endTag( "continuumDatabase", true );
160         writer.close();
161         LOGGER.info( "Done." );
162     }
163 
164     private static String getVersion()
165         throws IOException
166     {
167         Properties properties = new Properties();
168         properties.load( Backup.class.getResourceAsStream(
169             "/META-INF/maven/org.apache.maven.continuum/continuum-xmlrpc-backup/pom.properties" ) );
170         return properties.getProperty( "version" );
171     }
172 
173     private static class Commands
174     {
175 
176         @Argument( description = "Display help information", value = "help", alias = "h" )
177         private boolean help;
178 
179         @Argument( description = "Display version information", value = "version", alias = "v" )
180         private boolean version;
181 
182         @Argument( description = "Continuum XMLRPC URL", value = "url" )
183         private URL url;
184 
185         @Argument( description = "Username", value = "username", alias = "u" )
186         private String username;
187 
188         @Argument( description = "Password", value = "password", alias = "p" )
189         private String password;
190 
191         @Argument( description = "Backup file", value = "outputFile", alias = "o" )
192         private File outputFile;
193 
194         @Argument(
195             description = "Whether to overwrite the designated backup file if it already exists in export mode. Default is false.",
196             value = "overwrite" )
197         private boolean overwrite;
198 
199         @Argument(
200             description = "Turn on debugging information. Default is off.",
201             value = "debug" )
202         private boolean debug;
203     }
204 
205     private static void backupSystemConfiguration()
206         throws Exception
207     {
208         LOGGER.info( "Backup system configuration" );
209         writeObject( client.getSystemConfiguration(), "systemConfiguration", true );
210     }
211 
212     private static void backupAllSchedules()
213         throws Exception
214     {
215         LOGGER.info( "Backup schedules" );
216         List<Schedule> schedules = client.getSchedules();
217         if ( schedules != null && !schedules.isEmpty() )
218         {
219             startTag( "schedules", true );
220             for ( Schedule schedule : schedules )
221             {
222                 LOGGER.debug( "Backup schedule " + schedule.getName() );
223                 writeObject( schedule, "schedule", true );
224             }
225             endTag( "schedules", true );
226         }
227     }
228 
229     private static void backupAllInstallations()
230         throws Exception
231     {
232         LOGGER.info( "Backup installations" );
233         List<Installation> installs = client.getInstallations();
234         if ( installs != null && !installs.isEmpty() )
235         {
236             startTag( "installations", true );
237             for ( Installation install : installs )
238             {
239                 LOGGER.debug( "Backup installation " + install.getName() );
240                 writeObject( install, "installation", true );
241             }
242             endTag( "installations", true );
243         }
244     }
245 
246     private static void backupAllBuildDefinitionTemplates()
247         throws Exception
248     {
249         LOGGER.info( "Backup Build Definitions Templates" );
250         List<BuildDefinitionTemplate> bdts = client.getBuildDefinitionTemplates();
251         if ( bdts != null && !bdts.isEmpty() )
252         {
253             startTag( "buildDefinitionTemplates", true );
254             for ( BuildDefinitionTemplate bdt : bdts )
255             {
256                 LOGGER.debug( "Backup build definition template " + bdt.getName() );
257                 startTag( "buildDefinitionTemplate", true );
258                 writeSimpleFields( bdt );
259 
260                 List<BuildDefinition> bds = bdt.getBuildDefinitions();
261                 if ( bds != null && !bds.isEmpty() )
262                 {
263                     for ( BuildDefinition bd : bds )
264                     {
265                         backupBuildDefinition( bd );
266                     }
267                 }
268                 endTag( "buildDefinitionTemplate", true );
269             }
270             endTag( "buildDefinitionTemplates", true );
271         }
272     }
273 
274     private static void backupAllProfiles()
275         throws Exception
276     {
277         LOGGER.info( "Backup profiles" );
278         List<Profile> profiles = client.getProfiles();
279         if ( profiles != null && !profiles.isEmpty() )
280         {
281             startTag( "profiles", true );
282             for ( Profile p : profiles )
283             {
284                 LOGGER.debug( "Backup profile " + p.getName() );
285                 writeProfile( p );
286             }
287             endTag( "profiles", true );
288         }
289     }
290 
291     private static void backupAllProjectGroup()
292         throws Exception
293     {
294         LOGGER.info( "Backup project groups" );
295         List<ProjectGroup> pgs = client.getAllProjectGroupsWithAllDetails();
296         if ( pgs != null && !pgs.isEmpty() )
297         {
298             startTag( "projectGroups", true );
299             for ( ProjectGroup pg : pgs )
300             {
301                 backupProjectGroup( pg );
302             }
303             endTag( "projectGroups", true );
304         }
305     }
306 
307     private static void backupProjectGroup( ProjectGroup pg )
308         throws Exception
309     {
310         if ( pg == null )
311         {
312             return;
313         }
314 
315         LOGGER.debug( "Backup project group " + pg.getName() );
316         startTag( "projectGroup", true );
317         writeSimpleFields( pg );
318 
319         if ( pg.getProjects() != null && !pg.getProjects().isEmpty() )
320         {
321             startTag( "projects", true );
322             for ( ProjectSummary ps : (List<ProjectSummary>) pg.getProjects() )
323             {
324                 backupProject( ps );
325             }
326             endTag( "projects", true );
327         }
328 
329         if ( pg.getBuildDefinitions() != null && !pg.getBuildDefinitions().isEmpty() )
330         {
331             startTag( "buildDefinitions", true );
332             for ( BuildDefinition bd : (List<BuildDefinition>) pg.getBuildDefinitions() )
333             {
334                 backupBuildDefinition( bd );
335             }
336             endTag( "buildDefinitions", true );
337         }
338 
339         if ( pg.getNotifiers() != null && !pg.getNotifiers().isEmpty() )
340         {
341             startTag( "notifiers", true );
342             for ( ProjectNotifier notif : (List<ProjectNotifier>) pg.getNotifiers() )
343             {
344                 backupNotifier( notif );
345             }
346             endTag( "notifiers", true );
347         }
348 
349         backupContinuumReleaseResultsForProjectGroup( pg.getId() );
350         endTag( "projectGroup", true );
351     }
352 
353     private static void backupProject( ProjectSummary ps )
354         throws Exception
355     {
356         if ( ps == null )
357         {
358             return;
359         }
360 
361         LOGGER.debug( "Backup project " + ps.getName() );
362 
363         Project p = client.getProjectWithAllDetails( ps.getId() );
364         startTag( "project", true );
365         writeSimpleFields( p );
366 
367         if ( p.getProjectGroup() != null )
368         {
369             writeTagWithParameter( "projectGroup", "id", String.valueOf( p.getProjectGroup().getId() ) );
370         }
371 
372         if ( p.getDevelopers() != null && !p.getDevelopers().isEmpty() )
373         {
374             startTag( "developers", true );
375             for ( ProjectDeveloper pd : (List<ProjectDeveloper>) p.getDevelopers() )
376             {
377                 writeObject( pd, "developer", true );
378             }
379             endTag( "developers", true );
380         }
381 
382         if ( p.getDependencies() != null && !p.getDependencies().isEmpty() )
383         {
384             startTag( "dependencies", true );
385             for ( ProjectDependency pd : (List<ProjectDependency>) p.getDependencies() )
386             {
387                 writeObject( pd, "dependency", true );
388             }
389             endTag( "dependencies", true );
390         }
391 
392         if ( p.getBuildDefinitions() != null && !p.getBuildDefinitions().isEmpty() )
393         {
394             startTag( "buildDefinitions", true );
395             for ( BuildDefinition bd : (List<BuildDefinition>) p.getBuildDefinitions() )
396             {
397                 backupBuildDefinition( bd );
398             }
399             endTag( "buildDefinitions", true );
400         }
401 
402         if ( p.getNotifiers() != null && !p.getNotifiers().isEmpty() )
403         {
404             startTag( "notifiers", true );
405             for ( ProjectNotifier notif : (List<ProjectNotifier>) p.getNotifiers() )
406             {
407                 backupNotifier( notif );
408             }
409             endTag( "notifiers", true );
410         }
411 
412         List<BuildResultSummary> brs = client.getBuildResultsForProject( p.getId() );
413         if ( brs != null && !brs.isEmpty() )
414         {
415             startTag( "buildResults", true );
416             for ( BuildResultSummary brSummary : brs )
417             {
418                 BuildResult br = client.getBuildResult( p.getId(), brSummary.getId() );
419                 backupBuildResult( br );
420             }
421             endTag( "buildResults", true );
422         }
423         endTag( "project", true );
424     }
425 
426     private static void backupBuildResult( BuildResult br )
427         throws Exception
428     {
429         if ( br == null )
430         {
431             return;
432         }
433 
434         startTag( "buildResult", true );
435         writeSimpleFields( br );
436 
437         if ( br.getProject() != null )
438         {
439             writeTagWithParameter( "project", "id", String.valueOf( br.getProject().getId() ) );
440         }
441 
442         if ( br.getBuildDefinition() != null )
443         {
444             writeTagWithParameter( "buildDefinition", "id", String.valueOf( br.getBuildDefinition().getId() ) );
445         }
446 
447         if ( br.getModifiedDependencies() != null && !br.getModifiedDependencies().isEmpty() )
448         {
449             startTag( "dependencies", true );
450             for ( ProjectDependency pd : (List<ProjectDependency>) br.getModifiedDependencies() )
451             {
452                 writeObject( pd, "dependency", true );
453             }
454             endTag( "dependencies", true );
455         }
456         endTag( "buildResult", true );
457     }
458 
459     private static void writeSimpleFields( Object obj )
460         throws Exception
461     {
462         if ( obj == null )
463         {
464             return;
465         }
466 
467         for ( Field f : getFieldsIncludingSuperclasses( obj.getClass() ) )
468         {
469             if ( "modelEncoding".equals( f.getName() ) )
470             {
471                 continue;
472             }
473 
474             if ( !f.isAccessible() )
475             {
476                 f.setAccessible( true );
477             }
478 
479             if ( f.getType().getName().equals( "int" ) || f.getType().getName().equals( "long" ) ||
480                 f.getType().getName().equals( "boolean" ) || f.getType().getName().equals( "java.lang.String" ) )
481             {
482                 Object value = f.get( obj );
483                 if ( value != null )
484                 {
485                     startTag( f.getName(), false );
486                     writer.print( value );
487                     endTag( f.getName(), false );
488                 }
489             }
490             else if ( ScmResult.class.getName().equals( f.getType().getName() ) )
491             {
492                 writeScmResult( (ScmResult) f.get( obj ) );
493             }
494             else if ( ChangeFile.class.getName().equals( f.getType().getName() ) )
495             {
496                 writeObject( f.get( obj ), "changeFile", true );
497             }
498             else if ( Profile.class.getName().equals( f.getType().getName() ) )
499             {
500                 writeProfile( (Profile) f.get( obj ) );
501             }
502             else
503             {
504                 //LOGGER.debug(
505                 //    "Rejected: (" + f.getName() + ") " + f.getType() + " in object " + obj.getClass().getName() );
506             }
507         }
508     }
509 
510     private static void writeObject( Object obj, String tagName, boolean addNewLine )
511         throws Exception
512     {
513         if ( obj == null )
514         {
515             return;
516         }
517         startTag( tagName, addNewLine );
518         writeSimpleFields( obj );
519         endTag( tagName, addNewLine );
520     }
521 
522     private static void backupBuildDefinition( BuildDefinition buildDef )
523         throws Exception
524     {
525         if ( buildDef == null )
526         {
527             return;
528         }
529         startTag( "buildDefinition", true );
530         writeSimpleFields( buildDef );
531         if ( buildDef.getSchedule() != null )
532         {
533             writeTagWithParameter( "schedule", "id", String.valueOf( buildDef.getSchedule().getId() ) );
534         }
535         endTag( "buildDefinition", true );
536     }
537 
538     private static void backupNotifier( ProjectNotifier notifier )
539         throws Exception
540     {
541         startTag( "notifier", true );
542         writeSimpleFields( notifier );
543 
544         Map conf = notifier.getConfiguration();
545         startTag( "configuration", true );
546         for ( String key : (Set<String>) conf.keySet() )
547         {
548             startTag( key, false );
549             writer.print( conf.get( key ) );
550             endTag( key, false );
551         }
552         endTag( "configuration", true );
553 
554         endTag( "notifier", true );
555     }
556 
557     private static void writeProfile( Profile profile )
558         throws Exception
559     {
560         if ( profile == null )
561         {
562             return;
563         }
564 
565         startTag( "profile", true );
566         writeSimpleFields( profile );
567 
568         if ( profile.getEnvironmentVariables() != null && !profile.getEnvironmentVariables().isEmpty() )
569         {
570             startTag( "environmentVariables", true );
571             for ( Installation env : (List<Installation>) profile.getEnvironmentVariables() )
572             {
573                 writeTagWithParameter( "environmentVariable", "installationId", String.valueOf(
574                     env.getInstallationId() ) );
575             }
576             endTag( "environmentVariables", true );
577         }
578 
579         if ( profile.getJdk() != null )
580         {
581             writeTagWithParameter( "jdk", "installationId", String.valueOf( profile.getJdk().getInstallationId() ) );
582         }
583 
584         if ( profile.getBuilder() != null )
585         {
586             writeTagWithParameter( "builder", "installationId", String.valueOf(
587                 profile.getBuilder().getInstallationId() ) );
588         }
589 
590         endTag( "profile", true );
591     }
592 
593     private static void writeScmResult( ScmResult scmResult )
594         throws Exception
595     {
596         if ( scmResult == null )
597         {
598             return;
599         }
600 
601         startTag( "scmResult", true );
602         writeSimpleFields( scmResult );
603 
604         if ( scmResult.getChanges() != null && !scmResult.getChanges().isEmpty() )
605         {
606             startTag( "changeSets", true );
607             for ( ChangeSet cs : (List<ChangeSet>) scmResult.getChanges() )
608             {
609                 writeObject( cs, "changeSet", true );
610             }
611             endTag( "changeSets", true );
612         }
613         endTag( "scmResult", true );
614     }
615 
616     private static void startTag( String tagName, boolean addNewLineAfter )
617     {
618         writer.print( getIndent() );
619         writer.print( "<" );
620         writer.print( tagName );
621         writer.print( ">" );
622         if ( addNewLineAfter )
623         {
624             writer.println();
625             indent++;
626         }
627     }
628 
629     private static void endTag( String tagName, boolean isOnNewLine )
630     {
631         if ( isOnNewLine )
632         {
633             indent--;
634             writer.print( getIndent() );
635         }
636         writer.print( "</" );
637         writer.print( tagName );
638         writer.println( ">" );
639     }
640 
641     private static void writeTagWithParameter( String tagName, String parameterName, String parameterValue )
642     {
643         writer.print( getIndent() );
644         writer.print( "<" );
645         writer.print( tagName );
646         writer.print( " " );
647         writer.print( parameterName );
648         writer.print( "=\"" );
649         writer.print( parameterValue );
650         writer.print( "\"></" );
651         writer.print( tagName );
652         writer.println( ">" );
653     }
654 
655     private static String getIndent()
656     {
657         String result = "";
658         for ( int i = 0; i < indent; i++ )
659         {
660             result += "  ";
661         }
662         return result;
663     }
664 
665     private static List<Field> getFieldsIncludingSuperclasses( Class clazz )
666     {
667         List<Field> fields = new ArrayList<Field>( Arrays.asList( clazz.getDeclaredFields() ) );
668 
669         Class superclass = clazz.getSuperclass();
670 
671         if ( superclass != null )
672         {
673             fields.addAll( getFieldsIncludingSuperclasses( superclass ) );
674         }
675 
676         return fields;
677     }
678 
679     private static void backupAllLocalRepositories()
680         throws Exception
681     {
682         LOGGER.info( "Backup local repositories" );
683         List<LocalRepository> repos = client.getAllLocalRepositories();
684         if ( repos != null && !repos.isEmpty() )
685         {
686             startTag( "localRepositories", true );
687             for ( LocalRepository repo : repos )
688             {
689                 LOGGER.debug( "Backup local repository " + repo.getName() );
690                 writeObject( repo, "localRepository", true );
691             }
692             endTag( "localRepositories", true );
693         }
694     }
695 
696     private static void backupAllRepositoryPurgeConfigurations()
697         throws Exception
698     {
699         LOGGER.info( "Backup repository purge configurations" );
700         List<RepositoryPurgeConfiguration> purgeConfigs = client.getAllRepositoryPurgeConfigurations();
701         if ( purgeConfigs != null && !purgeConfigs.isEmpty() )
702         {
703             startTag( "repositoryPurgeConfigurations", true );
704             for ( RepositoryPurgeConfiguration purgeConfig : purgeConfigs )
705             {
706                 LOGGER.debug( "Backup repository purge configuration" );
707                 backupRepositoryPurgeConfiguration( purgeConfig );
708             }
709             endTag( "repositoryPurgeConfigurations", true );
710         }
711     }
712 
713     private static void backupRepositoryPurgeConfiguration( RepositoryPurgeConfiguration repoPurge )
714         throws Exception
715     {
716         if ( repoPurge == null )
717         {
718             return;
719         }
720         startTag( "repositoryPurgeConfiguration", true );
721         writeSimpleFields( repoPurge );
722 
723         if ( repoPurge.getRepository() != null )
724         {
725             writeTagWithParameter( "repository", "id", String.valueOf( repoPurge.getRepository().getId() ) );
726         }
727 
728         if ( repoPurge.getSchedule() != null )
729         {
730             writeTagWithParameter( "schedule", "id", String.valueOf( repoPurge.getSchedule().getId() ) );
731         }
732         endTag( "repositoryPurgeConfiguration", true );
733     }
734 
735     private static void backupAllDirectoryPurgeConfigurations()
736         throws Exception
737     {
738         LOGGER.info( "Backup repository purge configurations" );
739         List<DirectoryPurgeConfiguration> purgeConfigs = client.getAllDirectoryPurgeConfigurations();
740         if ( purgeConfigs != null && !purgeConfigs.isEmpty() )
741         {
742             startTag( "directoryPurgeConfigurations", true );
743             for ( DirectoryPurgeConfiguration purgeConfig : purgeConfigs )
744             {
745                 LOGGER.debug( "Backup directory purge configuration" );
746                 backupDirectoryPurgeConfiguration( purgeConfig );
747             }
748             endTag( "directoryPurgeConfigurations", true );
749         }
750     }
751 
752     private static void backupDirectoryPurgeConfiguration( DirectoryPurgeConfiguration dirPurge )
753         throws Exception
754     {
755         if ( dirPurge == null )
756         {
757             return;
758         }
759         startTag( "directoryPurgeConfiguration", true );
760         writeSimpleFields( dirPurge );
761 
762         if ( dirPurge.getSchedule() != null )
763         {
764             writeTagWithParameter( "schedule", "id", String.valueOf( dirPurge.getSchedule().getId() ) );
765         }
766         endTag( "directoryPurgeConfiguration", true );
767     }
768 
769     private static void backupContinuumReleaseResultsForProjectGroup( int projectGroupId )
770         throws Exception
771     {
772         LOGGER.info( "Backup release results" );
773         List<ContinuumReleaseResult> results = client.getReleaseResultsForProjectGroup( projectGroupId );
774         if ( results != null && !results.isEmpty() )
775         {
776             startTag( "continuumReleaseResults", true );
777             for ( ContinuumReleaseResult result : results )
778             {
779                 LOGGER.debug( "Backup release result" );
780                 backupContinuumReleaseResult( result );
781             }
782             endTag( "continuumReleaseResults", true );
783         }
784     }
785 
786     private static void backupContinuumReleaseResult( ContinuumReleaseResult result )
787         throws Exception
788     {
789         if ( result == null )
790         {
791             return;
792         }
793         startTag( "continuumReleaseResult", true );
794         writeSimpleFields( result );
795 
796         if ( result.getProjectGroup() != null )
797         {
798             writeTagWithParameter( "projectGroup", "id", String.valueOf( result.getProjectGroup().getId() ) );
799         }
800         if ( result.getProject() != null )
801         {
802             writeTagWithParameter( "project", "id", String.valueOf( result.getProject().getId() ) );
803         }
804         endTag( "continuumReleaseResult", true );
805     }
806 }