1 /*
2  * Copyright 2010-2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License").
5  * You may not use this file except in compliance with the License.
6  * A copy of the License is located at
7  *
8  *  http://aws.amazon.com/apache2.0
9  *
10  * or in the "license" file accompanying this file. This file is distributed
11  * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12  * express or implied. See the License for the specific language governing
13  * permissions and limitations under the License.
14  */

15 package com.amazonaws.services.s3.model.transform;
16
17 import static com.amazonaws.services.s3.model.transform.BucketConfigurationXmlFactoryFunctions.addParameterIfNotNull;
18 import static com.amazonaws.services.s3.model.transform.BucketConfigurationXmlFactoryFunctions.writePrefix;
19
20 import com.amazonaws.SdkClientException;
21 import com.amazonaws.services.s3.internal.Constants;
22 import com.amazonaws.services.s3.internal.ServiceUtils;
23 import com.amazonaws.services.s3.internal.XmlWriter;
24 import com.amazonaws.services.s3.model.AccessControlTranslation;
25 import com.amazonaws.services.s3.model.BucketAccelerateConfiguration;
26 import com.amazonaws.services.s3.model.BucketCrossOriginConfiguration;
27 import com.amazonaws.services.s3.model.BucketLifecycleConfiguration;
28 import com.amazonaws.services.s3.model.BucketLifecycleConfiguration.NoncurrentVersionTransition;
29 import com.amazonaws.services.s3.model.BucketLifecycleConfiguration.Rule;
30 import com.amazonaws.services.s3.model.BucketLifecycleConfiguration.Transition;
31 import com.amazonaws.services.s3.model.BucketLoggingConfiguration;
32 import com.amazonaws.services.s3.model.BucketNotificationConfiguration;
33 import com.amazonaws.services.s3.model.BucketReplicationConfiguration;
34 import com.amazonaws.services.s3.model.BucketTaggingConfiguration;
35 import com.amazonaws.services.s3.model.BucketVersioningConfiguration;
36 import com.amazonaws.services.s3.model.BucketWebsiteConfiguration;
37 import com.amazonaws.services.s3.model.CORSRule;
38 import com.amazonaws.services.s3.model.CORSRule.AllowedMethods;
39 import com.amazonaws.services.s3.model.CloudFunctionConfiguration;
40 import com.amazonaws.services.s3.model.DeleteMarkerReplication;
41 import com.amazonaws.services.s3.model.ExistingObjectReplication;
42 import com.amazonaws.services.s3.model.Filter;
43 import com.amazonaws.services.s3.model.FilterRule;
44 import com.amazonaws.services.s3.model.LambdaConfiguration;
45 import com.amazonaws.services.s3.model.Metrics;
46 import com.amazonaws.services.s3.model.NotificationConfiguration;
47 import com.amazonaws.services.s3.model.PublicAccessBlockConfiguration;
48 import com.amazonaws.services.s3.model.QueueConfiguration;
49 import com.amazonaws.services.s3.model.RedirectRule;
50 import com.amazonaws.services.s3.model.ReplicationDestinationConfig;
51 import com.amazonaws.services.s3.model.ReplicationRule;
52 import com.amazonaws.services.s3.model.ReplicationTime;
53 import com.amazonaws.services.s3.model.ReplicationTimeValue;
54 import com.amazonaws.services.s3.model.RoutingRule;
55 import com.amazonaws.services.s3.model.RoutingRuleCondition;
56 import com.amazonaws.services.s3.model.S3KeyFilter;
57 import com.amazonaws.services.s3.model.ServerSideEncryptionByDefault;
58 import com.amazonaws.services.s3.model.ServerSideEncryptionConfiguration;
59 import com.amazonaws.services.s3.model.ServerSideEncryptionRule;
60 import com.amazonaws.services.s3.model.SseKmsEncryptedObjects;
61 import com.amazonaws.services.s3.model.Tag;
62 import com.amazonaws.services.s3.model.TagSet;
63 import com.amazonaws.services.s3.model.TopicConfiguration;
64 import com.amazonaws.services.s3.model.analytics.AnalyticsConfiguration;
65 import com.amazonaws.services.s3.model.analytics.AnalyticsExportDestination;
66 import com.amazonaws.services.s3.model.analytics.AnalyticsFilter;
67 import com.amazonaws.services.s3.model.analytics.AnalyticsFilterPredicate;
68 import com.amazonaws.services.s3.model.analytics.AnalyticsS3BucketDestination;
69 import com.amazonaws.services.s3.model.analytics.StorageClassAnalysis;
70 import com.amazonaws.services.s3.model.analytics.StorageClassAnalysisDataExport;
71 import com.amazonaws.services.s3.model.inventory.InventoryConfiguration;
72 import com.amazonaws.services.s3.model.inventory.InventoryDestination;
73 import com.amazonaws.services.s3.model.inventory.InventoryEncryption;
74 import com.amazonaws.services.s3.model.inventory.InventoryFilter;
75 import com.amazonaws.services.s3.model.inventory.InventoryFilterPredicate;
76 import com.amazonaws.services.s3.model.inventory.InventoryPrefixPredicate;
77 import com.amazonaws.services.s3.model.inventory.InventoryS3BucketDestination;
78 import com.amazonaws.services.s3.model.inventory.InventorySchedule;
79 import com.amazonaws.services.s3.model.inventory.ServerSideEncryptionKMS;
80 import com.amazonaws.services.s3.model.inventory.ServerSideEncryptionS3;
81 import com.amazonaws.services.s3.model.lifecycle.LifecycleAndOperator;
82 import com.amazonaws.services.s3.model.lifecycle.LifecycleFilter;
83 import com.amazonaws.services.s3.model.lifecycle.LifecycleFilterPredicate;
84 import com.amazonaws.services.s3.model.lifecycle.LifecyclePredicateVisitor;
85 import com.amazonaws.services.s3.model.lifecycle.LifecyclePrefixPredicate;
86 import com.amazonaws.services.s3.model.lifecycle.LifecycleTagPredicate;
87 import com.amazonaws.services.s3.model.metrics.MetricsAndOperator;
88 import com.amazonaws.services.s3.model.metrics.MetricsConfiguration;
89 import com.amazonaws.services.s3.model.metrics.MetricsFilter;
90 import com.amazonaws.services.s3.model.metrics.MetricsFilterPredicate;
91 import com.amazonaws.services.s3.model.metrics.MetricsPredicateVisitor;
92 import com.amazonaws.services.s3.model.metrics.MetricsPrefixPredicate;
93 import com.amazonaws.services.s3.model.metrics.MetricsTagPredicate;
94 import com.amazonaws.services.s3.model.replication.ReplicationFilter;
95 import com.amazonaws.services.s3.model.replication.ReplicationFilterPredicate;
96 import com.amazonaws.util.CollectionUtils;
97 import java.util.List;
98 import java.util.Map;
99
100 /**
101  * Converts bucket configuration objects into XML byte arrays.
102  */

