/* * Copyright 2010 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Portions copyright 2006-2009 James Murty. Please see LICENSE.txt * for applicable license terms and NOTICE.txt for applicable notices. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package com.amazonaws.services.s3.model.transform; import java.io.BufferedReader; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.text.ParseException; import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.Locale; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.xml.sax.Attributes; import org.xml.sax.InputSource; import org.xml.sax.SAXException; import org.xml.sax.XMLReader; import org.xml.sax.helpers.DefaultHandler; import org.xml.sax.helpers.XMLReaderFactory; import com.amazonaws.AmazonClientException; import com.amazonaws.services.s3.internal.Constants; import com.amazonaws.services.s3.internal.ServiceUtils; import com.amazonaws.services.s3.model.AccessControlList; import com.amazonaws.services.s3.model.AmazonS3Exception; import com.amazonaws.services.s3.model.Bucket; import com.amazonaws.services.s3.model.BucketLoggingConfiguration; import com.amazonaws.services.s3.model.BucketNotificationConfiguration; import com.amazonaws.services.s3.model.BucketVersioningConfiguration; import com.amazonaws.services.s3.model.CanonicalGrantee; import com.amazonaws.services.s3.model.CompleteMultipartUploadResult; import com.amazonaws.services.s3.model.EmailAddressGrantee; import com.amazonaws.services.s3.model.Grantee; import com.amazonaws.services.s3.model.GroupGrantee; import com.amazonaws.services.s3.model.InitiateMultipartUploadResult; import com.amazonaws.services.s3.model.MultipartUpload; import com.amazonaws.services.s3.model.MultipartUploadListing; import com.amazonaws.services.s3.model.ObjectListing; import com.amazonaws.services.s3.model.Owner; import com.amazonaws.services.s3.model.PartListing; import com.amazonaws.services.s3.model.PartSummary; import com.amazonaws.services.s3.model.Permission; import com.amazonaws.services.s3.model.S3Object; import com.amazonaws.services.s3.model.S3ObjectSummary; import com.amazonaws.services.s3.model.S3VersionSummary; import com.amazonaws.services.s3.model.VersionListing; import com.amazonaws.services.s3.model.BucketNotificationConfiguration.TopicConfiguration; /** * XML Sax parser to read XML documents returned by S3 via the REST interface, * converting these documents into objects. */ public class XmlResponsesSaxParser { private static final Log log = LogFactory.getLog(XmlResponsesSaxParser.class); private XMLReader xr = null; private boolean sanitizeXmlDocument = true; /** * Constructs the XML SAX parser. * * @throws AmazonClientException */ public XmlResponsesSaxParser() throws AmazonClientException { // Ensure we can load the XML Reader. try { xr = XMLReaderFactory.createXMLReader(); } catch (SAXException e) { // oops, lets try doing this (needed in 1.4) System.setProperty("org.xml.sax.driver", "org.apache.crimson.parser.XMLReaderImpl"); try { // Try once more... xr = XMLReaderFactory.createXMLReader(); } catch (SAXException e2) { throw new AmazonClientException("Couldn't initialize a sax driver for the XMLReader"); } } } /** * Parses an XML document from an input stream using a document handler. * * @param handler * the handler for the XML document * @param inputStream * an input stream containing the XML document to parse * * @throws AmazonClientException * any parsing, IO or other exceptions are wrapped in an * S3ServiceException. */ protected void parseXmlInputStream(DefaultHandler handler, InputStream inputStream) throws AmazonClientException { try { if (log.isDebugEnabled()) { log.debug("Parsing XML response document with handler: " + handler.getClass()); } BufferedReader breader = new BufferedReader(new InputStreamReader(inputStream, Constants.DEFAULT_ENCODING)); xr.setContentHandler(handler); xr.setErrorHandler(handler); xr.parse(new InputSource(breader)); } catch (Throwable t) { try { inputStream.close(); } catch (IOException e) { if (log.isErrorEnabled()) { log.error("Unable to close response InputStream up after XML parse failure", e); } } throw new AmazonClientException("Failed to parse XML document with handler " + handler.getClass(), t); } } protected InputStream sanitizeXmlDocument(DefaultHandler handler, InputStream inputStream) throws AmazonClientException { if (!sanitizeXmlDocument) { // No sanitizing will be performed, return the original input stream unchanged. return inputStream; } else { if (log.isDebugEnabled()) { log.debug("Sanitizing XML document destined for handler " + handler.getClass()); } InputStream sanitizedInputStream = null; try { /* * Read object listing XML document from input stream provided into a * string buffer, so we can replace troublesome characters before * sending the document to the XML parser. */ StringBuilder listingDocBuffer = new StringBuilder(); BufferedReader br = new BufferedReader( new InputStreamReader(inputStream, Constants.DEFAULT_ENCODING)); char[] buf = new char[8192]; int read = -1; while ((read = br.read(buf)) != -1) { listingDocBuffer.append(buf, 0, read); } br.close(); /* * Replace any carriage return (\r) characters with explicit XML * character entities, to prevent the SAX parser from * misinterpreting 0x0D characters as 0x0A and being unable to * parse the XML. */ String listingDoc = listingDocBuffer.toString().replaceAll("\r", " "); sanitizedInputStream = new ByteArrayInputStream( listingDoc.getBytes(Constants.DEFAULT_ENCODING)); } catch (Throwable t) { try { inputStream.close(); } catch (IOException e) { if (log.isErrorEnabled()) { log.error("Unable to close response InputStream after failure sanitizing XML document", e); } } throw new AmazonClientException("Failed to sanitize XML document destined for handler " + handler.getClass(), t); } return sanitizedInputStream; } } /** * Checks if the specified string is empty or null and if so, returns null. * Otherwise simply returns the string. * * @param s * The string to check. * @return Null if the specified string was null, or empty, otherwise * returns the string the caller passed in. */ private String checkForEmptyString(String s) { if (s == null) return null; if (s.length() == 0) return null; return s; } /** * Safely parses the specified string as an integer and returns the value. * If a NumberFormatException occurs while parsing the integer, an error is * logged and -1 is returned. * * @param s * The string to parse and return as an integer. * * @return The integer value of the specified string, otherwise -1 if there * were any problems parsing the string as an integer. */ private int parseInt(String s) { try { return Integer.parseInt(s); } catch (NumberFormatException nfe) { log.error("Unable to parse integer value '" + s + "'", nfe); } return -1; } /** * Safely parses the specified string as a long and returns the value. If a * NumberFormatException occurs while parsing the long, an error is logged * and -1 is returned. * * @param s * The string to parse and return as a long. * * @return The long value of the specified string, otherwise -1 if there * were any problems parsing the string as a long. */ private long parseLong(String s) { try { return Long.parseLong(s); } catch (NumberFormatException nfe) { log.error("Unable to parse long value '" + s + "'", nfe); } return -1; } /** * Parses a ListBucket response XML document from an input stream. * * @param inputStream * XML data input stream. * @return the XML handler object populated with data parsed from the XML * stream. * @throws AmazonClientException */ public ListBucketHandler parseListBucketObjectsResponse(InputStream inputStream) throws AmazonClientException { ListBucketHandler handler = new ListBucketHandler(); parseXmlInputStream(handler, sanitizeXmlDocument(handler, inputStream)); return handler; } /** * Parses a ListVersions response XML document from an input stream. * * @param inputStream * XML data input stream. * @return the XML handler object populated with data parsed from the XML * stream. * @throws AmazonClientException */ public ListVersionsHandler parseListVersionsResponse(InputStream inputStream) throws AmazonClientException { ListVersionsHandler handler = new ListVersionsHandler(); parseXmlInputStream(handler, sanitizeXmlDocument(handler, inputStream)); return handler; } /** * Parses a ListAllMyBuckets response XML document from an input stream. * * @param inputStream * XML data input stream. * @return the XML handler object populated with data parsed from the XML * stream. * @throws AmazonClientException */ public ListAllMyBucketsHandler parseListMyBucketsResponse(InputStream inputStream) throws AmazonClientException { ListAllMyBucketsHandler handler = new ListAllMyBucketsHandler(); parseXmlInputStream(handler, sanitizeXmlDocument(handler, inputStream)); return handler; } /** * Parses an AccessControlListHandler response XML document from an input * stream. * * @param inputStream * XML data input stream. * @return the XML handler object populated with data parsed from the XML * stream. * * @throws AmazonClientException */ public AccessControlListHandler parseAccessControlListResponse(InputStream inputStream) throws AmazonClientException { AccessControlListHandler handler = new AccessControlListHandler(); parseXmlInputStream(handler, inputStream); return handler; } /** * Parses a LoggingStatus response XML document for a bucket from an input * stream. * * @param inputStream * XML data input stream. * @return the XML handler object populated with data parsed from the XML * stream. * * @throws AmazonClientException */ public BucketLoggingConfigurationHandler parseLoggingStatusResponse(InputStream inputStream) throws AmazonClientException { BucketLoggingConfigurationHandler handler = new BucketLoggingConfigurationHandler(); parseXmlInputStream(handler, inputStream); return handler; } public String parseBucketLocationResponse(InputStream inputStream) throws AmazonClientException { BucketLocationHandler handler = new BucketLocationHandler(); parseXmlInputStream(handler, inputStream); return handler.getLocation(); } public BucketVersioningConfigurationHandler parseVersioningConfigurationResponse(InputStream inputStream) throws AmazonClientException { BucketVersioningConfigurationHandler handler = new BucketVersioningConfigurationHandler(); parseXmlInputStream(handler, inputStream); return handler; } public BucketNotificationConfigurationHandler parseNotificationConfigurationResponse(InputStream inputStream) throws AmazonClientException { BucketNotificationConfigurationHandler handler = new BucketNotificationConfigurationHandler(); parseXmlInputStream(handler, inputStream); return handler; } public CopyObjectResultHandler parseCopyObjectResponse(InputStream inputStream) throws AmazonClientException { CopyObjectResultHandler handler = new CopyObjectResultHandler(); parseXmlInputStream(handler, inputStream); return handler; } public CompleteMultipartUploadHandler parseCompleteMultipartUploadResponse(InputStream inputStream) throws AmazonClientException { CompleteMultipartUploadHandler handler = new CompleteMultipartUploadHandler(); parseXmlInputStream(handler, inputStream); return handler; } public InitiateMultipartUploadHandler parseInitiateMultipartUploadResponse(InputStream inputStream) throws AmazonClientException { InitiateMultipartUploadHandler handler = new InitiateMultipartUploadHandler(); parseXmlInputStream(handler, inputStream); return handler; } public ListMultipartUploadsHandler parseListMultipartUploadsResponse(InputStream inputStream) throws AmazonClientException { ListMultipartUploadsHandler handler = new ListMultipartUploadsHandler(); parseXmlInputStream(handler, inputStream); return handler; } public ListPartsHandler parseListPartsResponse(InputStream inputStream) throws AmazonClientException { ListPartsHandler handler = new ListPartsHandler(); parseXmlInputStream(handler, inputStream); return handler; } /** * @param inputStream * * @return true if the bucket's is configured as Requester Pays, false if it * is configured as Owner pays. * * @throws AmazonClientException */ public boolean parseRequestPaymentConfigurationResponse(InputStream inputStream) throws AmazonClientException { RequestPaymentConfigurationHandler handler = new RequestPaymentConfigurationHandler(); parseXmlInputStream(handler, inputStream); return handler.isRequesterPays(); } // //////////// // Handlers // // //////////// /** * Handler for ListBucket response XML documents. The document is parsed * into {@link S3Object}s available via the {@link #getObjects()} method. */ public class ListBucketHandler extends DefaultHandler { private S3ObjectSummary currentObject = null; private Owner currentOwner = null; private StringBuilder currText = null; private boolean insideCommonPrefixes = false; private ObjectListing objectListing = new ObjectListing(); private List<String> commonPrefixes = new ArrayList<String>(); // Listing properties. private String bucketName = null; private String requestPrefix = null; private String requestMarker = null; private int requestMaxKeys = 0; private String requestDelimiter = null; private boolean listingTruncated = false; private String lastKey = null; private String nextMarker = null; public ListBucketHandler() { super(); this.currText = new StringBuilder(); } public ObjectListing getObjectListing() { objectListing.setBucketName(bucketName); objectListing.setCommonPrefixes(commonPrefixes); objectListing.setDelimiter(requestDelimiter); objectListing.setMarker(requestMarker); objectListing.setMaxKeys(requestMaxKeys); objectListing.setPrefix(requestPrefix); objectListing.setTruncated(listingTruncated); /* * S3 only includes the NextMarker XML element if the request * specified a delimiter, but for consistency we'd like to always * give easy access to the next marker if we're returning a list of * results that's truncated. */ if (nextMarker != null) { objectListing.setNextMarker(nextMarker); } else if (listingTruncated) { String nextMarker = objectListing.getObjectSummaries().get(objectListing.getObjectSummaries().size() - 1).getKey(); objectListing.setNextMarker(nextMarker); } return objectListing; } /** * If the listing is truncated this method will return the marker that * should be used in subsequent bucket list calls to complete the * listing. * * @return null if the listing is not truncated, otherwise the next * marker if it's available or the last object key seen if the * next marker isn't available. */ public String getMarkerForNextListing() { if (listingTruncated) { if (nextMarker != null) { return nextMarker; } else if (lastKey != null) { return lastKey; } else { if (log.isWarnEnabled()) { log.warn("Unable to find Next Marker or Last Key for truncated listing"); } return null; } } else { return null; } } /** * @return true if the listing document was truncated, and therefore * only contained a subset of the available S3 objects. */ public boolean isListingTruncated() { return listingTruncated; } /** * @return the S3 objects contained in the listing. */ public List<S3ObjectSummary> getObjects() { return objectListing.getObjectSummaries(); } public String[] getCommonPrefixes() { return (String[]) commonPrefixes.toArray(new String[commonPrefixes.size()]); } public String getRequestPrefix() { return requestPrefix; } public String getRequestMarker() { return requestMarker; } public String getNextMarker() { return nextMarker; } public long getRequestMaxKeys() { return requestMaxKeys; } public void startDocument() { } public void endDocument() { } public void startElement(String uri, String name, String qName, Attributes attrs) { if (name.equals("Contents")) { currentObject = new S3ObjectSummary(); currentObject.setBucketName(bucketName); } else if (name.equals("Owner")) { currentOwner = new Owner(); currentObject.setOwner(currentOwner); } else if (name.equals("CommonPrefixes")) { insideCommonPrefixes = true; } } public void endElement(String uri, String name, String qName) { String elementText = this.currText.toString(); // Listing details if (name.equals("Name")) { bucketName = elementText; if (log.isDebugEnabled()) { log.debug("Examining listing for bucket: " + bucketName); } } else if (!insideCommonPrefixes && name.equals("Prefix")) { requestPrefix = checkForEmptyString(elementText); } else if (name.equals("Marker")) { requestMarker = checkForEmptyString(elementText); } else if (name.equals("NextMarker")) { nextMarker = elementText; } else if (name.equals("MaxKeys")) { requestMaxKeys = parseInt(elementText); } else if (name.equals("Delimiter")) { requestDelimiter = checkForEmptyString(elementText); } else if (name.equals("IsTruncated")) { String isTruncatedStr = elementText.toLowerCase(Locale.getDefault()); if (isTruncatedStr.startsWith("false")) { listingTruncated = false; } else if (isTruncatedStr.startsWith("true")) { listingTruncated = true; } else { throw new RuntimeException("Invalid value for IsTruncated field: " + isTruncatedStr); } } // Object details. else if (name.equals("Contents")) { objectListing.getObjectSummaries().add(currentObject); if (log.isDebugEnabled()) { log.debug("Created new S3Object from listing: " + currentObject); } } else if (name.equals("Key")) { currentObject.setKey(elementText); lastKey = elementText; } else if (name.equals("LastModified")) { try { currentObject.setLastModified(ServiceUtils.parseIso8601Date(elementText)); } catch (ParseException e) { throw new RuntimeException( "Non-ISO8601 date for LastModified in bucket's object listing output: " + elementText, e); } } else if (name.equals("ETag")) { currentObject.setETag(ServiceUtils.removeQuotes(elementText)); } else if (name.equals("Size")) { currentObject.setSize(parseLong(elementText)); } else if (name.equals("StorageClass")) { currentObject.setStorageClass(elementText); } // Owner details. else if (name.equals("ID")) { if (currentOwner == null) { currentOwner = new Owner(); currentObject.setOwner(currentOwner); } currentOwner.setId(elementText); } else if (name.equals("DisplayName")) { currentOwner.setDisplayName(elementText); } // Common prefixes. else if (insideCommonPrefixes && name.equals("Prefix")) { commonPrefixes.add(elementText); } else if (name.equals("CommonPrefixes")) { insideCommonPrefixes = false; } this.currText = new StringBuilder(); } public void characters(char ch[], int start, int length) { this.currText.append(ch, start, length); } } /** * Handler for ListAllMyBuckets response XML documents. The document is * parsed into {@link Bucket}s available via the {@link #getBuckets()} * method. */ public class ListAllMyBucketsHandler extends DefaultHandler { private Owner bucketsOwner = null; private Bucket currentBucket = null; private StringBuilder currText = null; private List<Bucket> buckets = null; public ListAllMyBucketsHandler() { super(); buckets = new ArrayList<Bucket>(); this.currText = new StringBuilder(); } /** * @return the buckets listed in the document. */ public List<Bucket> getBuckets() { return buckets; } /** * @return the owner of the buckets. */ public Owner getOwner() { return bucketsOwner; } public void startDocument() { } public void endDocument() { } public void startElement(String uri, String name, String qName, Attributes attrs) { if (name.equals("Bucket")) { currentBucket = new Bucket(); } else if (name.equals("Owner")) { bucketsOwner = new Owner(); } } public void endElement(String uri, String name, String qName) { String elementText = this.currText.toString(); // Listing details. if (name.equals("ID")) { bucketsOwner.setId(elementText); } else if (name.equals("DisplayName")) { bucketsOwner.setDisplayName(elementText); } // Bucket item details. else if (name.equals("Bucket")) { if (log.isDebugEnabled()) { log.debug("Created new bucket from listing: " + currentBucket); } currentBucket.setOwner(bucketsOwner); buckets.add(currentBucket); } else if (name.equals("Name")) { currentBucket.setName(elementText); } else if (name.equals("CreationDate")) { elementText += ".000Z"; try { currentBucket.setCreationDate(ServiceUtils.parseIso8601Date(elementText)); } catch (ParseException e) { throw new RuntimeException( "Non-ISO8601 date for CreationDate in list buckets output: " + elementText, e); } } this.currText = new StringBuilder(); } public void characters(char ch[], int start, int length) { this.currText.append(ch, start, length); } } /** * Handler for AccessControlList response XML documents. The document is * parsed into an {@link AccessControlList} object available via the * {@link #getAccessControlList()} method. */ public class AccessControlListHandler extends DefaultHandler { private AccessControlList accessControlList = null; private Owner owner = null; private Grantee currentGrantee = null; private Permission currentPermission = null; private StringBuilder currText = null; private boolean insideACL = false; public AccessControlListHandler() { super(); this.currText = new StringBuilder(); } /** * @return an object representing the ACL document. */ public AccessControlList getAccessControlList() { return accessControlList; } public void startDocument() { } public void endDocument() { } public void startElement(String uri, String name, String qName, Attributes attrs) { if (name.equals("Owner")) { owner = new Owner(); } else if (name.equals("AccessControlList")) { accessControlList = new AccessControlList(); accessControlList.setOwner(owner); insideACL = true; } else if (name.equals("Grantee")) { if ("AmazonCustomerByEmail".equals(attrs.getValue("xsi:type"))) { currentGrantee = new EmailAddressGrantee(null); } else if ("CanonicalUser".equals(attrs.getValue("xsi:type"))) { currentGrantee = new CanonicalGrantee(null); } else if ("Group".equals(attrs.getValue("xsi:type"))) { /* * Nothing to do for GroupGrantees here since we * can't construct an empty enum value early. */ } } } public void endElement(String uri, String name, String qName) { String elementText = this.currText.toString(); // Owner details. if (name.equals("ID") && !insideACL) { owner.setId(elementText); } else if (name.equals("DisplayName") && !insideACL) { owner.setDisplayName(elementText); } // ACL details. else if (name.equals("ID")) { currentGrantee.setIdentifier(elementText); } else if (name.equals("EmailAddress")) { currentGrantee.setIdentifier(elementText); } else if (name.equals("URI")) { /* * Only GroupGrantees contain an URI element in them, and we * can't construct currentGrantee during startElement for a * GroupGrantee since it's an enum. */ currentGrantee = GroupGrantee.parseGroupGrantee(elementText); } else if (name.equals("DisplayName")) { ((CanonicalGrantee) currentGrantee).setDisplayName(elementText); } else if (name.equals("Permission")) { currentPermission = Permission.parsePermission(elementText); } else if (name.equals("Grant")) { accessControlList.grantPermission(currentGrantee, currentPermission); currentGrantee = null; currentPermission = null; } else if (name.equals("AccessControlList")) { insideACL = false; } this.currText = new StringBuilder(); } public void characters(char ch[], int start, int length) { this.currText.append(ch, start, length); } } /** * Handler for LoggingStatus response XML documents for a bucket. The * document is parsed into an {@link BucketLoggingConfiguration} object available * via the {@link #getBucketLoggingConfiguration()} method. */ public class BucketLoggingConfigurationHandler extends DefaultHandler { private BucketLoggingConfiguration bucketLoggingConfiguration = null; private String targetBucket = null; private String targetPrefix = null; private StringBuilder currText = null; public BucketLoggingConfigurationHandler() { this.currText = new StringBuilder(); } /** * @return * an object representing the bucket's LoggingStatus document. */ public BucketLoggingConfiguration getBucketLoggingConfiguration() { return bucketLoggingConfiguration; } public void startDocument() { } public void endDocument() { } public void startElement(String uri, String name, String qName, Attributes attrs) { if (name.equals("BucketLoggingStatus")) { bucketLoggingConfiguration = new BucketLoggingConfiguration(); } } public void endElement(String uri, String name, String qName) { String elementText = this.currText.toString(); if (name.equals("TargetBucket")) { targetBucket = elementText; } else if (name.equals("TargetPrefix")) { targetPrefix = elementText; } else if (name.equals("LoggingEnabled")) { bucketLoggingConfiguration.setDestinationBucketName(targetBucket); bucketLoggingConfiguration.setLogFilePrefix(targetPrefix); } this.currText = new StringBuilder(); } public void characters(char ch[], int start, int length) { this.currText.append(ch, start, length); } } /** * Handler for CreateBucketConfiguration response XML documents for a * bucket. The document is parsed into a String representing the bucket's * location, available via the {@link #getLocation()} method. */ public class BucketLocationHandler extends DefaultHandler { private String location = null; private StringBuilder currText = null; public BucketLocationHandler() { super(); this.currText = new StringBuilder(); } /** * @return * the bucket's location. */ public String getLocation() { return location; } public void startDocument() { } public void endDocument() { } public void startElement(String uri, String name, String qName, Attributes attrs) { if (name.equals("CreateBucketConfiguration")) { } } public void endElement(String uri, String name, String qName) { String elementText = this.currText.toString(); if (name.equals("LocationConstraint")) { if (elementText.length() == 0) { location = null; } else { location = elementText; } } this.currText = new StringBuilder(); } public void characters(char ch[], int start, int length) { this.currText.append(ch, start, length); } } public class CopyObjectResultHandler extends DefaultHandler { // Data items for successful copy private String etag = null; private Date lastModified = null; private String versionId = null; // Data items for failed copy private String errorCode = null; private String errorMessage = null; private String errorRequestId = null; private String errorHostId = null; private boolean receivedErrorResponse = false; private StringBuilder currText = null; public CopyObjectResultHandler() { super(); this.currText = new StringBuilder(); } public Date getLastModified() { return lastModified; } public String getVersionId() { return versionId; } public void setVersionId(String versionId) { this.versionId = versionId; } public String getETag() { return etag; } public String getErrorCode() { return errorCode; } public String getErrorHostId() { return errorHostId; } public String getErrorMessage() { return errorMessage; } public String getErrorRequestId() { return errorRequestId; } public boolean isErrorResponse() { return receivedErrorResponse; } public void startDocument() { } public void endDocument() { } public void startElement(String uri, String name, String qName, Attributes attrs) { if (name.equals("CopyObjectResult")) { receivedErrorResponse = false; } else if (name.equals("Error")) { receivedErrorResponse = true; } } public void endElement(String uri, String name, String qName) { String elementText = this.currText.toString(); if (name.equals("LastModified")) { try { lastModified = ServiceUtils.parseIso8601Date(elementText); } catch (ParseException e) { throw new RuntimeException( "Non-ISO8601 date for LastModified in copy object output: " + elementText, e); } } else if (name.equals("ETag")) { etag = ServiceUtils.removeQuotes(elementText); } else if (name.equals("Code")) { errorCode = elementText; } else if (name.equals("Message")) { errorMessage = elementText; } else if (name.equals("RequestId")) { errorRequestId = elementText; } else if (name.equals("HostId")) { errorHostId = elementText; } this.currText = new StringBuilder(); } public void characters(char ch[], int start, int length) { this.currText.append(ch, start, length); } } /** * Handler for RequestPaymentConfiguration response XML documents for a * bucket. The document is parsed into a boolean value: true if the bucket's * is configured as Requester Pays, false if it is configured as Owner pays. * This boolean value is available via the {@link #isRequesterPays()} * method. */ public class RequestPaymentConfigurationHandler extends DefaultHandler { private String payer = null; private StringBuilder currText = null; public RequestPaymentConfigurationHandler() { super(); this.currText = new StringBuilder(); } /** * @return true if the bucket's is configured as Requester Pays, false * if it is configured as Owner pays. */ public boolean isRequesterPays() { return "Requester".equals(payer); } public void startDocument() { } public void endDocument() { } public void startElement(String uri, String name, String qName, Attributes attrs) { if (name.equals("RequestPaymentConfiguration")) { } } public void endElement(String uri, String name, String qName) { String elementText = this.currText.toString(); if (name.equals("Payer")) { payer = elementText; } this.currText = new StringBuilder(); } public void characters(char ch[], int start, int length) { this.currText.append(ch, start, length); } } /** * Handler for ListVersionsResult XML document. */ public class ListVersionsHandler extends DefaultHandler { private VersionListing versionListing; private List<S3VersionSummary> versionSummaries; private S3VersionSummary currentVersionSummary; private Owner owner; private StringBuilder text; private boolean insideCommonPrefixes = false; public VersionListing getListing() { return versionListing; } @Override public void startDocument() { versionListing = new VersionListing(); versionSummaries = new ArrayList<S3VersionSummary>(); text = new StringBuilder(); } @Override public void startElement(String uri, String name, String qName, Attributes attrs) { if (name.equals("ListVersionsResult")) { } else if (name.equals("CommonPrefixes")) { insideCommonPrefixes = true; } else if (name.equals("Name")) { } else if (name.equals("Prefix")) { } else if (name.equals("KeyMarker")) { } else if (name.equals("VersionIdMarker")) { } else if (name.equals("MaxKeys")) { } else if (name.equals("NextKeyMarker")) { } else if (name.equals("NextVersionIdMarker")) { } else if (name.equals("IsTruncated")) { } else if (name.equals("Version")) { currentVersionSummary = new S3VersionSummary(); currentVersionSummary.setBucketName(versionListing.getBucketName()); } else if (name.equals("DeleteMarker")) { currentVersionSummary = new S3VersionSummary(); currentVersionSummary.setBucketName(versionListing.getBucketName()); currentVersionSummary.setIsDeleteMarker(true); } else if (name.equals("Key")) { } else if (name.equals("VersionId")) { } else if (name.equals("IsLatest")) { } else if (name.equals("LastModified")) { } else if (name.equals("ETag")) { } else if (name.equals("Size")) { } else if (name.equals("Owner")) { this.owner = new Owner(); } else if (name.equals("StorageClass")) { } else if (name.equals("ID")) { } else if (name.equals("DisplayName")) { } else { log.error("Ignoring unexpected tag <"+name+">"); } text.setLength(0); } @Override public void endElement(String uri, String name, String qName) throws SAXException { if (name.equals("ListVersionsResult")) { versionListing.setVersionSummaries(versionSummaries); } else if (name.equals("Name")) { versionListing.setBucketName(text.toString()); } else if (!insideCommonPrefixes && name.equals("Prefix")) { versionListing.setPrefix(checkForEmptyString(text.toString())); } else if (insideCommonPrefixes && name.equals("Prefix")) { versionListing.getCommonPrefixes().add(checkForEmptyString(text.toString())); } else if (name.equals("CommonPrefixes")) { insideCommonPrefixes = false; } else if (name.equals("KeyMarker")) { versionListing.setKeyMarker(checkForEmptyString(text.toString())); } else if (name.equals("VersionIdMarker")) { versionListing.setVersionIdMarker(checkForEmptyString(text.toString())); } else if (name.equals("MaxKeys")) { versionListing.setMaxKeys(Integer.parseInt(text.toString())); } else if (name.equals("Delimiter")) { versionListing.setDelimiter(checkForEmptyString(text.toString())); } else if (name.equals("NextKeyMarker")) { versionListing.setNextKeyMarker(text.toString()); } else if (name.equals("NextVersionIdMarker")) { versionListing.setNextVersionIdMarker(text.toString()); } else if (name.equals("IsTruncated")) { versionListing.setTruncated("true".equals(text.toString())); } else if (name.equals("Version")) { assert(currentVersionSummary != null); versionSummaries.add(currentVersionSummary); currentVersionSummary = null; } else if (name.equals("DeleteMarker")) { assert(currentVersionSummary != null); versionSummaries.add(currentVersionSummary); currentVersionSummary = null; } else if (name.equals("Key")) { assert(currentVersionSummary != null); currentVersionSummary.setKey(text.toString()); } else if (name.equals("VersionId")) { assert(currentVersionSummary != null); currentVersionSummary.setVersionId(text.toString()); } else if (name.equals("IsLatest")) { assert(currentVersionSummary != null); currentVersionSummary.setIsLatest("true".equals(text.toString())); } else if (name.equals("LastModified")) { assert(currentVersionSummary != null); try { currentVersionSummary.setLastModified(ServiceUtils.parseIso8601Date(text.toString())); } catch (ParseException e) { throw new SAXException( "Non-ISO8601 date for LastModified in copy object output: " + text.toString(), e); } } else if (name.equals("ETag")) { assert(currentVersionSummary != null); assert(!currentVersionSummary.isDeleteMarker()); currentVersionSummary.setETag(ServiceUtils.removeQuotes(text.toString())); } else if (name.equals("Size")) { assert(currentVersionSummary != null); assert(!currentVersionSummary.isDeleteMarker()); currentVersionSummary.setSize(Long.parseLong(text.toString())); } else if (name.equals("Owner")) { currentVersionSummary.setOwner(owner); owner = null; } else if (name.equals("StorageClass")) { assert(currentVersionSummary != null); assert(!currentVersionSummary.isDeleteMarker()); currentVersionSummary.setStorageClass(text.toString()); } else if (name.equals("ID")) { assert(owner != null); owner.setId(text.toString()); } else if (name.equals("DisplayName")) { assert(owner != null); owner.setDisplayName(text.toString()); } else { log.error("Ignoring unexpected tag <"+name+">"); } text.setLength(0); } @Override public void characters(char ch[], int start, int length) { this.text.append(ch, start, length); } } public class BucketVersioningConfigurationHandler extends DefaultHandler { private BucketVersioningConfiguration configuration = new BucketVersioningConfiguration(); private StringBuilder text; public BucketVersioningConfiguration getConfiguration() { return configuration; } @Override public void startDocument() { text = new StringBuilder(); } @Override public void startElement(String uri, String name, String qName, Attributes attrs) { if (name.equals("VersioningConfiguration")) { } else if (name.equals("Status")) { text.setLength(0); } else if (name.equals("MfaDelete")) { text.setLength(0); } else { log.error("Ignoring unexpected tag <"+name+">"); } } @Override public void endElement(String uri, String name, String qName) throws SAXException { if (name.equals("Status")) { configuration.setStatus(text.toString()); } else if (name.equals("MfaDelete")) { String mfaDeleteStatus = text.toString(); if (mfaDeleteStatus.equals("Disabled")) { configuration.setMfaDeleteEnabled(false); } else if (mfaDeleteStatus.equals("Enabled")) { configuration.setMfaDeleteEnabled(true); } else { configuration.setMfaDeleteEnabled(null); } } text.setLength(0); } @Override public void characters(char ch[], int start, int length) { this.text.append(ch, start, length); } } /* * <?xml version="1.0" encoding="UTF-8"?> * <CompleteMultipartUploadResult xmlns="http://s3.amazonaws.com/doc/2006-03-01/"> * <Location>http://Example-Bucket.s3.amazonaws.com/Example-Object</Location> * <Bucket>Example-Bucket</Bucket> * <Key>Example-Object</Key> * <ETag>"3858f62230ac3c915f300c664312c11f-9"</ETag> * </CompleteMultipartUploadResult> * * Or if an error occurred while completing: * * <?xml version="1.0" encoding="UTF-8"?> * <Error> * <Code>InternalError</Code> * <Message>We encountered an internal error. Please try again.</Message> * <RequestId>656c76696e6727732072657175657374</RequestId> * <HostId>Uuag1LuByRx9e6j5Onimru9pO4ZVKnJ2Qz7/C1NPcfTWAtRPfTaOFg==</HostId> * </Error> */ public class CompleteMultipartUploadHandler extends DefaultHandler { private StringBuilder text; // Successful completion private CompleteMultipartUploadResult result; // Error during completion private AmazonS3Exception ase; private String hostId; private String requestId; private String errorCode; public CompleteMultipartUploadResult getCompleteMultipartUploadResult() { return result; } public AmazonS3Exception getAmazonS3Exception() { return ase; } @Override public void startDocument() { text = new StringBuilder(); } @Override public void startElement(String uri, String name, String qName, Attributes attrs) { // Success response XML elements if (name.equals("CompleteMultipartUploadResult")) { result = new CompleteMultipartUploadResult(); } else if (name.equals("Location")) { } else if (name.equals("Bucket")) { } else if (name.equals("Key")) { } else if (name.equals("ETag")) { } // Error response XML elements if (name.equals("Error")) { } else if (name.equals("Code")) { } else if (name.equals("Message")) { } else if (name.equals("RequestId")) { } else if (name.equals("HostId")) { } text.setLength(0); } @Override public void endElement(String uri, String name, String qName) throws SAXException { if (result != null) { // Success response XML elements if (name.equals("CompleteMultipartUploadResult")) { } else if (name.equals("Location")) { result.setLocation(text.toString()); } else if (name.equals("Bucket")) { result.setBucketName(text.toString()); } else if (name.equals("Key")) { result.setKey(text.toString()); } else if (name.equals("ETag")) { result.setETag(ServiceUtils.removeQuotes(text.toString())); } } else { // Error response XML elements if (name.equals("Error")) { ase.setErrorCode(errorCode); ase.setRequestId(requestId); ase.setExtendedRequestId(hostId); } else if (name.equals("Code")) { errorCode = text.toString(); } else if (name.equals("Message")) { ase = new AmazonS3Exception(text.toString()); } else if (name.equals("RequestId")) { requestId = text.toString(); } else if (name.equals("HostId")) { hostId = text.toString(); } } } @Override public void characters(char ch[], int start, int length) { this.text.append(ch, start, length); } } /* * <?xml version="1.0" encoding="UTF-8"?> * <InitiateMultipartUploadResult xmlns="http://s3.amazonaws.com/doc/2006-03-01/"> * <Bucket>example-bucket</Bucket> * <Key>example-object</Key> * <UploadId>VXBsb2FkIElEIGZvciA2aWWpbmcncyBteS1tb3ZpZS5tMnRzIHVwbG9hZA</UploadId> * </InitiateMultipartUploadResult> */ public class InitiateMultipartUploadHandler extends DefaultHandler { private StringBuilder text; private InitiateMultipartUploadResult result; public InitiateMultipartUploadResult getInitiateMultipartUploadResult() { return result; } @Override public void startDocument() { text = new StringBuilder(); } @Override public void startElement(String uri, String name, String qName, Attributes attrs) { if (name.equals("InitiateMultipartUploadResult")) { result = new InitiateMultipartUploadResult(); } else if (name.equals("Bucket")) { } else if (name.equals("Key")) { } else if (name.equals("UploadId")) { } text.setLength(0); } @Override public void endElement(String uri, String name, String qName) throws SAXException { if (name.equals("InitiateMultipartUploadResult")) { } else if (name.equals("Bucket")) { result.setBucketName(text.toString()); } else if (name.equals("Key")) { result.setKey(text.toString()); } else if (name.equals("UploadId")) { result.setUploadId(text.toString()); } } @Override public void characters(char ch[], int start, int length) { this.text.append(ch, start, length); } } /* * HTTP/1.1 200 OK * x-amz-id-2: Uuag1LuByRx9e6j5Onimru9pO4ZVKnJ2Qz7/C1NPcfTWAtRPfTaOFg== * x-amz-request-id: 656c76696e6727732072657175657374 * Date: Tue, 16 Feb 2010 20:34:56 GMT * Content-Length: 1330 * Connection: keep-alive * Server: AmazonS3 * * <?xml version="1.0" encoding="UTF-8"?> * <ListMultipartUploadsResult xmlns="http://s3.amazonaws.com/doc/2006-03-01/"> * <Bucket>bucket</Bucket> * <KeyMarker></KeyMarker> * <UploadIdMarker></UploadIdMarker> * <NextKeyMarker>my-movie.m2ts</NextKeyMarker> * <NextUploadIdMarker>YW55IGlkZWEgd2h5IGVsdmluZydzIHVwbG9hZCBmYWlsZWQ</NextUploadIdMarker> * <MaxUploads>3</MaxUploads> * <IsTruncated>true</IsTruncated> * <Upload> * <Key>my-divisor</Key> * <UploadId>XMgbGlrZSBlbHZpbmcncyBub3QgaGF2aW5nIG11Y2ggbHVjaw</UploadId> * <Owner> * <ID>b1d16700c70b0b05597d7acd6a3f92be</ID> * <DisplayName>delving</DisplayName> * </Owner> * <StorageClass>STANDARD</StorageClass> * <Initiated>Tue, 26 Jan 2010 19:42:19 GMT</Initiated> * </Upload> * <Upload> * <Key>my-movie.m2ts</Key> * <UploadId>VXBsb2FkIElEIGZvciBlbHZpbmcncyBteS1tb3ZpZS5tMnRzIHVwbG9hZA</UploadId> * <Owner> * <ID>b1d16700c70b0b05597d7acd6a3f92be</ID> * <DisplayName>delving</DisplayName> * </Owner> * <StorageClass>STANDARD</StorageClass> * <Initiated>Tue, 16 Feb 2010 20:34:56 GMT</Initiated> * </Upload> * <Upload> * <Key>my-movie.m2ts</Key> * <UploadId>YW55IGlkZWEgd2h5IGVsdmluZydzIHVwbG9hZCBmYWlsZWQ</UploadId> * <Owner> * <ID>b1d16700c70b0b05597d7acd6a3f92be</ID> * <DisplayName>delving</DisplayName> * </Owner> * <StorageClass>STANDARD</StorageClass> * <Initiated>Wed, 27 Jan 2010 03:02:01 GMT</Initiated> * </Upload> * </ListMultipartUploadsResult> */ public class ListMultipartUploadsHandler extends DefaultHandler { private StringBuilder text; private MultipartUploadListing result; private MultipartUpload currentMultipartUpload; private Owner currentOwner; private Owner currentInitiator; public MultipartUploadListing getListMultipartUploadsResult() { return result; } @Override public void startDocument() { text = new StringBuilder(); } @Override public void startElement(String uri, String name, String qName, Attributes attrs) { if (name.equals("ListMultipartUploadsResult")) { result = new MultipartUploadListing(); } else if (name.equals("Bucket")) { } else if (name.equals("KeyMarker")) { } else if (name.equals("UploadIdMarker")) { } else if (name.equals("NextKeyMarker")) { } else if (name.equals("NextUploadIdMarker")) { } else if (name.equals("MaxUploads")) { } else if (name.equals("IsTruncated")) { } else if (name.equals("Upload")) { currentMultipartUpload = new MultipartUpload(); } else if (name.equals("Key")) { } else if (name.equals("UploadId")) { } else if (name.equals("Owner")) { currentOwner = new Owner(); } else if (name.equals("Initiator")) { currentInitiator = new Owner(); } else if (name.equals("ID")) { } else if (name.equals("DisplayName")) { } else if (name.equals("StorageClass")) { } else if (name.equals("Initiated")) { } text.setLength(0); } @Override public void endElement(String uri, String name, String qName) throws SAXException { if (name.equals("ListMultipartUploadsResult")) { } else if (name.equals("Bucket")) { result.setBucketName(text.toString()); } else if (name.equals("KeyMarker")) { result.setKeyMarker(checkForEmptyString(text.toString())); } else if (name.equals("UploadIdMarker")) { result.setUploadIdMarker(checkForEmptyString(text.toString())); } else if (name.equals("NextKeyMarker")) { result.setNextKeyMarker(checkForEmptyString(text.toString())); } else if (name.equals("NextUploadIdMarker")) { result.setNextUploadIdMarker(checkForEmptyString(text.toString())); } else if (name.equals("MaxUploads")) { result.setMaxUploads(Integer.parseInt(text.toString())); } else if (name.equals("IsTruncated")) { result.setTruncated(Boolean.parseBoolean(text.toString())); } else if (name.equals("Upload")) { result.getMultipartUploads().add(currentMultipartUpload); } else if (name.equals("Key")) { currentMultipartUpload.setKey(text.toString()); } else if (name.equals("UploadId")) { currentMultipartUpload.setUploadId(text.toString()); } else if (name.equals("Owner")) { currentMultipartUpload.setOwner(currentOwner); currentOwner = null; } else if (name.equals("Initiator")) { currentMultipartUpload.setInitiator(currentInitiator); currentInitiator = null; } else if (name.equals("ID") && currentOwner != null) { currentOwner.setId(checkForEmptyString(text.toString())); } else if (name.equals("DisplayName") && currentOwner != null) { currentOwner.setDisplayName(checkForEmptyString(text.toString())); } else if (name.equals("ID") && currentInitiator != null) { currentInitiator.setId(checkForEmptyString(text.toString())); } else if (name.equals("DisplayName") && currentInitiator != null) { currentInitiator.setDisplayName(checkForEmptyString(text.toString())); } else if (name.equals("StorageClass")) { currentMultipartUpload.setStorageClass(text.toString()); } else if (name.equals("Initiated")) { try { currentMultipartUpload.setInitiated(ServiceUtils.parseIso8601Date(text.toString())); } catch (ParseException e) { throw new SAXException( "Non-ISO8601 date for Initiated in initiate multipart upload result: " + text.toString(), e); } } } @Override public void characters(char ch[], int start, int length) { this.text.append(ch, start, length); } } /* * HTTP/1.1 200 OK * x-amz-id-2: Uuag1LuByRx9e6j5Onimru9pO4ZVKnJ2Qz7/C1NPcfTWAtRPfTaOFg== * x-amz-request-id: 656c76696e6727732072657175657374 * Date: Tue, 16 Feb 2010 20:34:56 GMT * Content-Length: 985 * Connection: keep-alive * Server: AmazonS3 * * <?xml version="1.0" encoding="UTF-8"?> * <ListPartsResult xmlns="http://s3.amazonaws.com/doc/2006-03-01/"> * <Bucket>example-bucket</Bucket> * <Key>example-object</Key> * <UploadId>XXBsb2FkIElEIGZvciBlbHZpbmcncyVcdS1tb3ZpZS5tMnRzEEEwbG9hZA</UploadId> * <Owner> * <ID>x1x16700c70b0b05597d7ecd6a3f92be</ID> * <DisplayName>username</DisplayName> * </Owner> * <Initiator> * <ID>x1x16700c70b0b05597d7ecd6a3f92be</ID> * <DisplayName>username</DisplayName> * </Initiator> * <StorageClass>STANDARD</StorageClass> * <PartNumberMarker>1</PartNumberMarker> * <NextPartNumberMarker>3</NextPartNumberMarker> * <MaxParts>2</MaxParts> * <IsTruncated>true</IsTruncated> * <Part> * <PartNumber>2</PartNumber> * <LastModified>Wed, 27 Jan 2010 03:02:03 GMT</LastModified> * <ETag>"7778aef83f66abc1fa1e8477f296d394"</ETag> * <Size>10485760</Size> * </Part> * <Part> * <PartNumber>3</PartNumber> * <LastModified>Wed, 27 Jan 2010 03:02:02 GMT</LastModified> * <ETag>"aaaa18db4cc2f85cedef654fccc4a4x8"</ETag> * <Size>10485760</Size> * </Part> * </ListPartsResult> */ public class ListPartsHandler extends DefaultHandler { private StringBuilder text; private PartListing result; private Owner currentOwner; private Owner currentInitiator; private PartSummary currentPart; public PartListing getListPartsResult() { return result; } @Override public void startDocument() { text = new StringBuilder(); } @Override public void startElement(String uri, String name, String qName, Attributes attrs) { if (name.equals("ListPartsResult")) { result = new PartListing(); } else if (name.equals("Bucket")) { } else if (name.equals("Key")) { } else if (name.equals("UploadId")) { } else if (name.equals("Owner")) { currentOwner = new Owner(); } else if (name.equals("Initiator")) { currentInitiator = new Owner(); } else if (name.equals("ID")) { } else if (name.equals("DisplayName")) { } else if (name.equals("StorageClass")) { } else if (name.equals("PartNumberMarker")) { } else if (name.equals("NextPartNumberMarker")) { } else if (name.equals("MaxParts")) { } else if (name.equals("IsTruncated")) { } else if (name.equals("Part")) { currentPart = new PartSummary(); } else if (name.equals("PartNumber")) { } else if (name.equals("LastModified")) { } else if (name.equals("ETag")) { } else if (name.equals("Size")) { } text.setLength(0); } private Integer parseInteger(String text) { text = checkForEmptyString(text.toString()); if (text == null) return null; return Integer.parseInt(text); } @Override public void endElement(String uri, String name, String qName) throws SAXException { if (name.equals("ListPartsResult")) { } else if (name.equals("Bucket")) { result.setBucketName(text.toString()); } else if (name.equals("Key")) { result.setKey(text.toString()); } else if (name.equals("UploadId")) { result.setUploadId(text.toString()); } else if (name.equals("Owner")) { result.setOwner(currentOwner); currentOwner = null; } else if (name.equals("Initiator")) { result.setInitiator(currentInitiator); currentInitiator = null; } else if (name.equals("ID") && currentOwner != null) { currentOwner.setId(checkForEmptyString(text.toString())); } else if (name.equals("DisplayName") && currentOwner != null) { currentOwner.setDisplayName(checkForEmptyString(text.toString())); } else if (name.equals("ID") && currentInitiator != null) { currentInitiator.setId(checkForEmptyString(text.toString())); } else if (name.equals("DisplayName") && currentInitiator != null) { currentInitiator.setDisplayName(checkForEmptyString(text.toString())); } else if (name.equals("StorageClass")) { result.setStorageClass(text.toString()); } else if (name.equals("PartNumberMarker")) { result.setPartNumberMarker(parseInteger(text.toString())); } else if (name.equals("NextPartNumberMarker")) { result.setNextPartNumberMarker(parseInteger(text.toString())); } else if (name.equals("MaxParts")) { result.setMaxParts(parseInteger(text.toString())); } else if (name.equals("IsTruncated")) { result.setTruncated(Boolean.parseBoolean(text.toString())); } else if (name.equals("Part")) { result.getParts().add(currentPart); } else if (name.equals("PartNumber")) { currentPart.setPartNumber(Integer.parseInt(text.toString())); } else if (name.equals("LastModified")) { try { currentPart.setLastModified(ServiceUtils.parseIso8601Date(text.toString())); } catch (ParseException e) { throw new SAXException( "Non-ISO8601 date for LastModified in list parts result: " + text.toString(), e); } } else if (name.equals("ETag")) { currentPart.setETag(ServiceUtils.removeQuotes(text.toString())); } else if (name.equals("Size")) { currentPart.setSize(Long.parseLong(text.toString())); } } @Override public void characters(char ch[], int start, int length) { this.text.append(ch, start, length); } } public class BucketNotificationConfigurationHandler extends DefaultHandler { private BucketNotificationConfiguration configuration = new BucketNotificationConfiguration(); private StringBuilder text; private List<TopicConfiguration> topicConfigurations; private String topic; private String event; public BucketNotificationConfiguration getConfiguration() { return configuration; } @Override public void startDocument() { text = new StringBuilder(); } @Override public void startElement(String uri, String name, String qName, Attributes attrs) { if (name.equals("NotificationConfiguration")) { topicConfigurations = new ArrayList<BucketNotificationConfiguration.TopicConfiguration>(1); } else if (name.equals("TopicConfiguration")) { topic = null; event = null; } else if (name.equals("Topic")) { text.setLength(0); } else if (name.equals("Event")) { text.setLength(0); } else { log.error("Ignoring unexpected tag <"+name+">"); } } @Override public void endElement(String uri, String name, String qName) throws SAXException { if (name.equals("Topic")) { topic = text.toString(); } else if (name.equals("Event")) { event = text.toString(); } else if (name.equals("TopicConfiguration")) { if ( topic != null && event != null ) { topicConfigurations.add( new TopicConfiguration( topic, event ) ); } } else if (name.equals("NotificationConfiguration")) { configuration.setTopicConfigurations( topicConfigurations ); } text.setLength(0); } @Override public void characters(char ch[], int start, int length) { this.text.append(ch, start, length); } } }