103 public class BucketConfigurationXmlFactory {
104
105     /**
106      * Converts the specified versioning configuration into an XML byte array.
107      *
108      * @param versioningConfiguration
109      *            The configuration to convert.
110      *
111      * @return The XML byte array representation.
112      */

113     public byte[] convertToXmlByteArray(BucketVersioningConfiguration versioningConfiguration) {
114         XmlWriter xml = new XmlWriter();
115         xml.start("VersioningConfiguration""xmlns", Constants.XML_NAMESPACE);
116         xml.start("Status").value(versioningConfiguration.getStatus()).end();
117
118         Boolean mfaDeleteEnabled = versioningConfiguration.isMfaDeleteEnabled();
119         if (mfaDeleteEnabled != null) {
120             if (mfaDeleteEnabled) {
121                 xml.start("MfaDelete").value("Enabled").end();
122             } else {
123                 xml.start("MfaDelete").value("Disabled").end();
124             }
125         }
126
127         xml.end();
128
129         return xml.getBytes();
130     }
131
132     /**
133      * Converts the specified accelerate configuration into an XML byte array.
134      *
135      * @param accelerateConfiguration
136      *            The configuration to convert.
137      *
138      * @return The XML byte array representation.
139      */

140     public byte[] convertToXmlByteArray(BucketAccelerateConfiguration accelerateConfiguration) {
141         XmlWriter xml = new XmlWriter();
142         xml.start("AccelerateConfiguration""xmlns", Constants.XML_NAMESPACE);
143         xml.start("Status").value(accelerateConfiguration.getStatus()).end();
144         xml.end();
145         return xml.getBytes();
146     }
147
148     /**
149      * Converts the specified logging configuration into an XML byte array.
150      *
151      * @param loggingConfiguration
152      *            The configuration to convert.
153      *
154      * @return The XML byte array representation.
155      */

156     public byte[] convertToXmlByteArray(BucketLoggingConfiguration loggingConfiguration) {
157         // Default log file prefix to the empty string if none is specified
158         String logFilePrefix = loggingConfiguration.getLogFilePrefix();
159         if (logFilePrefix == null)
160             logFilePrefix = "";
161
162         XmlWriter xml = new XmlWriter();
163         xml.start("BucketLoggingStatus""xmlns", Constants.XML_NAMESPACE);
164         if (loggingConfiguration.isLoggingEnabled()) {
165             xml.start("LoggingEnabled");
166             xml.start("TargetBucket").value(loggingConfiguration.getDestinationBucketName()).end();
167             xml.start("TargetPrefix").value(loggingConfiguration.getLogFilePrefix()).end();
168             xml.end();
169         }
170         xml.end();
171
172         return xml.getBytes();
173     }
174
175     /**
176      * Converts the specified notification configuration into an XML byte array.
177      *
178      * @param notificationConfiguration
179      *            The configuration to convert.
180      *
181      * @return The XML byte array representation.
182      */

183     public byte[] convertToXmlByteArray(
184             BucketNotificationConfiguration notificationConfiguration) {
185         XmlWriter xml = new XmlWriter();
186         xml.start("NotificationConfiguration""xmlns", Constants.XML_NAMESPACE);
187         Map<String, NotificationConfiguration> configurations = notificationConfiguration
188                 .getConfigurations();
189
190         for (Map.Entry<String, NotificationConfiguration> entry : configurations
191                 .entrySet()) {
192             String configName = entry.getKey();
193             NotificationConfiguration config = entry.getValue();
194             if (config instanceof TopicConfiguration) {
195                 xml.start("TopicConfiguration");
196                 xml.start("Id").value(configName).end();
197                 xml.start("Topic")
198                         .value(((TopicConfiguration) config).getTopicARN())
199                         .end();
200                 addEventsAndFilterCriteria(xml, config);
201                 xml.end();
202             } else if (config instanceof QueueConfiguration) {
203                 xml.start("QueueConfiguration");
204                 xml.start("Id").value(configName).end();
205                 xml.start("Queue")
206                         .value(((QueueConfiguration) config).getQueueARN())
207                         .end();
208                 addEventsAndFilterCriteria(xml, config);
209                 xml.end();
210             } else if (config instanceof CloudFunctionConfiguration) {
211                 xml.start("CloudFunctionConfiguration");
212                 xml.start("Id").value(configName).end();
213                 xml.start("InvocationRole")
214                         .value(((CloudFunctionConfiguration) config)
215                                 .getInvocationRoleARN()).end();
216                 xml.start("CloudFunction")
217                         .value(((CloudFunctionConfiguration) config).getCloudFunctionARN())
218                         .end();
219                 addEventsAndFilterCriteria(xml, config);
220                 xml.end();
221             } else if (config instanceof LambdaConfiguration) {
222                 xml.start("CloudFunctionConfiguration");
223                 xml.start("Id").value(configName).end();
224                 xml.start("CloudFunction")
225                         .value(((LambdaConfiguration) config).getFunctionARN())
226                         .end();
227                 addEventsAndFilterCriteria(xml, config);
228                 xml.end();
229             }
230         }
231         xml.end();
232         return xml.getBytes();
233     }
234
235     private void addEventsAndFilterCriteria(XmlWriter xml, NotificationConfiguration config) {
236         for (String event : config.getEvents()) {
237             xml.start("Event").value(event).end();
238         }
239
240         Filter filter = config.getFilter();
241         if (filter != null) {
242             validateFilter(filter);
243             xml.start("Filter");
244             if (filter.getS3KeyFilter() != null) {
245                 validateS3KeyFilter(filter.getS3KeyFilter());
246                 xml.start("S3Key");
247                 for (FilterRule filterRule : filter.getS3KeyFilter().getFilterRules()) {
248                     xml.start("FilterRule");
249                     xml.start("Name").value(filterRule.getName()).end();
250                     xml.start("Value").value(filterRule.getValue()).end();
251                     xml.end();
252                 }
253                 xml.end();
254             }
255             xml.end();
256         }
257     }
258
259     private void validateFilter(Filter filter) {
260         if (filter.getS3KeyFilter() == null) {
261             throw new SdkClientException("Cannot have a Filter without any criteria");
262         }
263     }
264
265     /**
266      * If S3Key filter is set make sure it has at least one rule
267      */

268     private void validateS3KeyFilter(S3KeyFilter s3KeyFilter) {
269         if (CollectionUtils.isNullOrEmpty(s3KeyFilter.getFilterRules())) {
270             throw new SdkClientException("Cannot have an S3KeyFilter without any filter rules");
271         }
272     }
273
274     private void writeReplicationPrefix(final XmlWriter xml, final ReplicationRule rule) {
275         // If no filter is set stick with the legacy behavior where we treat a null prefix as empty prefix.
276         if (rule.getFilter() == null) {
277             xml.start("Prefix").value(rule.getPrefix() == null ? "" : rule.getPrefix()).end();
278         } else if (rule.getPrefix() != null) {
279             throw new IllegalArgumentException(
280                     "Prefix cannot be used with Filter. Use ReplicationPrefixPredicate to create a ReplicationFilter");
281         }
282     }
283
284     public byte[] convertToXmlByteArray(BucketReplicationConfiguration replicationConfiguration) {
285         XmlWriter xml = new XmlWriter();
286         xml.start("ReplicationConfiguration");
287         Map<String, ReplicationRule> rules = replicationConfiguration
288                 .getRules();
289
290         final String role = replicationConfiguration.getRoleARN();
291         xml.start("Role").value(role).end();
292         for (Map.Entry<String, ReplicationRule> entry : rules
293                 .entrySet()) {
294             final String ruleId = entry.getKey();
295             final ReplicationRule rule = entry.getValue();
296
297             xml.start("Rule");
298             xml.start("ID").value(ruleId).end();
299             Integer priority = rule.getPriority();
300             if (priority != null) {
301                 xml.start("Priority").value(Integer.toString(priority)).end();
302             }
303             xml.start("Status").value(rule.getStatus()).end();
304             ExistingObjectReplication existingObjectReplication = rule.getExistingObjectReplication();
305             if (existingObjectReplication != null) {
306                 xml.start("ExistingObjectReplication").start("Status").value(existingObjectReplication.getStatus()).end().end();
307             }
308             DeleteMarkerReplication deleteMarkerReplication = rule.getDeleteMarkerReplication();
309             if (deleteMarkerReplication != null) {
310                 xml.start("DeleteMarkerReplication").start("Status").value(deleteMarkerReplication.getStatus()).end().end();
311             }
312             writeReplicationPrefix(xml, rule);
313             writeReplicationFilter(xml, rule.getFilter());
314
315             if (rule.getSourceSelectionCriteria() != null) {
316                 xml.start("SourceSelectionCriteria");
317                 SseKmsEncryptedObjects sseKmsEncryptedObjects = rule.getSourceSelectionCriteria().getSseKmsEncryptedObjects();
318                 if (sseKmsEncryptedObjects != null) {
319                     xml.start("SseKmsEncryptedObjects");
320                     addParameterIfNotNull(xml, "Status", sseKmsEncryptedObjects.getStatus());
321                     xml.end();
322                 }
323                 xml.end();
324             }
325
326             final ReplicationDestinationConfig config = rule.getDestinationConfig();
327             xml.start("Destination");
328             xml.start("Bucket").value(config.getBucketARN()).end();
329
330             addParameterIfNotNull(xml, "Account", config.getAccount());
331
332             if (config.getStorageClass() != null) {
333                 xml.start("StorageClass").value(config.getStorageClass()).end();
334             }
335
336             final AccessControlTranslation accessControlTranslation = config.getAccessControlTranslation();
337             if (accessControlTranslation != null) {
338                 xml.start("AccessControlTranslation");
339                 addParameterIfNotNull(xml, "Owner", accessControlTranslation.getOwner());
340                 xml.end();
341             }
342             if (config.getEncryptionConfiguration() != null) {
343                 xml.start("EncryptionConfiguration");
344                 addParameterIfNotNull(xml, "ReplicaKmsKeyID",
345                                       config.getEncryptionConfiguration().getReplicaKmsKeyID());
346                 xml.end();
347             }
348
349             ReplicationTime replicationTime = config.getReplicationTime();
350             if (replicationTime != null) {
351                 xml.start("ReplicationTime");
352                 addParameterIfNotNull(xml, "Status", replicationTime.getStatus());
353
354                 if (replicationTime.getTime() != null) {
355                     xml.start("Time");
356                     ReplicationTimeValue time = replicationTime.getTime();
357                     if (time.getMinutes() != null) {
358                         xml.start("Minutes").value(time.getMinutes().toString()).end();
359                     }
360                     xml.end();
361                 }
362                 xml.end();
363             }
364
365             Metrics metrics = config.getMetrics();
366             if (metrics != null) {
367                 xml.start("Metrics");
368                 addParameterIfNotNull(xml, "Status", metrics.getStatus());
369
370                 if (metrics.getEventThreshold() != null) {
371                     xml.start("EventThreshold");
372                     ReplicationTimeValue eventThreshold = metrics.getEventThreshold();
373                     if (eventThreshold.getMinutes() != null) {
374                         xml.start("Minutes").value(eventThreshold.getMinutes().toString()).end();
375                     }
376                     xml.end();
377                 }
378                 xml.end();
379             }
380
381             xml.end();
382
383             xml.end();
384
385         }
386         xml.end();
387         return xml.getBytes();
388     }
389
390     /**
391      * Converts the specified website configuration into an XML byte array to
392      * send to S3.
393      *
394      * Sample XML:
395      * <WebsiteConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
396      *    <IndexDocument>
397      *      <Suffix>index.html</Suffix>
398      *    </IndexDocument>
399      *    <ErrorDocument>
400      *      <Key>404.html</Key>
401      *    </ErrorDocument>
402      *  </WebsiteConfiguration>
403      *
404      * @param websiteConfiguration
405      *            The configuration to convert.
406      * @return The XML byte array representation.
407      */

408     public byte[] convertToXmlByteArray(BucketWebsiteConfiguration websiteConfiguration) {
409         XmlWriter xml = new XmlWriter();
410         xml.start("WebsiteConfiguration""xmlns", Constants.XML_NAMESPACE);
411
412         if (websiteConfiguration.getIndexDocumentSuffix() != null) {
413             XmlWriter indexDocumentElement = xml.start("IndexDocument");
414             indexDocumentElement.start("Suffix").value(websiteConfiguration.getIndexDocumentSuffix()).end();
415             indexDocumentElement.end();
416         }
417
418         if (websiteConfiguration.getErrorDocument() != null) {
419             XmlWriter errorDocumentElement = xml.start("ErrorDocument");
420             errorDocumentElement.start("Key").value(websiteConfiguration.getErrorDocument()).end();
421             errorDocumentElement.end();
422         }
423
424         RedirectRule redirectAllRequestsTo = websiteConfiguration.getRedirectAllRequestsTo();
425         if (redirectAllRequestsTo != null) {
426             XmlWriter redirectAllRequestsElement = xml.start("RedirectAllRequestsTo");
427             if (redirectAllRequestsTo.getprotocol() != null) {
428                 xml.start("Protocol").value(redirectAllRequestsTo.getprotocol()).end();
429             }
430
431             if (redirectAllRequestsTo.getHostName() != null) {
432                 xml.start("HostName").value(redirectAllRequestsTo.getHostName()).end();
433             }
434
435             if (redirectAllRequestsTo.getReplaceKeyPrefixWith() != null) {
436                 xml.start("ReplaceKeyPrefixWith").value(redirectAllRequestsTo.getReplaceKeyPrefixWith()).end();
437             }
438
439             if (redirectAllRequestsTo.getReplaceKeyWith() != null) {
440                 xml.start("ReplaceKeyWith").value(redirectAllRequestsTo.getReplaceKeyWith()).end();
441             }
442             redirectAllRequestsElement.end();
443         }
444
445         if (websiteConfiguration.getRoutingRules() != null && websiteConfiguration.getRoutingRules().size() > 0) {
446
447             XmlWriter routingRules = xml.start("RoutingRules");
448             for (RoutingRule rule : websiteConfiguration.getRoutingRules()) {
449                 writeRule(routingRules, rule);
450             }
451
452             routingRules.end();
453         }
454
455         xml.end();
456         return xml.getBytes();
457     }
458
459     /**
460      * Converts the specified {@link BucketLifecycleConfiguration} object to an XML fragment that
461      * can be sent to Amazon S3.
462      *
463      * @param config
464      *            The {@link BucketLifecycleConfiguration}
465      */

466      /* <LifecycleConfiguration>
467            <Rule>
468                <ID>logs-rule</ID>
469                <Status>Enabled</Status>
470                <Transition>
471                    <Days>30</Days>
472                    <StorageClass>GLACIER</StorageClass>
473                </Transition>
474                <Expiration>
475                    <Days>365</Days>
476                </Expiration>
477                <NoncurrentVersionTransition>
478                    <NoncurrentDays>7</NoncurrentDays>
479                    <StorageClass>GLACIER</StorageClass>
480                </NoncurrentVersionTransition>
481                <NoncurrentVersionExpiration>
482                    <NoncurrentDays>14</NoncurrentDays>
483                </NoncurrentVersionExpiration>
484                <Filter> <!-- A filter can have only one of Prefix, Tag or And. -->
485                  <Prefix>logs/</Prefix>
486                  <Tag>
487                     <Key>key1</Key>
488                     <Value>value1</Value>
489                  </Tag>
490                  <And>
491                     <Prefix>logs/</Prefix>
492                     <Tag>
493                         <Key>key1</Key>
494                         <Value>value1</Value>
495                     </Tag>
496                     <Tag>
497                         <Key>key1</Key>
498                         <Value>value1</Value>
499                     </Tag>
500                  </And>
501            </Filter>
502            </Rule>
503            <Rule>
504                <ID>image-rule</ID>
505                <Prefix>image/</Prefix>
506                <Status>Enabled</Status>
507                <Transition>
508                    <Date>2012-12-31T00:00:00.000Z</Date>
509                    <StorageClass>GLACIER</StorageClass>
510                </Transition>
511                <Expiration>
512                    <Date>2020-12-31T00:00:00.000Z</Date>
513                </Expiration>
514                <AbortIncompleteMultipartUpload>
515                    <DaysAfterInitiation>10</DaysAfterInitiation>
516                </AbortIncompleteMultipartUpload>
517           </Rule>
518     </LifecycleConfiguration>
519     */

520     public byte[] convertToXmlByteArray(BucketLifecycleConfiguration config) throws SdkClientException {
521
522         XmlWriter xml = new XmlWriter();
523         xml.start("LifecycleConfiguration");
524
525         for (Rule rule : config.getRules()) {
526             writeRule(xml, rule);
527         }
528
529         xml.end();
530
531         return xml.getBytes();
532     }
533
534     /**
535      * Converts the specified {@link BucketCrossOriginConfiguration} object to an XML fragment that
536      * can be sent to Amazon S3.
537      *
538      * @param config
539      *            The {@link BucketCrossOriginConfiguration}
540      */

541     /*
542      * <CORSConfiguration>
543              <CORSRule>
544                <AllowedOrigin>http://www.foobar.com</AllowedOrigin>
545                <AllowedMethod>GET</AllowedMethod>
546                <MaxAgeSeconds>3000</MaxAgeSec>
547                <ExposeHeader>x-amz-server-side-encryption</ExposeHeader>
548              </CORSRule>
549        </CORSConfiguration>
550      */

551     public byte[] convertToXmlByteArray(BucketCrossOriginConfiguration config) throws SdkClientException {
552
553         XmlWriter xml = new XmlWriter();
554         xml.start("CORSConfiguration""xmlns", Constants.XML_NAMESPACE);
555
556         for (CORSRule rule : config.getRules()) {
557             writeRule(xml, rule);
558         }
559
560         xml.end();
561
562         return xml.getBytes();
563     }
564
565     private void writeLifecyclePrefix(final XmlWriter xml, final Rule rule) {
566         // If no filter is set stick with the legacy behavior where we treat a null prefix as empty prefix.
567         if (rule.getFilter() == null) {
568             xml.start("Prefix").value(rule.getPrefix() == null ? "" : rule.getPrefix()).end();
569         } else if (rule.getPrefix() != null) {
570             throw new IllegalArgumentException(
571                     "Prefix cannot be used with Filter. Use LifecyclePrefixPredicate to create a LifecycleFilter");
572         }
573     }
574
575     private void writeRule(XmlWriter xml, Rule rule) {
576         xml.start("Rule");
577         if (rule.getId() != null) {
578             xml.start("ID").value(rule.getId()).end();
579         }
580         writeLifecyclePrefix(xml, rule);
581         xml.start("Status").value(rule.getStatus()).end();
582         writeLifecycleFilter(xml, rule.getFilter());
583
584         addTransitions(xml, rule.getTransitions());
585         addNoncurrentTransitions(xml, rule.getNoncurrentVersionTransitions());
586
587         if (hasCurrentExpirationPolicy(rule)) {
588             // The rule attributes below are mutually exclusive, the service will throw an error if
589             // more than one is provided
590             xml.start("Expiration");
591             if (rule.getExpirationInDays() != -1) {
592                 xml.start("Days").value("" + rule.getExpirationInDays()).end();
593             }
594             if (rule.getExpirationDate() != null) {
595                 xml.start("Date").value(ServiceUtils.formatIso8601Date(rule.getExpirationDate())).end();
596             }
597             if (rule.isExpiredObjectDeleteMarker() == true) {
598                 xml.start("ExpiredObjectDeleteMarker").value("true").end();
599             }
600             xml.end(); // </Expiration>
601         }
602
603         if (rule.getNoncurrentVersionExpirationInDays() != -1) {
604             xml.start("NoncurrentVersionExpiration");
605             xml.start("NoncurrentDays")
606                 .value(Integer.toString(
607                     rule.getNoncurrentVersionExpirationInDays()))
608                 .end();
609             xml.end(); // </NoncurrentVersionExpiration>
610         }
611
612         if (rule.getAbortIncompleteMultipartUpload() != null) {
613             xml.start("AbortIncompleteMultipartUpload");
614             xml.start("DaysAfterInitiation").
615                     value(Integer.toString(rule.getAbortIncompleteMultipartUpload().getDaysAfterInitiation()))
616                     .end();
617             xml.end(); // </AbortIncompleteMultipartUpload>
618         }
619
620         xml.end(); // </Rule>
621     }
622
623
624     private void addTransitions(XmlWriter xml, List<Transition> transitions) {
625         if (transitions == null || transitions.isEmpty()) {
626             return;
627         }
628
629         for (Transition t : transitions) {
630             if (t != null) {
631                 xml.start("Transition");
632                 if (t.getDate() != null) {
633                     xml.start("Date");
634                     xml.value(ServiceUtils.formatIso8601Date(t.getDate()));
635                     xml.end();
636                 }
637                 if (t.getDays() != -1) {
638                     xml.start("Days");
639                     xml.value(Integer.toString(t.getDays()));
640                     xml.end();
641                 }
642
643                 xml.start("StorageClass");
644                 xml.value(t.getStorageClassAsString());
645                 xml.end(); // <StorageClass>
646                 xml.end(); // </Transition>
647             }
648         }
649     }
650
651     private void addNoncurrentTransitions(XmlWriter xml,
652             List<NoncurrentVersionTransition> transitions) {
653         if (transitions == null || transitions.isEmpty()) {
654             return;
655         }
656
657         for (NoncurrentVersionTransition t : transitions) {
658             if (t != null) {
659                 xml.start("NoncurrentVersionTransition");
660                 if (t.getDays() != -1) {
661                     xml.start("NoncurrentDays");
662                     xml.value(Integer.toString(t.getDays()));
663                     xml.end();
664                 }
665
666                 xml.start("StorageClass");
667                 xml.value(t.getStorageClassAsString());
668                 xml.end(); // </StorageClass>
669                 xml.end(); // </NoncurrentVersionTransition>
670             }
671         }
672     }
673
674     private void writeLifecycleFilter(XmlWriter xml, LifecycleFilter filter) {
675         if (filter == null) {
676             return;
677         }
678
679         xml.start("Filter");
680         writeLifecycleFilterPredicate(xml, filter.getPredicate());
681         xml.end();
682     }
683
684     private void writeLifecycleFilterPredicate(XmlWriter xml, LifecycleFilterPredicate predicate) {
685         if (predicate == null) {
686             return;
687         }
688         predicate.accept(new LifecyclePredicateVisitorImpl(xml));
689     }
690
691     private void writeReplicationFilter(XmlWriter xml, ReplicationFilter filter) {
692         if (filter == null) {
693             return;
694         }
695
696         xml.start("Filter");
697         writeReplicationPredicate(xml, filter.getPredicate());
698         xml.end();
699     }
700
701     private void writeReplicationPredicate(XmlWriter xml, ReplicationFilterPredicate predicate) {
702         if (predicate == null) {
703             return;
704         }
705         predicate.accept(new ReplicationPredicateVisitorImpl(xml));
706     }
707
708     public byte[] convertToXmlByteArray(ServerSideEncryptionConfiguration sseConfig) {
709         XmlWriter xml = new XmlWriter();
710         xml.start("ServerSideEncryptionConfiguration""xmlns", Constants.XML_NAMESPACE);
711         for (ServerSideEncryptionRule rule : sseConfig.getRules()) {
712             xml.start("Rule");
713             writeServerSideEncryptionByDefault(xml, rule.getApplyServerSideEncryptionByDefault());
714             xml.end();
715         }
716         xml.end();
717         return xml.getBytes();
718     }
719
720     private void writeServerSideEncryptionByDefault(XmlWriter xml, ServerSideEncryptionByDefault sseByDefault) {
721         if (sseByDefault == null) {
722             return;
723         }
724         xml.start("ApplyServerSideEncryptionByDefault");
725         addParameterIfNotNull(xml, "SSEAlgorithm", sseByDefault.getSSEAlgorithm());
726         addParameterIfNotNull(xml, "KMSMasterKeyID", sseByDefault.getKMSMasterKeyID());
727         xml.end();
728     }
729
730     public byte[] convertToXmlByteArray(PublicAccessBlockConfiguration config) {
731         XmlWriter xml = new XmlWriter();
732         xml.start("PublicAccessBlockConfiguration""xmlns", Constants.XML_NAMESPACE);
733         addBooleanParameterIfNotNull(xml, "BlockPublicAcls", config.getBlockPublicAcls());
734         addBooleanParameterIfNotNull(xml, "IgnorePublicAcls", config.getIgnorePublicAcls());
735         addBooleanParameterIfNotNull(xml, "BlockPublicPolicy", config.getBlockPublicPolicy());
736         addBooleanParameterIfNotNull(xml, "RestrictPublicBuckets", config.getRestrictPublicBuckets());
737         xml.end();
738         return xml.getBytes();
739     }
740
741     private class LifecyclePredicateVisitorImpl implements LifecyclePredicateVisitor {
742         private final XmlWriter xml;
743
744         public LifecyclePredicateVisitorImpl(XmlWriter xml) {
745             this.xml = xml;
746         }
747
748         @Override
749         public void visit(LifecyclePrefixPredicate lifecyclePrefixPredicate) {
750             writePrefix(xml, lifecyclePrefixPredicate.getPrefix());
751         }
752
753         @Override
754         public void visit(LifecycleTagPredicate lifecycleTagPredicate) {
755             writeTag(xml, lifecycleTagPredicate.getTag());
756         }
757
758         @Override
759         public void visit(LifecycleAndOperator lifecycleAndOperator) {
760             xml.start("And");
761             for (LifecycleFilterPredicate predicate : lifecycleAndOperator.getOperands()) {
762                 predicate.accept(this);
763             }
764             xml.end(); // </And>
765         }
766     }
767
768     /**
769      * @param rule
770      * @return True if rule has a current expiration (<Expiration/>) policy set
771      */

772     private boolean hasCurrentExpirationPolicy(Rule rule) {
773         return rule.getExpirationInDays() != -1 || rule.getExpirationDate() != null || rule.isExpiredObjectDeleteMarker();
774     }
775
776     private void writeRule(XmlWriter xml, CORSRule rule) {
777         xml.start("CORSRule");
778         if (rule.getId() != null) {
779             xml.start("ID").value(rule.getId()).end();
780         }
781         if (rule.getAllowedOrigins() != null) {
782             for (String origin : rule.getAllowedOrigins()) {
783                 xml.start("AllowedOrigin").value(origin).end();
784             }
785         }
786         if (rule.getAllowedMethods() != null) {
787             for (AllowedMethods method : rule.getAllowedMethods()) {
788                 xml.start("AllowedMethod").value(method.toString()).end();
789             }
790         }
791         if(rule.getMaxAgeSeconds() != 0) {
792             xml.start("MaxAgeSeconds").value(Integer.toString(rule.getMaxAgeSeconds())).end();
793         }
794         if (rule.getExposedHeaders() != null) {
795             for (String header : rule.getExposedHeaders()) {
796                 xml.start("ExposeHeader").value(header).end();
797             }
798         }
799         if (rule.getAllowedHeaders() != null) {
800             for(String header : rule.getAllowedHeaders()) {
801                 xml.start("AllowedHeader").value(header).end();
802             }
803         }
804         xml.end();//</CORSRule>
805     }
806
807     private void writeRule(XmlWriter xml, RoutingRule rule) {
808         xml.start("RoutingRule");
809         RoutingRuleCondition condition = rule.getCondition();
810         if (condition != null) {
811             xml.start("Condition");
812             xml.start("KeyPrefixEquals");
813             if (condition.getKeyPrefixEquals() != null) {
814                 xml.value(condition.getKeyPrefixEquals());
815             }
816             xml.end(); // </KeyPrefixEquals">
817
818             if (condition.getHttpErrorCodeReturnedEquals() != null) {
819                 xml.start("HttpErrorCodeReturnedEquals ").value(condition.getHttpErrorCodeReturnedEquals()).end();
820             }
821
822             xml.end(); // </Condition>
823         }
824
825         xml.start("Redirect");
826         RedirectRule redirect = rule.getRedirect();
827         if (redirect != null) {
828             if (redirect.getprotocol() != null) {
829                 xml.start("Protocol").value(redirect.getprotocol()).end();
830             }
831
832             if (redirect.getHostName() != null) {
833                 xml.start("HostName").value(redirect.getHostName()).end();
834             }
835
836             if (redirect.getReplaceKeyPrefixWith() != null) {
837                 xml.start("ReplaceKeyPrefixWith").value(redirect.getReplaceKeyPrefixWith()).end();
838             }
839
840             if (redirect.getReplaceKeyWith() != null) {
841                 xml.start("ReplaceKeyWith").value(redirect.getReplaceKeyWith()).end();
842             }
843
844             if (redirect.getHttpRedirectCode() != null) {
845                 xml.start("HttpRedirectCode").value(redirect.getHttpRedirectCode()).end();
846             }
847         }
848         xml.end(); // </Redirect>
849         xml.end();// </CORSRule>
850     }
851
852
853     /**
854      * Converts the specified {@link BucketTaggingConfiguration} object to an XML fragment that
855      * can be sent to Amazon S3.
856      *
857      * @param config
858      *            The {@link BucketTaggingConfiguration}
859      */

860     /*
861      * <Tagging>
862          <TagSet>
863             <Tag>
864                    <Key>Project</Key>
865                    <Value>Foo</Value>
866             </Tag>
867             <Tag>
868                    <Key>User</Key>
869                    <Value>nschnarr</Value>
870             </Tag>
871          </TagSet>
872         </Tagging>
873     */

874     public byte[] convertToXmlByteArray(BucketTaggingConfiguration config) throws SdkClientException {
875
876         XmlWriter xml = new XmlWriter();
877         xml.start("Tagging");
878
879         for (TagSet tagset : config.getAllTagSets()) {
880             writeRule(xml, tagset);
881         }
882
883         xml.end();
884
885         return xml.getBytes();
886     }
887
888     /**
889      * Converts the specified {@link InventoryConfiguration} object to an XML fragment that
890      * can be sent to Amazon S3.
891      *
892      * @param config
893      *            The {@link InventoryConfiguration}
894      */

895      /*
896         <?xml version="1.0" encoding="UTF-8"?>
897         <InventoryConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
898            <Destination>
899               <S3BucketDestination>
900                  <AccountId>A2OCNCIEQW9MSG</AccountId>
901                  <Bucket>s3-object-inventory-list-gamma-us-east-1</Bucket>
902                  <Format>CSV</Format>
903                  <Prefix>string</Prefix>
904               </S3BucketDestination>
905            </Destination>
906            <IsEnabled>true</IsEnabled>
907            <Filter>
908               <Prefix>string</Prefix>
909            </Filter>
910            <Id>configId</Id>
911            <IncludedObjectVersions>All</IncludedObjectVersions>
912            <OptionalFields>
913               <Field>Size</Field>
914               <Field>LastModifiedDate</Field>
915               <Field>StorageClass</Field>
916               <Field>ETag</Field>
917               <Field>IsMultipartUploaded</Field>
918               <Field>ReplicationStatus</Field>
919            </OptionalFields>
920            <Schedule>
921               <Frequency>Daily</Frequency>
922            </Schedule>
923         </InventoryConfiguration>
924     */

925     public byte[] convertToXmlByteArray(InventoryConfiguration config) throws SdkClientException {
926         XmlWriter xml = new XmlWriter();
927         xml.start("InventoryConfiguration""xmlns", Constants.XML_NAMESPACE);
928
929         xml.start("Id").value(config.getId()).end();
930         xml.start("IsEnabled").value(String.valueOf(config.isEnabled())).end();
931         xml.start("IncludedObjectVersions").value(config.getIncludedObjectVersions()).end();
932
933         writeInventoryDestination(xml, config.getDestination());
934         writeInventoryFilter(xml, config.getInventoryFilter());
935         addInventorySchedule(xml, config.getSchedule());
936         addInventoryOptionalFields(xml, config.getOptionalFields());
937
938         xml.end(); // </InventoryConfiguration>
939
940         return xml.getBytes();
941     }
942
943     private void writeInventoryDestination(XmlWriter xml, InventoryDestination destination) {
944         if (destination == null) {
945             return;
946         }
947
948         xml.start("Destination");
949         InventoryS3BucketDestination s3BucketDestination = destination.getS3BucketDestination();
950         if (s3BucketDestination != null) {
951             xml.start("S3BucketDestination");
952             addParameterIfNotNull(xml, "AccountId", s3BucketDestination.getAccountId());
953             addParameterIfNotNull(xml, "Bucket", s3BucketDestination.getBucketArn());
954             addParameterIfNotNull(xml, "Prefix", s3BucketDestination.getPrefix());
955             addParameterIfNotNull(xml, "Format", s3BucketDestination.getFormat());
956             writeInventoryEncryption(xml, s3BucketDestination.getEncryption());
957             xml.end(); // </S3BucketDestination>
958         }
959         xml.end(); // </Destination>
960     }
961
962     private void writeInventoryEncryption(XmlWriter xml, InventoryEncryption encryption) {
963         if (encryption == null) {
964             return;
965         }
966         xml.start("Encryption");
967         if (encryption instanceof ServerSideEncryptionS3) {
968             xml.start("SSE-S3").end();
969         } else if (encryption instanceof ServerSideEncryptionKMS) {
970             xml.start("SSE-KMS");
971             addParameterIfNotNull(xml, "KeyId", ((ServerSideEncryptionKMS) encryption).getKeyId());
972             xml.end();
973         }
974         xml.end();
975     }
976
977     private void writeInventoryFilter(XmlWriter xml, InventoryFilter inventoryFilter) {
978         if (inventoryFilter == null) {
979             return;
980         }
981
982         xml.start("Filter");
983         writeInventoryFilterPredicate(xml, inventoryFilter.getPredicate());
984         xml.end();
985     }
986
987     private void writeInventoryFilterPredicate(XmlWriter xml, InventoryFilterPredicate predicate) {
988         if (predicate == null) {
989             return;
990         }
991
992         if (predicate instanceof InventoryPrefixPredicate) {
993             writePrefix(xml, ((InventoryPrefixPredicate) predicate).getPrefix());
994         }
995     }
996
997     private void addInventorySchedule(XmlWriter xml, InventorySchedule schedule) {
998         if (schedule == null) {
999             return;
1000         }
1001
1002         xml.start("Schedule");
1003         addParameterIfNotNull(xml, "Frequency", schedule.getFrequency());
1004         xml.end();
1005     }
1006
1007     private void addInventoryOptionalFields(XmlWriter xml, List<String> optionalFields) {
1008         if (CollectionUtils.isNullOrEmpty(optionalFields)) {
1009             return;
1010         }
1011
1012         xml.start("OptionalFields");
1013         for (String field : optionalFields) {
1014             xml.start("Field").value(field).end();
1015         }
1016         xml.end();
1017     }
1018
1019     private void writeRule(XmlWriter xml, TagSet tagset) {
1020         xml.start("TagSet");
1021         for ( String key : tagset.getAllTags().keySet() ) {
1022             xml.start("Tag");
1023             xml.start("Key").value(key).end();
1024             xml.start("Value").value(tagset.getTag(key)).end();
1025             xml.end(); // </Tag>
1026         }
1027         xml.end(); // </TagSet>
1028     }
1029
1030     private boolean hasTags(TagSet tagSet) {
1031         return tagSet != null && tagSet.getAllTags() != null && tagSet.getAllTags().size() > 0;
1032     }
1033
1034     /**
1035      * Converts the specified {@link com.amazonaws.services.s3.model.analytics.AnalyticsConfiguration} object to an
1036      * XML fragment that can be sent to Amazon S3.
1037      *
1038      * @param config
1039      *            The {@link com.amazonaws.services.s3.model.analytics.AnalyticsConfiguration}
1040      */

1041      /*
1042       * <AnalyticsConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
1043            <Id>XXX</Id>
1044            <Filter>
1045              <And>
1046                <Prefix>documents/</Prefix>
1047                <Tag>
1048                  <Key>foo</Key>
1049                  <Value>bar</Value>
1050                </Tag>
1051              </And>
1052            </Filter>
1053            <StorageClassAnalysis>
1054              <DataExport>
1055                <OutputSchemaVersion>1</OutputSchemaVersion>
1056                <Destination>
1057                  <S3BucketDestination>
1058                    <Format>CSV</Format>
1059                    <BucketAccountId>123456789</BucketAccountId>
1060                    <Bucket>destination-bucket</Bucket>
1061                    <Prefix>destination-prefix</Prefix>
1062                  </S3BucketDestination>
1063                </Destination>
1064              </DataExport>
1065            </StorageClassAnalysis>
1066         </AnalyticsConfiguration>
1067      */

1068     public byte[] convertToXmlByteArray(AnalyticsConfiguration config) throws SdkClientException {
1069         XmlWriter xml = new XmlWriter();
1070
1071         xml.start("AnalyticsConfiguration""xmlns", Constants.XML_NAMESPACE);
1072
1073         addParameterIfNotNull(xml, "Id", config.getId());
1074         writeAnalyticsFilter(xml, config.getFilter());
1075         writeStorageClassAnalysis(xml, config.getStorageClassAnalysis());
1076
1077         xml.end();
1078
1079         return xml.getBytes();
1080     }
1081
1082     private void writeAnalyticsFilter(XmlWriter xml, AnalyticsFilter filter) {
1083         if (filter == null) {
1084             return;
1085         }
1086
1087         xml.start("Filter");
1088         writeAnalyticsFilterPredicate(xml, filter.getPredicate());
1089         xml.end();
1090     }
1091
1092     private void writeAnalyticsFilterPredicate(XmlWriter xml, AnalyticsFilterPredicate predicate) {
1093         if (predicate == null) {
1094             return;
1095         }
1096
1097         predicate.accept(new AnalyticsPredicateVisitorImpl(xml));
1098     }
1099
1100     private void writeStorageClassAnalysis(XmlWriter xml, StorageClassAnalysis storageClassAnalysis) {
1101         if (storageClassAnalysis == nullreturn;
1102
1103         xml.start("StorageClassAnalysis");
1104         if (storageClassAnalysis.getDataExport() != null) {
1105             StorageClassAnalysisDataExport dataExport = storageClassAnalysis.getDataExport();
1106
1107             xml.start("DataExport");
1108
1109             addParameterIfNotNull(xml, "OutputSchemaVersion", dataExport.getOutputSchemaVersion());
1110             writeAnalyticsExportDestination(xml, dataExport.getDestination());
1111
1112             xml.end(); // </DataExport>
1113         }
1114
1115         xml.end(); // </StorageClassAnalysis>
1116     }
1117
1118     private void writeAnalyticsExportDestination(XmlWriter xml, AnalyticsExportDestination destination) {
1119         if (destination == null) {
1120             return;
1121         }
1122
1123         xml.start("Destination");
1124
1125         if (destination.getS3BucketDestination() != null) {
1126             xml.start("S3BucketDestination");
1127             AnalyticsS3BucketDestination s3BucketDestination = destination.getS3BucketDestination();
1128             addParameterIfNotNull(xml, "Format", s3BucketDestination.getFormat());
1129             addParameterIfNotNull(xml, "BucketAccountId", s3BucketDestination.getBucketAccountId());
1130             addParameterIfNotNull(xml, "Bucket", s3BucketDestination.getBucketArn());
1131             addParameterIfNotNull(xml, "Prefix", s3BucketDestination.getPrefix());
1132             xml.end();  // </S3BucketDestination>
1133         }
1134
1135         xml.end(); // </Destination>
1136     }
1137
1138     /**
1139      * Converts the specified {@link com.amazonaws.services.s3.model.metrics.MetricsConfiguration}
1140      * object to an XML fragment that can be sent to Amazon S3.
1141      *
1142      * @param config
1143      *            The {@link com.amazonaws.services.s3.model.metrics.MetricsConfiguration}.
1144      */

1145      /*
1146       * <MetricsConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
1147            <Id>metrics-id</Id>
1148            <Filter>
1149            <!-- A filter should have only one of Prefix, Tag or And-->
1150              <Prefix>prefix</Prefix>
1151              <Tag>
1152                  <Key>Project</Key>
1153                  <Value>Foo</Value>
1154              </Tag>
1155              <And>
1156                <Prefix>documents/</Prefix>
1157                <Tag>
1158                  <Key>foo</Key>
1159                  <Value>bar</Value>
1160                </Tag>
1161              </And>
1162            </Filter>
1163         </MetricsConfiguration>
1164      */

1165     public byte[] convertToXmlByteArray(MetricsConfiguration config) throws SdkClientException {
1166         XmlWriter xml = new XmlWriter();
1167
1168         xml.start("MetricsConfiguration""xmlns", Constants.XML_NAMESPACE);
1169
1170         addParameterIfNotNull(xml, "Id", config.getId());
1171         writeMetricsFilter(xml, config.getFilter());
1172
1173         xml.end();
1174
1175         return xml.getBytes();
1176     }
1177
1178     private void writeMetricsFilter(XmlWriter xml, MetricsFilter filter) {
1179         if (filter == null) {
1180             return;
1181         }
1182
1183         xml.start("Filter");
1184         writeMetricsFilterPredicate(xml, filter.getPredicate());
1185         xml.end();
1186     }
1187
1188     private void writeMetricsFilterPredicate(XmlWriter xml, MetricsFilterPredicate predicate) {
1189         if (predicate == null) {
1190             return;
1191         }
1192
1193         predicate.accept(new MetricsPredicateVisitorImpl(xml));
1194     }
1195
1196     private class MetricsPredicateVisitorImpl implements MetricsPredicateVisitor {
1197         private final XmlWriter xml;
1198
1199         public MetricsPredicateVisitorImpl(XmlWriter xml) {
1200             this.xml = xml;
1201         }
1202
1203         @Override
1204         public void visit(MetricsPrefixPredicate metricsPrefixPredicate) {
1205             writePrefix(xml, metricsPrefixPredicate.getPrefix());
1206         }
1207
1208         @Override
1209         public void visit(MetricsTagPredicate metricsTagPredicate) {
1210             writeTag(xml, metricsTagPredicate.getTag());
1211         }
1212
1213         @Override
1214         public void visit(MetricsAndOperator metricsAndOperator) {
1215             xml.start("And");
1216             for (MetricsFilterPredicate predicate : metricsAndOperator.getOperands()) {
1217                 predicate.accept(this);
1218             }
1219             xml.end();
1220         }
1221     }
1222
1223     private void addBooleanParameterIfNotNull(XmlWriter xml, String xmlTagName, Boolean value) {
1224         if (value != null) {
1225             xml.start(xmlTagName).value(value.toString()).end();
1226         }
1227     }
1228
1229     private void writeTag(XmlWriter xml, Tag tag) {
1230         if (tag == null) {
1231             return;
1232         }
1233         xml.start("Tag");
1234         xml.start("Key").value(tag.getKey()).end();
1235         xml.start("Value").value(tag.getValue()).end();
1236         xml.end();
1237     }
1238
1239 }
1240