/* * Copyright 2013 EMC Corporation. All Rights Reserved. * * 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://www.apache.org/licenses/LICENSE-2.0.txt * * 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.emc.esu.api.rest; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.EOFException; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.UnsupportedEncodingException; import java.net.*; import java.security.GeneralSecurityException; import java.sql.Date; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import org.apache.log4j.Logger; import org.jdom.Document; import org.jdom.JDOMException; import org.jdom.input.SAXBuilder; import com.emc.esu.api.Acl; import com.emc.esu.api.BufferSegment; import com.emc.esu.api.Checksum; import com.emc.esu.api.DirectoryEntry; import com.emc.esu.api.EsuException; import com.emc.esu.api.Extent; import com.emc.esu.api.Grantee; import com.emc.esu.api.HttpInputStreamWrapper; import com.emc.esu.api.Identifier; import com.emc.esu.api.ListOptions; import com.emc.esu.api.MetadataList; import com.emc.esu.api.MetadataTag; import com.emc.esu.api.MetadataTags; import com.emc.esu.api.ObjectId; import com.emc.esu.api.ObjectInfo; import com.emc.esu.api.ObjectMetadata; import com.emc.esu.api.ObjectPath; import com.emc.esu.api.ObjectResult; import com.emc.esu.api.ServiceInformation; import com.emc.esu.api.Version; /** * Implements the REST version of the ESU API. This class uses HttpUrlRequest to * perform object and metadata calls against the ESU server. All of the methods * that communicate with the server are atomic and stateless so the object can * be used safely in a multithreaded environment. * * NOTE: When running on MacOS, it is highly recommended to set the platform * encoding to ISO-8859-1 instead of the default MacRoman. Java's internal * HTTP layer does not translate characters properly and user metadata * containing extended characters may not be handled properly. You can set * the encoding by passing the argument "-Dfile.encoding=ISO-8859-1" to the * VM on startup. */ public class EsuRestApi extends AbstractEsuRestApi { private static final Logger l4j = Logger.getLogger(EsuRestApi.class); private Map<String, String> customHeaders; /** * Creates a new EsuRestApi object. * * @param host the hostname or IP address of the ESU server * @param port the port on the server to communicate with. Generally this is * 80 for HTTP and 443 for HTTPS. * @param uid the username to use when connecting to the server * @param sharedSecret the Base64 encoded shared secret to use to sign * requests to the server. */ public EsuRestApi(String host, int port, String uid, String sharedSecret) { super(host, port, uid, sharedSecret); } /** * Creates a new object in the cloud. * @param path The path to create the object on. * @param acl Access control list for the new object. May be null * to use a default ACL * @param metadata Metadata for the new object. May be null for * no metadata. * @param data The initial contents of the object. May be appended * to later. The stream will NOT be closed at the end of the request. * @param length The length of the stream in bytes. If the stream * is longer than the length, only length bytes will be written. If * the stream is shorter than the length, an error will occur. * @param mimeType the MIME type of the content. Optional, * may be null. If data is non-null and mimeType is null, the MIME * type will default to application/octet-stream. * @return Identifier of the newly created object. * @throws EsuException if the request fails. */ public ObjectId createObjectFromStreamOnPath( ObjectPath path, Acl acl, MetadataList metadata, InputStream data, long length, String mimeType ) { try { String resource = getResourcePath(context, path); URL u = buildUrl(resource, null); HttpURLConnection con = (HttpURLConnection) u.openConnection(); if (data == null) { throw new IllegalArgumentException("Input stream is required"); } // Build headers Map<String, String> headers = new HashMap<String, String>(); // Figure out the mimetype if (mimeType == null) { mimeType = "application/octet-stream"; } headers.put("Content-Type", mimeType); headers.put("x-emc-uid", uid); // Process metadata if (metadata != null) { processMetadata(metadata, headers); } l4j.debug("meta " + headers.get("x-emc-meta")); // Add acl if (acl != null) { processAcl(acl, headers); } con.setFixedLengthStreamingMode((int) length); con.setDoOutput(true); // Add date headers.put("Date", getDateHeader()); // Sign request signRequest("POST", resource, null, headers); configureRequest( con, "POST", headers ); con.connect(); // post data OutputStream out = null; byte[] buffer = new byte[128 * 1024]; int read = 0; try { out = con.getOutputStream(); while (read < length) { // make sure we don't write past the content-length int maxRead = (int) Math.min( (long) buffer.length, length - read ); int c = data.read( buffer, 0, maxRead ); if (c == -1) { throw new EsuException( "EOF encountered reading data stream"); } out.write(buffer, 0, c); read += c; } out.close(); } catch (IOException e) { silentClose(out); con.disconnect(); throw new EsuException("Error posting data", e); } // Check response if (con.getResponseCode() > 299) { handleError(con); } // The new object ID is returned in the location response header String location = con.getHeaderField("location"); con.disconnect(); // Parse the value out of the URL return getObjectId( location ); } catch (MalformedURLException e) { throw new EsuException("Invalid URL", e); } catch (IOException e) { throw new EsuException("Error connecting to server", e); } catch (GeneralSecurityException e) { throw new EsuException("Error computing request signature", e); } catch (URISyntaxException e) { throw new EsuException("Invalid URL", e); } } /** * Creates a new object in the cloud. * * @param acl Access control list for the new object. May be null to use a * default ACL * @param metadata Metadata for the new object. May be null for no metadata. * @param data The initial contents of the object. May be appended to later. * The stream will NOT be closed at the end of the request. * @param length The length of the stream in bytes. If the stream is longer * than the length, only length bytes will be written. If the * stream is shorter than the length, an error will occur. * @param mimeType the MIME type of the content. Optional, may be null. If * data is non-null and mimeType is null, the MIME type will * default to application/octet-stream. * @return Identifier of the newly created object. * @throws EsuException if the request fails. */ public ObjectId createObjectFromStream(Acl acl, MetadataList metadata, InputStream data, long length, String mimeType) { try { String resource = context + "/objects"; URL u = buildUrl(resource, null); HttpURLConnection con = (HttpURLConnection) u.openConnection(); if (data == null) { throw new IllegalArgumentException("Input stream is required"); } // Build headers Map<String, String> headers = new HashMap<String, String>(); // Figure out the mimetype if (mimeType == null) { mimeType = "application/octet-stream"; } headers.put("Content-Type", mimeType); headers.put("x-emc-uid", uid); // Process metadata if (metadata != null) { processMetadata(metadata, headers); } l4j.debug("meta " + headers.get("x-emc-meta")); // Add acl if (acl != null) { processAcl(acl, headers); } con.setFixedLengthStreamingMode((int) length); con.setDoOutput(true); // Add date headers.put("Date", getDateHeader()); // Sign request signRequest("POST", resource, null, headers); configureRequest( con, "POST", headers ); con.connect(); // post data OutputStream out = null; byte[] buffer = new byte[128 * 1024]; int read = 0; try { out = con.getOutputStream(); while (read < length) { // make sure we don't write past the content-length int maxRead = (int) Math.min( (long) buffer.length, length - read ); int c = data.read( buffer, 0, maxRead ); if (c == -1) { throw new EsuException( "EOF encountered reading data stream"); } out.write(buffer, 0, c); read += c; } out.close(); } catch (IOException e) { silentClose(out); con.disconnect(); throw new EsuException("Error posting data", e); } // Check response if (con.getResponseCode() > 299) { handleError(con); } // The new object ID is returned in the location response header String location = con.getHeaderField("location"); con.disconnect(); // Parse the value out of the URL return getObjectId( location ); } catch (MalformedURLException e) { throw new EsuException("Invalid URL", e); } catch (IOException e) { throw new EsuException("Error connecting to server", e); } catch (GeneralSecurityException e) { throw new EsuException("Error computing request signature", e); } catch (URISyntaxException e) { throw new EsuException("Invalid URL", e); } } /** * Creates a new object in the cloud using a BufferSegment. * * @param acl Access control list for the new object. May be null to use a * default ACL * @param metadata Metadata for the new object. May be null for no metadata. * @param data The initial contents of the object. May be appended to later. * May be null to create an object with no content. * @param mimeType the MIME type of the content. Optional, may be null. If * data is non-null and mimeType is null, the MIME type will * default to application/octet-stream. * @param checksum if not null, use the Checksum object to compute * the checksum for the create object request. If appending * to the object with subsequent requests, use the same * checksum object for each request. * @return Identifier of the newly created object. * @throws EsuException if the request fails. */ public ObjectId createObjectFromSegment(Acl acl, MetadataList metadata, BufferSegment data, String mimeType, Checksum checksum) { try { String resource = context + "/objects"; URL u = buildUrl(resource, null); HttpURLConnection con = (HttpURLConnection) u.openConnection(); // Build headers Map<String, String> headers = new HashMap<String, String>(); // Figure out the mimetype if (mimeType == null) { mimeType = "application/octet-stream"; } headers.put("Content-Type", mimeType); headers.put("x-emc-uid", uid); // Process metadata if (metadata != null) { processMetadata(metadata, headers); } l4j.debug("meta " + headers.get("x-emc-meta")); // Add acl if (acl != null) { processAcl(acl, headers); } // Process data if (data == null) { data = new BufferSegment(new byte[0]); } con.setFixedLengthStreamingMode(data.getSize()); con.setDoOutput(true); // Add date headers.put("Date", getDateHeader()); // Compute checksum if( checksum != null ) { checksum.update( data.getBuffer(), data.getOffset(), data.getSize() ); headers.put( "x-emc-wschecksum", checksum.toString() ); } // Sign request signRequest("POST", resource, null, headers); configureRequest( con, "POST", headers ); con.connect(); // post data OutputStream out = null; try { out = con.getOutputStream(); out.write(data.getBuffer(), data.getOffset(), data.getSize()); out.close(); } catch (IOException e) { silentClose(out); con.disconnect(); throw new EsuException("Error posting data", e); } // Check response if (con.getResponseCode() > 299) { handleError(con); } // The new object ID is returned in the location response header String location = con.getHeaderField("location"); con.disconnect(); // Parse the value out of the URL return getObjectId(location); } catch (MalformedURLException e) { throw new EsuException("Invalid URL", e); } catch (IOException e) { throw new EsuException("Error connecting to server", e); } catch (GeneralSecurityException e) { throw new EsuException("Error computing request signature", e); } catch (URISyntaxException e) { throw new EsuException("Invalid URL", e); } } /** * Creates a new object in the cloud using a BufferSegment on the given * path. * * @param path the path to create the object on. * @param acl Access control list for the new object. May be null to use a * default ACL * @param metadata Metadata for the new object. May be null for no metadata. * @param data The initial contents of the object. May be appended to later. * May be null to create an object with no content. * @param mimeType the MIME type of the content. Optional, may be null. If * data is non-null and mimeType is null, the MIME type will * default to application/octet-stream. * @param checksum if not null, use the Checksum object to compute * the checksum for the create object request. If appending * to the object with subsequent requests, use the same * checksum object for each request. * @return the ObjectId of the newly-created object for references by ID. * @throws EsuException if the request fails. */ public ObjectId createObjectFromSegmentOnPath(ObjectPath path, Acl acl, MetadataList metadata, BufferSegment data, String mimeType, Checksum checksum) { try { String resource = getResourcePath(context, path); URL u = buildUrl(resource, null); HttpURLConnection con = (HttpURLConnection) u.openConnection(); // Build headers Map<String, String> headers = new HashMap<String, String>(); // Figure out the mimetype if (mimeType == null) { mimeType = "application/octet-stream"; } headers.put("Content-Type", mimeType); headers.put("x-emc-uid", uid); // Process metadata if (metadata != null) { processMetadata(metadata, headers); } l4j.debug("meta " + headers.get("x-emc-meta")); // Add acl if (acl != null) { processAcl(acl, headers); } // Process data if (data == null) { data = new BufferSegment(new byte[0]); } con.setFixedLengthStreamingMode(data.getSize()); con.setDoOutput(true); // Add date headers.put("Date", getDateHeader()); // Compute checksum if( checksum != null ) { checksum.update( data.getBuffer(), data.getOffset(), data.getSize() ); headers.put( "x-emc-wschecksum", checksum.toString() ); } // Sign request signRequest("POST", resource, null, headers); configureRequest( con, "POST", headers ); con.connect(); // post data OutputStream out = null; try { out = con.getOutputStream(); out.write(data.getBuffer(), data.getOffset(), data.getSize()); out.close(); } catch (IOException e) { silentClose(out); con.disconnect(); throw new EsuException("Error posting data", e); } // Check response if (con.getResponseCode() > 299) { handleError(con); } // The new object ID is returned in the location response header String location = con.getHeaderField("location"); con.disconnect(); // Parse the value out of the URL return getObjectId(location); } catch (MalformedURLException e) { throw new EsuException("Invalid URL", e); } catch (IOException e) { throw new EsuException("Error connecting to server", e); } catch (GeneralSecurityException e) { throw new EsuException("Error computing request signature", e); } catch (URISyntaxException e) { throw new EsuException("Invalid URL", e); } } /** * Deletes an object from the cloud. * * @param id the identifier of the object to delete. */ public void deleteObject(Identifier id) { try { String resource = getResourcePath(context, id); URL u = buildUrl(resource, null); HttpURLConnection con = (HttpURLConnection) u.openConnection(); // Build headers Map<String, String> headers = new HashMap<String, String>(); headers.put("x-emc-uid", uid); // Add date headers.put("Date", getDateHeader()); // Sign request signRequest("DELETE", resource, null, headers); configureRequest( con, "DELETE", headers ); con.connect(); // Check response if (con.getResponseCode() > 299) { handleError(con); } con.disconnect(); } catch (MalformedURLException e) { throw new EsuException("Invalid URL", e); } catch (IOException e) { throw new EsuException("Error connecting to server", e); } catch (GeneralSecurityException e) { throw new EsuException("Error computing request signature", e); } catch (URISyntaxException e) { throw new EsuException("Invalid URL", e); } } /** * Deletes a version of an object from the cloud. * * @param id the identifier of the object to delete. */ public void deleteVersion(ObjectId id) { try { String resource = getResourcePath(context, id); String query = "versions"; URL u = buildUrl(resource, query); HttpURLConnection con = (HttpURLConnection) u.openConnection(); // Build headers Map<String, String> headers = new HashMap<String, String>(); headers.put("x-emc-uid", uid); // Add date headers.put("Date", getDateHeader()); // Sign request signRequest("DELETE", resource, query, headers); configureRequest( con, "DELETE", headers ); con.connect(); // Check response if (con.getResponseCode() > 299) { handleError(con); } con.disconnect(); } catch (MalformedURLException e) { throw new EsuException("Invalid URL", e); } catch (IOException e) { throw new EsuException("Error connecting to server", e); } catch (GeneralSecurityException e) { throw new EsuException("Error computing request signature", e); } catch (URISyntaxException e) { throw new EsuException("Invalid URL", e); } } /** * Deletes metadata items from an object. * * @param id the identifier of the object whose metadata to delete. * @param tags the list of metadata tags to delete. */ public void deleteUserMetadata(Identifier id, MetadataTags tags) { if (tags == null) { throw new EsuException("Must specify tags to delete"); } try { String resource = getResourcePath(context, id); String query = "metadata/user"; URL u = buildUrl(resource, query); HttpURLConnection con = (HttpURLConnection) u.openConnection(); // Build headers Map<String, String> headers = new HashMap<String, String>(); headers.put("x-emc-uid", uid); // process tags if (tags != null) { processTags(tags, headers); } // Add date headers.put("Date", getDateHeader()); // Sign request signRequest("DELETE", resource, query, headers); configureRequest( con, "DELETE", headers ); con.connect(); // Check response if (con.getResponseCode() > 299) { handleError(con); } con.disconnect(); } catch (MalformedURLException e) { throw new EsuException("Invalid URL", e); } catch (IOException e) { throw new EsuException("Error connecting to server", e); } catch (GeneralSecurityException e) { throw new EsuException("Error computing request signature", e); } catch (URISyntaxException e) { throw new EsuException("Invalid URL", e); } } /** * Returns an object's ACL * * @param id the identifier of the object whose ACL to read * @return the object's ACL */ public Acl getAcl(Identifier id) { try { String resource = getResourcePath(context, id); String query = "acl"; URL u = buildUrl(resource, query); HttpURLConnection con = (HttpURLConnection) u.openConnection(); // Build headers Map<String, String> headers = new HashMap<String, String>(); headers.put("x-emc-uid", uid); // Add date headers.put("Date", getDateHeader()); // Sign request signRequest("GET", resource, query, headers); configureRequest( con, "GET", headers ); con.connect(); // Check response if (con.getResponseCode() > 299) { handleError(con); } // Parse return headers. User grants are in x-emc-useracl and // group grants are in x-emc-groupacl Acl acl = new Acl(); readAcl(acl, con.getHeaderField("x-emc-useracl"), Grantee.GRANT_TYPE.USER); readAcl(acl, con.getHeaderField("x-emc-groupacl"), Grantee.GRANT_TYPE.GROUP); con.disconnect(); return acl; } catch (MalformedURLException e) { throw new EsuException("Invalid URL", e); } catch (IOException e) { throw new EsuException("Error connecting to server", e); } catch (GeneralSecurityException e) { throw new EsuException("Error computing request signature", e); } catch (URISyntaxException e) { throw new EsuException("Invalid URL", e); } } /** * Returns a list of the tags that are listable the current user's tennant. * * @param tag optional. If specified, the list will be limited to the tags * under the specified tag. If null, only top level tags will be * returned. * @return the list of listable tags. */ public MetadataTags getListableTags(MetadataTag tag) { return getListableTags(tag == null ? null : tag.getName()); } /** * Returns a list of the tags that are listable the current user's tennant. * * @param tag optional. If specified, the list will be limited to the tags * under the specified tag. If null, only top level tags will be * returned. * @return the list of listable tags. */ public MetadataTags getListableTags(String tag) { try { String resource = context + "/objects"; String query = "listabletags"; URL u = buildUrl(resource, query); HttpURLConnection con = (HttpURLConnection) u.openConnection(); // Build headers Map<String, String> headers = new HashMap<String, String>(); headers.put("x-emc-uid", uid); if(unicodeEnabled) { headers.put("x-emc-utf8", "true"); } // Add tag if (tag != null) { headers.put("x-emc-tags", unicodeEnabled ? encodeUtf8(tag) : tag); } // Add date headers.put("Date", getDateHeader()); // Sign request signRequest("GET", resource, query, headers); configureRequest( con, "GET", headers ); con.connect(); // Check response if (con.getResponseCode() > 299) { handleError(con); } String header = con.getHeaderField("x-emc-listable-tags"); l4j.debug("x-emc-listable-tags: " + header); MetadataTags tags = new MetadataTags(); readTags(tags, header, true); con.disconnect(); return tags; } catch (MalformedURLException e) { throw new EsuException("Invalid URL", e); } catch (IOException e) { throw new EsuException("Error connecting to server", e); } catch (GeneralSecurityException e) { throw new EsuException("Error computing request signature", e); } catch (URISyntaxException e) { throw new EsuException("Invalid URL", e); } } /** * Fetches the system metadata for the object. * * @param id the identifier of the object whose system metadata to fetch. * @param tags A list of system metadata tags to fetch. Optional. Default * value is null to fetch all system metadata. * @return The list of system metadata for the object. */ public MetadataList getSystemMetadata(Identifier id, MetadataTags tags) { try { String resource = getResourcePath(context, id); String query = "metadata/system"; URL u = buildUrl(resource, query); HttpURLConnection con = (HttpURLConnection) u.openConnection(); // Build headers Map<String, String> headers = new HashMap<String, String>(); headers.put("x-emc-uid", uid); // process tags if (tags != null) { processTags(tags, headers); } // Add date headers.put("Date", getDateHeader()); // Sign request signRequest("GET", resource, query, headers); configureRequest( con, "GET", headers ); con.connect(); // Check response if (con.getResponseCode() > 299) { handleError(con); } // Parse return headers. Regular metadata is in x-emc-meta and // listable metadata is in x-emc-listable-meta MetadataList meta = new MetadataList(); readMetadata(meta, con.getHeaderField("x-emc-meta"), false); readMetadata(meta, con.getHeaderField("x-emc-listable-meta"), true); con.disconnect(); return meta; } catch (MalformedURLException e) { throw new EsuException("Invalid URL", e); } catch (IOException e) { throw new EsuException("Error connecting to server", e); } catch (GeneralSecurityException e) { throw new EsuException("Error computing request signature", e); } catch (URISyntaxException e) { throw new EsuException("Invalid URL", e); } } /** * Fetches the user metadata for the object. * * @param id the identifier of the object whose user metadata to fetch. * @param tags A list of user metadata tags to fetch. Optional. If null, all * user metadata will be fetched. * @return The list of user metadata for the object. */ public MetadataList getUserMetadata(Identifier id, MetadataTags tags) { try { String resource = getResourcePath(context, id); String query = "metadata/user"; URL u = buildUrl(resource, query); HttpURLConnection con = (HttpURLConnection) u.openConnection(); // Build headers Map<String, String> headers = new HashMap<String, String>(); headers.put("x-emc-uid", uid); if(unicodeEnabled) { headers.put("x-emc-utf8", "true"); } // process tags if (tags != null) { processTags(tags, headers); } // Add date headers.put("Date", getDateHeader()); // Sign request signRequest("GET", resource, query, headers); configureRequest( con, "GET", headers ); con.connect(); // Check response if (con.getResponseCode() > 299) { handleError(con); } // Parse return headers. Regular metadata is in x-emc-meta and // listable metadata is in x-emc-listable-meta MetadataList meta = new MetadataList(); readMetadata(meta, con.getHeaderField("x-emc-meta"), false); readMetadata(meta, con.getHeaderField("x-emc-listable-meta"), true); con.disconnect(); return meta; } catch (MalformedURLException e) { throw new EsuException("Invalid URL", e); } catch (IOException e) { throw new EsuException("Error connecting to server", e); } catch (GeneralSecurityException e) { throw new EsuException("Error computing request signature", e); } catch (URISyntaxException e) { throw new EsuException("Invalid URL", e); } } /** * Lists all objects with the given tag. * * @param tag the tag to search for * @return The list of objects with the given tag. If no objects are found * the List will be empty. */ public List<ObjectResult> listObjects(String tag, ListOptions options) { try { String resource = context + "/objects"; URL u = buildUrl(resource, null); HttpURLConnection con = (HttpURLConnection) u.openConnection(); // Build headers Map<String, String> headers = new HashMap<String, String>(); headers.put("x-emc-uid", uid); if(unicodeEnabled) { headers.put("x-emc-utf8", "true"); } // Add tag if (tag != null) { headers.put("x-emc-tags", unicodeEnabled ? encodeUtf8(tag) : tag); } else { throw new EsuException("Tag cannot be null"); } // Process options if( options != null ) { if( options.isIncludeMetadata() ) { headers.put( "x-emc-include-meta", "1" ); if( options.getSystemMetadata() != null ) { headers.put( "x-emc-system-tags", join( options.getSystemMetadata(), "," ) ); } if( options.getUserMetadata() != null ) { headers.put( "x-emc-user-tags", join( options.getUserMetadata(), "," ) ); } } if( options.getLimit() > 0 ) { headers.put( "x-emc-limit", ""+options.getLimit() ); } if( options.getToken() != null ) { headers.put( "x-emc-token", options.getToken() ); } } // Add date headers.put("Date", getDateHeader()); // Sign request signRequest("GET", resource, null, headers); configureRequest( con, "GET", headers ); con.connect(); // Check response try { if (con.getResponseCode() > 299) { handleError(con); } } catch( EsuException e ) { if( e.getAtmosCode() == 1003 ) { return Collections.emptyList(); } throw e; } // Get object id list from response byte[] response = readResponse(con, null); l4j.debug("Response: " + new String(response, "UTF-8")); con.disconnect(); if( options != null ) { // Update the token for listing more results. If there are no // more results, the header will not be set and the token will // be cleared in the options object. options.setToken( con.getHeaderField("x-emc-token") ); } else { if( con.getHeaderField( "x-emc-token" ) != null ) { l4j.warn( "Result set truncated. Use ListOptions to " + "retrieve token for next page of results." ); } } return parseObjectListWithMetadata(response); } catch (MalformedURLException e) { throw new EsuException("Invalid URL", e); } catch (IOException e) { throw new EsuException("Error connecting to server", e); } catch (GeneralSecurityException e) { throw new EsuException("Error computing request signature", e); } catch (URISyntaxException e) { throw new EsuException("Invalid URL", e); } } /** * Returns the list of user metadata tags assigned to the object. * * @param id the object whose metadata tags to list * @return the list of user metadata tags assigned to the object */ public MetadataTags listUserMetadataTags(Identifier id) { try { String resource = getResourcePath(context, id); String query = "metadata/tags"; URL u = buildUrl(resource, query); HttpURLConnection con = (HttpURLConnection) u.openConnection(); // Build headers Map<String, String> headers = new HashMap<String, String>(); headers.put("x-emc-uid", uid); if(unicodeEnabled) { headers.put("x-emc-utf8", "true"); } // Add date headers.put("Date", getDateHeader()); // Sign request signRequest("GET", resource, query, headers); configureRequest( con, "GET", headers ); con.connect(); // Check response if (con.getResponseCode() > 299) { handleError(con); } // Get the user metadata tags out of x-emc-listable-tags and // x-emc-tags MetadataTags tags = new MetadataTags(); readTags(tags, con.getHeaderField("x-emc-listable-tags"), true); readTags(tags, con.getHeaderField("x-emc-tags"), false); con.disconnect(); return tags; } catch (MalformedURLException e) { throw new EsuException("Invalid URL", e); } catch (IOException e) { throw new EsuException("Error connecting to server", e); } catch (GeneralSecurityException e) { throw new EsuException("Error computing request signature", e); } catch (URISyntaxException e) { throw new EsuException("Invalid URL", e); } } /** * Lists the versions of an object. * * @param id the object whose versions to list. * @return The list of versions of the object. If the object does not have * any versions, the array will be empty. */ public List<Identifier> listVersions(Identifier id) { try { String resource = getResourcePath(context, id); String query = "versions"; URL u = buildUrl(resource, query); HttpURLConnection con = (HttpURLConnection) u.openConnection(); // Build headers Map<String, String> headers = new HashMap<String, String>(); headers.put("x-emc-uid", uid); // Add date headers.put("Date", getDateHeader()); // Sign request signRequest("GET", resource, query, headers); configureRequest( con, "GET", headers ); con.connect(); // Check response if (con.getResponseCode() > 299) { handleError(con); } // Get object id list from response byte[] response = readResponse(con, null); l4j.debug("Response: " + new String(response, "UTF-8")); con.disconnect(); return parseVersionList(response); } catch (MalformedURLException e) { throw new EsuException("Invalid URL", e); } catch (IOException e) { throw new EsuException("Error connecting to server", e); } catch (GeneralSecurityException e) { throw new EsuException("Error computing request signature", e); } catch (URISyntaxException e) { throw new EsuException("Invalid URL", e); } } public List<Version> listVersions(ObjectId id, ListOptions options) { try { String resource = getResourcePath(context, id); String query = "versions"; URL u = buildUrl(resource, query); HttpURLConnection con = (HttpURLConnection) u.openConnection(); // Build headers Map<String, String> headers = new HashMap<String, String>(); headers.put("x-emc-uid", uid); // Add date headers.put("Date", getDateHeader()); // Process options if( options != null ) { if( options.isIncludeMetadata() ) { l4j.warn("Include metadata is not supported for listVersions"); } if( options.getLimit() > 0 ) { headers.put( "x-emc-limit", ""+options.getLimit() ); } if( options.getToken() != null ) { headers.put( "x-emc-token", options.getToken() ); } } // Sign request signRequest("GET", resource, query, headers); configureRequest( con, "GET", headers ); con.connect(); // Check response if (con.getResponseCode() > 299) { handleError(con); } if( options != null ) { // Update the token for listing more results. If there are no // more results, the header will not be set and the token will // be cleared in the options object. options.setToken( con.getHeaderField("x-emc-token") ); } else { if( con.getHeaderField( "x-emc-token" ) != null ) { l4j.warn( "Result set truncated. Use ListOptions to " + "retrieve token for next page of results." ); } } // Get object id list from response byte[] response = readResponse(con, null); l4j.debug("Response: " + new String(response, "UTF-8")); con.disconnect(); return parseVersionListLong(response); } catch (MalformedURLException e) { throw new EsuException("Invalid URL", e); } catch (IOException e) { throw new EsuException("Error connecting to server", e); } catch (GeneralSecurityException e) { throw new EsuException("Error computing request signature", e); } catch (URISyntaxException e) { throw new EsuException("Invalid URL", e); } } /** * Executes a query for objects matching the specified XQuery string. * * @param xquery the XQuery string to execute against the cloud. * @return the list of objects matching the query. If no objects are found, * the array will be empty. */ public List<ObjectId> queryObjects(String xquery) { try { String resource = context + "/objects"; URL u = buildUrl(resource, null); HttpURLConnection con = (HttpURLConnection) u.openConnection(); // Build headers Map<String, String> headers = new HashMap<String, String>(); headers.put("x-emc-uid", uid); // Add query if (xquery != null) { headers.put("x-emc-xquery", xquery); } else { throw new EsuException("Query cannot be null"); } // Add date headers.put("Date", getDateHeader()); // Sign request signRequest("GET", resource, null, headers); configureRequest( con, "GET", headers ); con.connect(); // Check response if (con.getResponseCode() > 299) { handleError(con); } // Get object id list from response Map<String,List<String>> responseHeaders = con.getHeaderFields(); l4j.debug("Response headers: " + responseHeaders); byte[] response = readResponse(con, null); if(l4j.isDebugEnabled()) { String responseBody = new String(response, "UTF-8"); l4j.debug("Response: " + responseBody); } con.disconnect(); return parseObjectList(response); } catch (MalformedURLException e) { throw new EsuException("Invalid URL", e); } catch (IOException e) { throw new EsuException("Error connecting to server", e); } catch (GeneralSecurityException e) { throw new EsuException("Error computing request signature", e); } catch (URISyntaxException e) { throw new EsuException("Invalid URL", e); } } /** * Reads an object's content. * * @param id the identifier of the object whose content to read. * @param extent the portion of the object data to read. Optional. Default * is null to read the entire object. * @param buffer the buffer to use to read the extent. Must be large enough * to read the response or an error will be thrown. If null, a * buffer will be allocated to hold the response data. If you * pass a buffer that is larger than the extent, only * extent.getSize() bytes will be valid. * @param checksum if not null, the given checksum object will be used * to verify checksums during the read operation. Note that only erasure * coded objects will return checksums *and* if you're reading the object * in chunks, you'll have to read the data back sequentially to keep * the checksum consistent. If the read operation does not return * a checksum from the server, the checksum operation will be skipped. * @return the object data read as a byte array. */ public byte[] readObject(Identifier id, Extent extent, byte[] buffer, Checksum checksum) { try { String resource = getResourcePath(context, id); URL u = buildUrl(resource, null); HttpURLConnection con = (HttpURLConnection) u.openConnection(); // Build headers Map<String, String> headers = new HashMap<String, String>(); headers.put("x-emc-uid", uid); // Add date headers.put("Date", getDateHeader()); // Add extent if needed if (extent != null && !extent.equals(Extent.ALL_CONTENT)) { headers.put(extent.getHeaderName(), extent.toString()); } // Sign request signRequest("GET", resource, null, headers); configureRequest( con, "GET", headers ); con.connect(); // Check response if (con.getResponseCode() > 299) { handleError(con); } if(buffer != null && extent != null) { if(extent.getSize() > (long)buffer.length) { throw new IllegalArgumentException( "The buffer is smaller than the requested extent"); } } // The requested content is in the response body. byte[] data = readResponse(con, buffer); // See if a checksum was returned. String checksumStr = con.getHeaderField("x-emc-wschecksum"); if( checksumStr != null && checksum != null ) { l4j.debug( "Checksum header: " + checksumStr ); checksum.setExpectedValue( checksumStr ); if( con.getContentLength() != -1 ) { checksum.update( data, 0, con.getContentLength() ); } else { // readResponse should return a new content-sized buffer in this case checksum.update( data, 0, data.length ); } } con.disconnect(); return data; } catch (MalformedURLException e) { throw new EsuException("Invalid URL", e); } catch (IOException e) { throw new EsuException("Error connecting to server", e); } catch (GeneralSecurityException e) { throw new EsuException("Error computing request signature", e); } catch (URISyntaxException e) { throw new EsuException("Invalid URL", e); } } /** * Reads an object's content and returns an InputStream to read the content. * Since the input stream is linked to the HTTP connection, it is imperative * that you close the input stream as soon as you are done with the stream * to release the underlying connection. * * @param id the identifier of the object whose content to read. * @param extent the portion of the object data to read. Optional. Default * is null to read the entire object. * @return an InputStream to read the object data. */ public InputStream readObjectStream(Identifier id, Extent extent) { try { String resource = getResourcePath(context, id); URL u = buildUrl(resource, null); HttpURLConnection con = (HttpURLConnection) u.openConnection(); // Build headers Map<String, String> headers = new HashMap<String, String>(); headers.put("x-emc-uid", uid); // Add date headers.put("Date", getDateHeader()); // Add extent if needed if (extent != null && !extent.equals(Extent.ALL_CONTENT)) { headers.put(extent.getHeaderName(), extent.toString()); } // Sign request signRequest("GET", resource, null, headers); configureRequest( con, "GET", headers ); con.connect(); // Check response if (con.getResponseCode() > 299) { handleError(con); } return new HttpInputStreamWrapper(con.getInputStream(), con); } catch (MalformedURLException e) { throw new EsuException("Invalid URL", e); } catch (IOException e) { throw new EsuException("Error connecting to server", e); } catch (GeneralSecurityException e) { throw new EsuException("Error computing request signature", e); } catch (URISyntaxException e) { throw new EsuException("Invalid URL", e); } } /** * Updates an object in the cloud and optionally its metadata and ACL. * * @param id The ID of the object to update * @param acl Access control list for the new object. Optional, default is * NULL to leave the ACL unchanged. * @param metadata Metadata list for the new object. Optional, default is * NULL for no changes to the metadata. * @param data The new contents of the object. May be appended to later. * Optional, default is NULL (no content changes). * @param extent portion of the object to update. May be null to indicate * the whole object is to be replaced. If not null, the extent * size must match the data size. * @param mimeType the MIME type of the content. Optional, may be null. If * data is non-null and mimeType is null, the MIME type will * default to application/octet-stream. * @param checksum if not null, use the Checksum object to compute * the checksum for the update object request. If appending * to the object with subsequent requests, use the same * checksum object for each request. * @throws EsuException if the request fails. */ public void updateObjectFromSegment(Identifier id, Acl acl, MetadataList metadata, Extent extent, BufferSegment data, String mimeType, Checksum checksum) { try { String resource = getResourcePath(context, id); URL u = buildUrl(resource, null); HttpURLConnection con = (HttpURLConnection) u.openConnection(); // Build headers Map<String, String> headers = new HashMap<String, String>(); // Figure out the mimetype if (mimeType == null) { mimeType = "application/octet-stream"; } headers.put("Content-Type", mimeType); headers.put("x-emc-uid", uid); // Process metadata if (metadata != null) { processMetadata(metadata, headers); } l4j.debug("meta " + headers.get("x-emc-meta")); // Add acl if (acl != null) { processAcl(acl, headers); } // Add extent if needed if (extent != null && !extent.equals(Extent.ALL_CONTENT)) { headers.put(extent.getHeaderName(), extent.toString()); } // Process data if (data == null) { data = new BufferSegment(new byte[0]); } con.setFixedLengthStreamingMode(data.getSize()); con.setDoOutput(true); // Add date headers.put("Date", getDateHeader()); // Compute checksum if( checksum != null ) { checksum.update( data.getBuffer(), data.getOffset(), data.getSize() ); headers.put( "x-emc-wschecksum", checksum.toString() ); } // Sign request signRequest("PUT", resource, null, headers); configureRequest( con, "PUT", headers ); con.connect(); // post data OutputStream out = null; try { out = con.getOutputStream(); out.write(data.getBuffer(), data.getOffset(), data.getSize()); out.close(); } catch (IOException e) { silentClose(out); con.disconnect(); throw new EsuException("Error posting data", e); } // Check response if (con.getResponseCode() > 299) { handleError(con); } con.disconnect(); } catch (MalformedURLException e) { throw new EsuException("Invalid URL", e); } catch (IOException e) { throw new EsuException("Error connecting to server", e); } catch (GeneralSecurityException e) { throw new EsuException("Error computing request signature", e); } catch (URISyntaxException e) { throw new EsuException("Invalid URL", e); } } /** * Updates an object in the cloud. * * @param id The ID of the object to update * @param acl Access control list for the new object. Optional, default is * NULL to leave the ACL unchanged. * @param metadata Metadata list for the new object. Optional, default is * NULL for no changes to the metadata. * @param data The updated data to apply to the object. Requred. Note that * the input stream is NOT closed at the end of the request. * @param extent portion of the object to update. May be null to indicate * the whole object is to be replaced. If not null, the extent * size must match the data size. * @param length The length of the stream in bytes. If the stream is longer * than the length, only length bytes will be written. If the * stream is shorter than the length, an error will occur. * @param mimeType the MIME type of the content. Optional, may be null. If * data is non-null and mimeType is null, the MIME type will * default to application/octet-stream. * @throws EsuException if the request fails. */ public void updateObjectFromStream(Identifier id, Acl acl, MetadataList metadata, Extent extent, InputStream data, long length, String mimeType) { try { String resource = getResourcePath(context, id); URL u = buildUrl(resource, null); HttpURLConnection con = (HttpURLConnection) u.openConnection(); // Build headers Map<String, String> headers = new HashMap<String, String>(); // Figure out the mimetype if (mimeType == null) { mimeType = "application/octet-stream"; } headers.put("Content-Type", mimeType); headers.put("x-emc-uid", uid); // Process metadata if (metadata != null) { processMetadata(metadata, headers); } l4j.debug("meta " + headers.get("x-emc-meta")); // Add acl if (acl != null) { processAcl(acl, headers); } // Add extent if needed if (extent != null && !extent.equals(Extent.ALL_CONTENT)) { headers.put(extent.getHeaderName(), extent.toString()); } con.setFixedLengthStreamingMode((int) length); con.setDoOutput(true); // Add date headers.put("Date", getDateHeader()); // Sign request signRequest("PUT", resource, null, headers); configureRequest( con, "PUT", headers ); con.connect(); // post data OutputStream out = null; byte[] buffer = new byte[128 * 1024]; int read = 0; try { out = con.getOutputStream(); while (read < length) { // make sure we don't write past the content-length int maxRead = (int) Math.min( (long) buffer.length, length - read ); int c = data.read( buffer, 0, maxRead ); if (c == -1) { throw new EsuException( "EOF encountered reading data stream"); } out.write(buffer, 0, c); read += c; } out.close(); } catch (IOException e) { silentClose(out); con.disconnect(); throw new EsuException("Error posting data", e); } // Check response if (con.getResponseCode() > 299) { handleError(con); } con.disconnect(); } catch (MalformedURLException e) { throw new EsuException("Invalid URL", e); } catch (IOException e) { throw new EsuException("Error connecting to server", e); } catch (GeneralSecurityException e) { throw new EsuException("Error computing request signature", e); } catch (URISyntaxException e) { throw new EsuException("Invalid URL", e); } } /** * Writes the metadata into the object. If the tag does not exist, it is * created and set to the corresponding value. If the tag exists, the * existing value is replaced. * * @param id the identifier of the object to update * @param metadata metadata to write to the object. */ public void setUserMetadata(Identifier id, MetadataList metadata) { try { String resource = getResourcePath(context, id); String query = "metadata/user"; URL u = buildUrl(resource, query); HttpURLConnection con = (HttpURLConnection) u.openConnection(); // Build headers Map<String, String> headers = new HashMap<String, String>(); headers.put("x-emc-uid", uid); // Process metadata if (metadata != null) { processMetadata(metadata, headers); } // Add date headers.put("Date", getDateHeader()); // Sign request signRequest("POST", resource, query, headers); configureRequest( con, "POST", headers ); con.connect(); // Check response if (con.getResponseCode() > 299) { handleError(con); } // Read the response to complete the request (will be empty) InputStream in = con.getInputStream(); in.close(); con.disconnect(); } catch (MalformedURLException e) { throw new EsuException("Invalid URL", e); } catch (IOException e) { throw new EsuException("Error connecting to server", e); } catch (GeneralSecurityException e) { throw new EsuException("Error computing request signature", e); } catch (URISyntaxException e) { throw new EsuException("Invalid URL", e); } } /** * Sets (overwrites) the ACL on the object. * * @param id the identifier of the object to change the ACL on. * @param acl the new ACL for the object. */ public void setAcl(Identifier id, Acl acl) { try { String resource = getResourcePath(context, id); String query = "acl"; URL u = buildUrl(resource, query); HttpURLConnection con = (HttpURLConnection) u.openConnection(); // Build headers Map<String, String> headers = new HashMap<String, String>(); headers.put("x-emc-uid", uid); // Add acl if (acl != null) { processAcl(acl, headers); } // Add date headers.put("Date", getDateHeader()); // Sign request signRequest("POST", resource, query, headers); configureRequest( con, "POST", headers ); con.connect(); // Check response if (con.getResponseCode() > 299) { handleError(con); } // Read the response to complete the request (will be empty) InputStream in = con.getInputStream(); in.close(); con.disconnect(); } catch (MalformedURLException e) { throw new EsuException("Invalid URL", e); } catch (IOException e) { throw new EsuException("Error connecting to server", e); } catch (GeneralSecurityException e) { throw new EsuException("Error computing request signature", e); } catch (URISyntaxException e) { throw new EsuException("Invalid URL", e); } } /** * Creates a new immutable version of an object. * * @param id the object to version * @return the id of the newly created version */ public ObjectId versionObject(Identifier id) { try { String resource = getResourcePath(context, id); String query = "versions"; URL u = buildUrl(resource, query); HttpURLConnection con = (HttpURLConnection) u.openConnection(); // Build headers Map<String, String> headers = new HashMap<String, String>(); headers.put("x-emc-uid", uid); // Add date headers.put("Date", getDateHeader()); // Sign request signRequest("POST", resource, query, headers); configureRequest( con, "POST", headers ); con.connect(); // Check response if (con.getResponseCode() > 299) { handleError(con); } // The new object ID is returned in the location response header String location = con.getHeaderField("location"); // Parse the value out of the URL return getObjectId( location ); } catch (MalformedURLException e) { throw new EsuException("Invalid URL", e); } catch (IOException e) { throw new EsuException("Error connecting to server", e); } catch (GeneralSecurityException e) { throw new EsuException("Error computing request signature", e); } catch (URISyntaxException e) { throw new EsuException("Invalid URL", e); } } /** * Lists the contents of a directory. * * @param path the path to list. Must be a directory. * @return the directory entries in the directory. */ public List<DirectoryEntry> listDirectory(ObjectPath path, ListOptions options ) { if (!path.isDirectory()) { throw new EsuException( "listDirectory must be called with a directory path"); } byte[] data = null; // Read out the directory's contents try { String resource = getResourcePath(context, path); URL u = buildUrl(resource, null); HttpURLConnection con = (HttpURLConnection) u.openConnection(); // Build headers Map<String, String> headers = new HashMap<String, String>(); headers.put("x-emc-uid", uid); if(unicodeEnabled) { headers.put("x-emc-utf8", "true"); } // Process options if( options != null ) { if( options.isIncludeMetadata() ) { headers.put( "x-emc-include-meta", "1" ); if( options.getSystemMetadata() != null ) { headers.put( "x-emc-system-tags", join( options.getSystemMetadata(), "," ) ); } if( options.getUserMetadata() != null ) { headers.put( "x-emc-user-tags", join( options.getUserMetadata(), "," ) ); } } if( options.getLimit() > 0 ) { headers.put( "x-emc-limit", ""+options.getLimit() ); } if( options.getToken() != null ) { headers.put( "x-emc-token", options.getToken() ); } } // Add date headers.put("Date", getDateHeader()); // Sign request signRequest("GET", resource, null, headers); configureRequest( con, "GET", headers ); con.connect(); // Check response if (con.getResponseCode() > 299) { handleError(con); } if( options != null ) { // Update the token for listing more results. If there are no // more results, the header will not be set and the token will // be cleared in the options object. options.setToken( con.getHeaderField("x-emc-token") ); } else { if( con.getHeaderField( "x-emc-token" ) != null ) { l4j.warn( "Result set truncated. Use ListOptions to " + "retrieve token for next page of results." ); } } // The requested content is in the response body. data = readResponse(con, null); } catch (MalformedURLException e) { throw new EsuException("Invalid URL", e); } catch (IOException e) { throw new EsuException("Error connecting to server", e); } catch (GeneralSecurityException e) { throw new EsuException("Error computing request signature", e); } catch (URISyntaxException e) { throw new EsuException("Invalid URL", e); } return parseDirectoryListing( data, path ); } public ObjectMetadata getAllMetadata(Identifier id) { try { String resource = getResourcePath(context, id); URL u = buildUrl(resource, null); HttpURLConnection con = (HttpURLConnection) u.openConnection(); // Build headers Map<String, String> headers = new HashMap<String, String>(); headers.put("x-emc-uid", uid); if(unicodeEnabled) { headers.put("x-emc-utf8", "true"); } // Add date headers.put("Date", getDateHeader()); // Sign request signRequest("HEAD", resource, null, headers); configureRequest( con, "HEAD", headers ); con.connect(); // Check response if (con.getResponseCode() > 299) { handleError(con); } // Parse return headers. User grants are in x-emc-useracl and // group grants are in x-emc-groupacl Acl acl = new Acl(); readAcl(acl, con.getHeaderField("x-emc-useracl"), Grantee.GRANT_TYPE.USER); readAcl(acl, con.getHeaderField("x-emc-groupacl"), Grantee.GRANT_TYPE.GROUP); // Parse return headers. Regular metadata is in x-emc-meta and // listable metadata is in x-emc-listable-meta MetadataList meta = new MetadataList(); readMetadata(meta, con.getHeaderField("x-emc-meta"), false); readMetadata(meta, con.getHeaderField("x-emc-listable-meta"), true); ObjectMetadata om = new ObjectMetadata(); om.setAcl(acl); om.setMetadata(meta); om.setMimeType(con.getContentType()); return om; } catch (MalformedURLException e) { throw new EsuException("Invalid URL", e); } catch (IOException e) { throw new EsuException("Error connecting to server", e); } catch (GeneralSecurityException e) { throw new EsuException("Error computing request signature", e); } catch (URISyntaxException e) { throw new EsuException("Invalid URL", e); } } public ServiceInformation getServiceInformation() { try { String resource = context + "/service"; URL u = buildUrl(resource, null); HttpURLConnection con = (HttpURLConnection) u.openConnection(); // Build headers Map<String, String> headers = new HashMap<String, String>(); headers.put("x-emc-uid", uid); // Add date headers.put("Date", getDateHeader()); // Sign request signRequest("GET", resource, null, headers); configureRequest( con, "GET", headers ); con.connect(); // Check response if (con.getResponseCode() > 299) { handleError(con); } // Get object id list from response byte[] response = readResponse(con, null); l4j.debug("Response: " + new String(response, "UTF-8")); con.disconnect(); return parseServiceInformation(response, con.getHeaderFields()); } catch (MalformedURLException e) { throw new EsuException("Invalid URL", e); } catch (IOException e) { throw new EsuException("Error connecting to server", e); } catch (GeneralSecurityException e) { throw new EsuException("Error computing request signature", e); } catch (URISyntaxException e) { throw new EsuException("Invalid URL", e); } } /** * Renames a file or directory within the namespace. * @param source The file or directory to rename * @param destination The new path for the file or directory * @param force If true, the desination file or * directory will be overwritten. Directories must be empty to be * overwritten. Also note that overwrite operations on files are * not synchronous; a delay may be required before the object is * available at its destination. */ public void rename(ObjectPath source, ObjectPath destination, boolean force) { try { String resource = getResourcePath(context, source); String query = "rename"; URL u = buildUrl(resource, query); HttpURLConnection con = (HttpURLConnection) u.openConnection(); // Build headers Map<String, String> headers = new HashMap<String, String>(); headers.put("x-emc-uid", uid); if(unicodeEnabled) { headers.put("x-emc-utf8", "true"); } String destPath = destination.toString(); if (destPath.startsWith("/")) { destPath = destPath.substring(1); } headers.put("x-emc-path", unicodeEnabled ? encodeUtf8(destPath) : destPath); if (force) { headers.put("x-emc-force", "true"); } // Add date headers.put("Date", getDateHeader()); // Compute checksum // Sign request signRequest("POST", resource, query, headers); configureRequest( con, "POST", headers ); con.connect(); // Check response if (con.getResponseCode() > 299) { handleError(con); } con.disconnect(); } catch (MalformedURLException e) { throw new EsuException("Invalid URL", e); } catch (IOException e) { throw new EsuException("Error connecting to server", e); } catch (GeneralSecurityException e) { throw new EsuException("Error computing request signature", e); } catch (URISyntaxException e) { throw new EsuException("Invalid URL", e); } } /** * Restores a version of an object to the base version (i.e. "promote" an * old version to the current version). * @param id Base object ID (target of the restore) * @param vId Version object ID to restore */ public void restoreVersion( ObjectId id, ObjectId vId ) { try { String resource = getResourcePath(context, id); String query = "versions"; URL u = buildUrl(resource, query); HttpURLConnection con = (HttpURLConnection) u.openConnection(); // Build headers Map<String, String> headers = new HashMap<String, String>(); headers.put("x-emc-uid", uid); // Version to promote headers.put("x-emc-version-oid", vId.toString()); // Add date headers.put("Date", getDateHeader()); // Sign request signRequest("PUT", resource, query, headers); configureRequest( con, "PUT", headers ); con.connect(); // Check response if (con.getResponseCode() > 299) { handleError(con); } con.disconnect(); } catch (MalformedURLException e) { throw new EsuException("Invalid URL", e); } catch (IOException e) { throw new EsuException("Error connecting to server", e); } catch (GeneralSecurityException e) { throw new EsuException("Error computing request signature", e); } catch (URISyntaxException e) { throw new EsuException("Invalid URL", e); } } /** * Get information about an object's state including * replicas, expiration, and retention. * @param id the object identifier * @return and ObjectInfo object containing the state information */ public ObjectInfo getObjectInfo( Identifier id ) { try { String resource = getResourcePath(context, id); String query = "info"; URL u = buildUrl(resource, query); HttpURLConnection con = (HttpURLConnection) u.openConnection(); // Build headers Map<String, String> headers = new HashMap<String, String>(); headers.put("x-emc-uid", uid); // Add date headers.put("Date", getDateHeader()); // Sign request signRequest("GET", resource, query, headers); configureRequest( con, "GET", headers ); con.connect(); // Check response if (con.getResponseCode() > 299) { handleError(con); } // Get object id list from response byte[] response = readResponse(con, null); String responseXml = new String(response, "UTF-8"); l4j.debug("Response: " + responseXml ); con.disconnect(); return new ObjectInfo( responseXml ); } catch (MalformedURLException e) { throw new EsuException("Invalid URL", e); } catch (IOException e) { throw new EsuException("Error connecting to server", e); } catch (GeneralSecurityException e) { throw new EsuException("Error computing request signature", e); } catch (URISyntaxException e) { throw new EsuException("Invalid URL", e); } } @Override public long calculateServerOffset() { try { String resource = context + "/"; URL u = buildUrl(resource, null); HttpURLConnection con = (HttpURLConnection) u.openConnection(); con.connect(); // Check response Date serverDate = new Date(con.getHeaderFieldDate("Date", 0)); if(serverDate.getTime() == 0) { EsuException e = new EsuException( "Unable to get date from server request: " + con.getResponseMessage(), con.getResponseCode()); throw(e); } return System.currentTimeMillis()-serverDate.getTime(); } catch (MalformedURLException e) { throw new EsuException("Invalid URL", e); } catch (IOException e) { throw new EsuException("Error connecting to server", e); } catch (URISyntaxException e) { throw new EsuException("Invalid URL", e); } } // /////////////////// // Private Methods // // /////////////////// protected void configureRequest( HttpURLConnection con, String method, Map<String, String> headers ) throws ProtocolException, UnsupportedEncodingException { // add any custom headers (i.e. for authentication proxy) if ( getCustomHeaders() != null ) headers.putAll( getCustomHeaders() ); // Can set all the headers, etc now. for ( String name : headers.keySet() ) { con.setRequestProperty( name, headers.get(name) ); } // Set the method. con.setRequestMethod( method ); } /** * Generates the HMAC-SHA1 signature used to authenticate the request using * the Java security APIs. * * @param method the HTTP method used * @param path the resource path * @param query the URL querystring (if present) * @param headers the HTTP headers for the request * @throws IOException if character data cannot be encoded. * @throws GeneralSecurityException If errors occur generating the HMAC-SHA1 * signature. */ protected void signRequest(String method, String path, String query, Map<String, String> headers) throws IOException, GeneralSecurityException { // Build the string to hash. ByteArrayOutputStream out = new ByteArrayOutputStream(); appendBytes( out, method + "\n", null ); // If content type exists, add it. Otherwise add a blank line. if (headers.containsKey("Content-Type")) { l4j.debug("Content-Type: " + headers.get("Content-Type")); appendBytes( out, headers.get("Content-Type") + "\n", null ); } else { appendBytes( out, "\n", null ); } // If the range header exists, add it. Otherwise add a blank line. if (headers.containsKey("Range")) { appendBytes( out, headers.get("Range") + "\n", null); } else if (headers.containsKey("Content-Range")) { appendBytes( out, headers.get("Content-Range") + "\n", null ); } else { appendBytes( out, "\n", null ); } // Add the current date and the resource. appendBytes( out, headers.get("Date") + "\n" + path.toLowerCase(), "UTF-8" ); if (query != null) { appendBytes( out,"?" + query + "\n", "UTF-8" ); } else { appendBytes( out, "\n", null ); } // Do the 'x-emc' headers. The headers must be hashed in alphabetic // order and the values must be stripped of whitespace and newlines. List<String> keys = new ArrayList<String>(); Map<String, String> newheaders = new HashMap<String, String>(); // Extract the keys and values for (Iterator<String> i = headers.keySet().iterator(); i.hasNext();) { String key = i.next(); if (key.indexOf("x-emc") == 0) { keys.add(key.toLowerCase()); newheaders.put(key.toLowerCase(), headers.get(key).replace( "\n", "")); } } // Sort the keys and add the headers to the hash string. Collections.sort(keys); boolean first = true; for (Iterator<String> i = keys.iterator(); i.hasNext();) { String key = i.next(); if (!first) { appendBytes( out, "\n", null ); } else { first = false; } // this.trace( "xheader: " . k . "." . newheaders[k] ); appendBytes( out, key + ':' + normalizeSpace(newheaders.get(key)), null ); } byte[] data = out.toByteArray(); l4j.debug("Hashing:\n" + new String(data, "UTF-8")); String hashOut = sign( data ); headers.put( "x-emc-signature", hashOut ); } private void appendBytes(OutputStream out, String s, String encoding) throws UnsupportedEncodingException, IOException { if( encoding == null ) { out.write( s.getBytes() ); } else { out.write( s.getBytes( encoding ) ); } } /** * Attempts to generate a reasonable error message from from a request. If * the error is from the web service, there should be a message and code in * the response body encapsulated in XML. * * @param con the connection from the failed request. */ protected void handleError(HttpURLConnection con) { int http_code = 0; // Try and read the response body. try { http_code = con.getResponseCode(); byte[] response = readResponse(con, null); l4j.debug("Error response: " + new String(response, "UTF-8")); SAXBuilder sb = new SAXBuilder(); Document d = sb.build(new ByteArrayInputStream(response)); String code = d.getRootElement().getChildText("Code"); String message = d.getRootElement().getChildText("Message"); if (code == null && message == null) { // not an error from ESU throw new EsuException(con.getResponseMessage(), http_code); } l4j.debug("Error: " + code + " message: " + message); throw new EsuException(message, http_code, Integer.parseInt(code)); } catch (IOException e) { l4j.debug("Could not read error response body", e); // Just throw what we know from the response try { throw new EsuException(con.getResponseMessage(), http_code); } catch (IOException e1) { l4j.warn("Could not get response code/message!", e); throw new EsuException("Could not get response code", e, http_code); } } catch (JDOMException e) { try { l4j.debug("Could not parse response body for " + http_code + ": " + con.getResponseMessage(), e); throw new EsuException("Could not parse response body for " + http_code + ": " + con.getResponseMessage(), e, http_code); } catch (IOException e1) { throw new EsuException("Could not parse response body", e1, http_code); } } } /** * Reads the response body and returns it in a byte array. * * @param con the HTTP connection * @param buffer The buffer to use to read the response. The response buffer * must be large enough to read the entire response or an error * will be thrown. * @return the byte array containing the response body. Note that if you * pass in a buffer, this will return the same buffer object unless * the content length is indeterminate. Be sure to check the content * length to know what data in the buffer is valid * (from zero to contentLength). If the content length is * indeterminate a new buffer will be returned that is the exact size * of the response data. * @throws IOException if reading the response stream fails. */ protected byte[] readResponse(HttpURLConnection con, byte[] buffer) throws IOException { InputStream in = null; if (con.getResponseCode() > 299) { in = con.getErrorStream(); if (in == null) { in = con.getInputStream(); } } else { in = con.getInputStream(); } if (in == null) { // could not get stream return new byte[0]; } try { byte[] output; int contentLength = con.getContentLength(); // If we know the content length, read it directly into a buffer. if (contentLength != -1) { if (buffer != null && buffer.length < con.getContentLength()) { throw new EsuException( "The response buffer was not long enough to hold the response: " + buffer.length + "<" + con.getContentLength()); } if (buffer != null) { output = buffer; } else { output = new byte[con.getContentLength()]; } int c = 0; while (c < contentLength) { int read = in.read(output, c, contentLength - c); if (read == -1) { // EOF! throw new EOFException( "EOF reading response at position " + c + " size " + (contentLength - c)); } c += read; } return output; } else { // Content length is indeterminate. l4j.debug("Content length is unknown. Buffering output."); // use a ByteArrayOutputStream to collect the response. if (buffer == null) { buffer = new byte[4096]; } ByteArrayOutputStream baos = new ByteArrayOutputStream(); int c = 0; while ((c = in.read(buffer)) != -1) { baos.write(buffer, 0, c); } baos.close(); l4j.debug("Buffered " + baos.size() + " response bytes"); return baos.toByteArray(); } } finally { if (in != null) { in.close(); } } } // Getters/Setters public Map<String, String> getCustomHeaders() { return customHeaders; } public void setCustomHeaders(Map<String, String> customHeaders) { this.customHeaders = customHeaders; } //---------- Features supported by the Atmos 2.0 REST API. ----------\\ @Override public void hardLink( ObjectPath source, ObjectPath target ) { try { String resource = getResourcePath( context, source ); String query = "hardlink"; URL u = buildUrl( resource, query ); HttpURLConnection con = (HttpURLConnection) u.openConnection(); // Build headers Map<String, String> headers = new HashMap<String, String>(); headers.put( "x-emc-uid", uid ); String destPath = target.toString(); if ( destPath.startsWith( "/" ) ) { destPath = destPath.substring( 1 ); } headers.put( "x-emc-path", destPath ); // Add date headers.put( "Date", getDateHeader() ); // Compute checksum // Sign request signRequest( "POST", resource, query, headers ); configureRequest( con, "POST", headers ); con.connect(); // Check response if ( con.getResponseCode() > 299 ) { handleError( con ); } con.disconnect(); } catch ( MalformedURLException e ) { throw new EsuException( "Invalid URL", e ); } catch ( IOException e ) { throw new EsuException( "Error connecting to server", e ); } catch ( GeneralSecurityException e ) { throw new EsuException( "Error computing request signature", e ); } catch ( URISyntaxException e ) { throw new EsuException( "Invalid URL", e ); } } @Override public ObjectId createObjectWithKeyFromSegment( String keyPool, String key, Acl acl, MetadataList metadata, BufferSegment data, String mimeType, Checksum checksum ) { try { String resource = context + "/namespace/" + key; URL u = buildUrl( resource, null ); HttpURLConnection con = (HttpURLConnection) u.openConnection(); // Build headers Map<String, String> headers = new HashMap<String, String>(); // Figure out the mimetype if ( mimeType == null ) { mimeType = "application/octet-stream"; } headers.put( "Content-Type", mimeType ); headers.put( "x-emc-uid", uid ); headers.put( "x-emc-pool", keyPool ); // Process metadata if ( metadata != null ) { processMetadata( metadata, headers ); } l4j.debug( "meta " + headers.get( "x-emc-meta" ) ); // Add acl if ( acl != null ) { processAcl( acl, headers ); } // Process data if ( data == null ) { data = new BufferSegment( new byte[0] ); } con.setFixedLengthStreamingMode( data.getSize() ); con.setDoOutput( true ); // Add date headers.put( "Date", getDateHeader() ); // Compute checksum if ( checksum != null ) { checksum.update( data.getBuffer(), data.getOffset(), data.getSize() ); headers.put( "x-emc-wschecksum", checksum.toString() ); } // Sign request signRequest( "POST", resource, null, headers ); configureRequest( con, "POST", headers ); con.connect(); // post data OutputStream out = null; try { out = con.getOutputStream(); out.write( data.getBuffer(), data.getOffset(), data.getSize() ); out.close(); } catch ( IOException e ) { silentClose( out ); con.disconnect(); throw new EsuException( "Error posting data", e ); } // Check response if ( con.getResponseCode() > 299 ) { handleError( con ); } // The new object ID is returned in the location response header String location = con.getHeaderField( "location" ); con.disconnect(); // Parse the value out of the URL return getObjectId( location ); } catch ( MalformedURLException e ) { throw new EsuException( "Invalid URL", e ); } catch ( IOException e ) { throw new EsuException( "Error connecting to server", e ); } catch ( GeneralSecurityException e ) { throw new EsuException( "Error computing request signature", e ); } catch ( URISyntaxException e ) { throw new EsuException( "Invalid URL", e ); } } @Override public ObjectId createObjectWithKeyFromStream( String keyPool, String key, Acl acl, MetadataList metadata, InputStream data, long length, String mimeType ) { try { String resource = context + "/namespace/" + key; URL u = buildUrl( resource, null ); HttpURLConnection con = (HttpURLConnection) u.openConnection(); if ( data == null ) { throw new IllegalArgumentException( "Input stream is required" ); } // Build headers Map<String, String> headers = new HashMap<String, String>(); // Figure out the mimetype if ( mimeType == null ) { mimeType = "application/octet-stream"; } headers.put( "Content-Type", mimeType ); headers.put( "x-emc-uid", uid ); headers.put( "x-emc-pool", keyPool ); // Process metadata if ( metadata != null ) { processMetadata( metadata, headers ); } l4j.debug( "meta " + headers.get( "x-emc-meta" ) ); // Add acl if ( acl != null ) { processAcl( acl, headers ); } con.setFixedLengthStreamingMode( (int) length ); con.setDoOutput( true ); // Add date headers.put( "Date", getDateHeader() ); // Sign request signRequest( "POST", resource, null, headers ); configureRequest( con, "POST", headers ); con.connect(); // post data OutputStream out = null; byte[] buffer = new byte[128 * 1024]; int read = 0; try { out = con.getOutputStream(); while ( read < length ) { // make sure we don't write past the content-length int maxRead = (int) Math.min( (long) buffer.length, length - read ); int c = data.read( buffer, 0, maxRead ); if ( c == -1 ) { throw new EsuException( "EOF encountered reading data stream" ); } out.write( buffer, 0, c ); read += c; } out.close(); } catch ( IOException e ) { silentClose( out ); con.disconnect(); throw new EsuException( "Error posting data", e ); } // Check response if ( con.getResponseCode() > 299 ) { handleError( con ); } // The new object ID is returned in the location response header String location = con.getHeaderField( "location" ); con.disconnect(); // Parse the value out of the URL return getObjectId( location ); } catch ( MalformedURLException e ) { throw new EsuException( "Invalid URL", e ); } catch ( IOException e ) { throw new EsuException( "Error connecting to server", e ); } catch ( GeneralSecurityException e ) { throw new EsuException( "Error computing request signature", e ); } catch ( URISyntaxException e ) { throw new EsuException( "Invalid URL", e ); } } @Override public void deleteObjectWithKey( String keyPool, String key ) { try { String resource = context + "/namespace/" + key; URL u = buildUrl( resource, null ); HttpURLConnection con = (HttpURLConnection) u.openConnection(); // Build headers Map<String, String> headers = new HashMap<String, String>(); headers.put( "x-emc-uid", uid ); headers.put( "x-emc-pool", keyPool ); // Add date headers.put( "Date", getDateHeader() ); // Sign request signRequest( "DELETE", resource, null, headers ); configureRequest( con, "DELETE", headers ); con.connect(); // Check response if ( con.getResponseCode() > 299 ) { handleError( con ); } con.disconnect(); } catch ( MalformedURLException e ) { throw new EsuException( "Invalid URL", e ); } catch ( IOException e ) { throw new EsuException( "Error connecting to server", e ); } catch ( GeneralSecurityException e ) { throw new EsuException( "Error computing request signature", e ); } catch ( URISyntaxException e ) { throw new EsuException( "Invalid URL", e ); } } @Override public ObjectMetadata getAllMetadata( String keyPool, String key ) { try { String resource = context + "/namespace/" + key; URL u = buildUrl( resource, null ); HttpURLConnection con = (HttpURLConnection) u.openConnection(); // Build headers Map<String, String> headers = new HashMap<String, String>(); headers.put( "x-emc-uid", uid ); headers.put( "x-emc-pool", keyPool ); if ( unicodeEnabled ) { headers.put( "x-emc-utf8", "true" ); } // Add date headers.put( "Date", getDateHeader() ); // Sign request signRequest( "HEAD", resource, null, headers ); configureRequest( con, "HEAD", headers ); con.connect(); // Check response if ( con.getResponseCode() > 299 ) { handleError( con ); } // Parse return headers. User grants are in x-emc-useracl and // group grants are in x-emc-groupacl Acl acl = new Acl(); readAcl( acl, con.getHeaderField( "x-emc-useracl" ), Grantee.GRANT_TYPE.USER ); readAcl( acl, con.getHeaderField( "x-emc-groupacl" ), Grantee.GRANT_TYPE.GROUP ); // Parse return headers. Regular metadata is in x-emc-meta and // listable metadata is in x-emc-listable-meta MetadataList meta = new MetadataList(); readMetadata( meta, con.getHeaderField( "x-emc-meta" ), false ); readMetadata( meta, con.getHeaderField( "x-emc-listable-meta" ), true ); ObjectMetadata om = new ObjectMetadata(); om.setAcl( acl ); om.setMetadata( meta ); om.setMimeType( con.getContentType() ); return om; } catch ( MalformedURLException e ) { throw new EsuException( "Invalid URL", e ); } catch ( IOException e ) { throw new EsuException( "Error connecting to server", e ); } catch ( GeneralSecurityException e ) { throw new EsuException( "Error computing request signature", e ); } catch ( URISyntaxException e ) { throw new EsuException( "Invalid URL", e ); } } @Override public MetadataList getSystemMetadata( String keyPool, String key, MetadataTags tags ) { try { String resource = context + "/namespace/" + key; String query = "metadata/system"; URL u = buildUrl( resource, query ); HttpURLConnection con = (HttpURLConnection) u.openConnection(); // Build headers Map<String, String> headers = new HashMap<String, String>(); headers.put( "x-emc-uid", uid ); headers.put( "x-emc-pool", keyPool ); // process tags if ( tags != null ) { processTags( tags, headers ); } // Add date headers.put( "Date", getDateHeader() ); // Sign request signRequest( "GET", resource, query, headers ); configureRequest( con, "GET", headers ); con.connect(); // Check response if ( con.getResponseCode() > 299 ) { handleError( con ); } // Parse return headers. Regular metadata is in x-emc-meta and // listable metadata is in x-emc-listable-meta MetadataList meta = new MetadataList(); readMetadata( meta, con.getHeaderField( "x-emc-meta" ), false ); readMetadata( meta, con.getHeaderField( "x-emc-listable-meta" ), true ); con.disconnect(); return meta; } catch ( MalformedURLException e ) { throw new EsuException( "Invalid URL", e ); } catch ( IOException e ) { throw new EsuException( "Error connecting to server", e ); } catch ( GeneralSecurityException e ) { throw new EsuException( "Error computing request signature", e ); } catch ( URISyntaxException e ) { throw new EsuException( "Invalid URL", e ); } } @Override public byte[] readObjectWithKey( String keyPool, String key, Extent extent, byte[] buffer, Checksum checksum ) { try { String resource = context + "/namespace/" + key; URL u = buildUrl( resource, null ); HttpURLConnection con = (HttpURLConnection) u.openConnection(); // Build headers Map<String, String> headers = new HashMap<String, String>(); headers.put( "x-emc-uid", uid ); headers.put( "x-emc-pool", keyPool ); // Add date headers.put( "Date", getDateHeader() ); // Add extent if needed if ( extent != null && !extent.equals( Extent.ALL_CONTENT ) ) { headers.put( extent.getHeaderName(), extent.toString() ); } // Sign request signRequest( "GET", resource, null, headers ); configureRequest( con, "GET", headers ); con.connect(); // Check response if ( con.getResponseCode() > 299 ) { handleError( con ); } if ( buffer != null && extent != null ) { if ( extent.getSize() > (long) buffer.length ) { throw new IllegalArgumentException( "The buffer is smaller than the requested extent" ); } } // The requested content is in the response body. byte[] data = readResponse( con, buffer ); // See if a checksum was returned. String checksumStr = con.getHeaderField( "x-emc-wschecksum" ); if ( checksumStr != null && checksum != null ) { l4j.debug( "Checksum header: " + checksumStr ); checksum.setExpectedValue( checksumStr ); if ( con.getContentLength() != -1 ) { checksum.update( data, 0, con.getContentLength() ); } else { // readResponse should return a new content-sized buffer in this case checksum.update( data, 0, data.length ); } } con.disconnect(); return data; } catch ( MalformedURLException e ) { throw new EsuException( "Invalid URL", e ); } catch ( IOException e ) { throw new EsuException( "Error connecting to server", e ); } catch ( GeneralSecurityException e ) { throw new EsuException( "Error computing request signature", e ); } catch ( URISyntaxException e ) { throw new EsuException( "Invalid URL", e ); } } @Override public InputStream readObjectStreamWithKey( String keyPool, String key, Extent extent ) { try { String resource = context + "/namespace/" + key; URL u = buildUrl( resource, null ); HttpURLConnection con = (HttpURLConnection) u.openConnection(); // Build headers Map<String, String> headers = new HashMap<String, String>(); headers.put( "x-emc-uid", uid ); headers.put( "x-emc-pool", keyPool ); // Add date headers.put( "Date", getDateHeader() ); // Add extent if needed if ( extent != null && !extent.equals( Extent.ALL_CONTENT ) ) { headers.put( extent.getHeaderName(), extent.toString() ); } // Sign request signRequest( "GET", resource, null, headers ); configureRequest( con, "GET", headers ); con.connect(); // Check response if ( con.getResponseCode() > 299 ) { handleError( con ); } return new HttpInputStreamWrapper( con.getInputStream(), con ); } catch ( MalformedURLException e ) { throw new EsuException( "Invalid URL", e ); } catch ( IOException e ) { throw new EsuException( "Error connecting to server", e ); } catch ( GeneralSecurityException e ) { throw new EsuException( "Error computing request signature", e ); } catch ( URISyntaxException e ) { throw new EsuException( "Invalid URL", e ); } } @Override public void updateObjectWithKeyFromStream( String keyPool, String key, Acl acl, MetadataList metadata, Extent extent, InputStream data, long length, String mimeType ) { try { String resource = context + "/namespace/" + key; URL u = buildUrl( resource, null ); HttpURLConnection con = (HttpURLConnection) u.openConnection(); // Build headers Map<String, String> headers = new HashMap<String, String>(); // Figure out the mimetype if ( mimeType == null ) { mimeType = "application/octet-stream"; } headers.put( "Content-Type", mimeType ); headers.put( "x-emc-uid", uid ); headers.put( "x-emc-pool", keyPool ); // Process metadata if ( metadata != null ) { processMetadata( metadata, headers ); } l4j.debug( "meta " + headers.get( "x-emc-meta" ) ); // Add acl if ( acl != null ) { processAcl( acl, headers ); } // Add extent if needed if ( extent != null && !extent.equals( Extent.ALL_CONTENT ) ) { headers.put( extent.getHeaderName(), extent.toString() ); } con.setFixedLengthStreamingMode( (int) length ); con.setDoOutput( true ); // Add date headers.put( "Date", getDateHeader() ); // Sign request signRequest( "PUT", resource, null, headers ); configureRequest( con, "PUT", headers ); con.connect(); // post data OutputStream out = null; byte[] buffer = new byte[128 * 1024]; int read = 0; try { out = con.getOutputStream(); while ( read < length ) { // make sure we don't write past the content-length int maxRead = (int) Math.min( (long) buffer.length, length - read ); int c = data.read( buffer, 0, maxRead ); if ( c == -1 ) { throw new EsuException( "EOF encountered reading data stream" ); } out.write( buffer, 0, c ); read += c; } out.close(); } catch ( IOException e ) { silentClose( out ); con.disconnect(); throw new EsuException( "Error posting data", e ); } // Check response if ( con.getResponseCode() > 299 ) { handleError( con ); } con.disconnect(); } catch ( MalformedURLException e ) { throw new EsuException( "Invalid URL", e ); } catch ( IOException e ) { throw new EsuException( "Error connecting to server", e ); } catch ( GeneralSecurityException e ) { throw new EsuException( "Error computing request signature", e ); } catch ( URISyntaxException e ) { throw new EsuException( "Invalid URL", e ); } } @Override public void updateObjectWithKeyFromSegment( String keyPool, String key, Acl acl, MetadataList metadata, Extent extent, BufferSegment data, String mimeType, Checksum checksum ) { try { String resource = context + "/namespace/" + key; URL u = buildUrl( resource, null ); HttpURLConnection con = (HttpURLConnection) u.openConnection(); // Build headers Map<String, String> headers = new HashMap<String, String>(); // Figure out the mimetype if ( mimeType == null ) { mimeType = "application/octet-stream"; } headers.put( "Content-Type", mimeType ); headers.put( "x-emc-uid", uid ); headers.put( "x-emc-pool", keyPool ); // Process metadata if ( metadata != null ) { processMetadata( metadata, headers ); } l4j.debug( "meta " + headers.get( "x-emc-meta" ) ); // Add acl if ( acl != null ) { processAcl( acl, headers ); } // Add extent if needed if ( extent != null && !extent.equals( Extent.ALL_CONTENT ) ) { headers.put( extent.getHeaderName(), extent.toString() ); } // Process data if ( data == null ) { data = new BufferSegment( new byte[0] ); } con.setFixedLengthStreamingMode( data.getSize() ); con.setDoOutput( true ); // Add date headers.put( "Date", getDateHeader() ); // Compute checksum if ( checksum != null ) { checksum.update( data.getBuffer(), data.getOffset(), data.getSize() ); headers.put( "x-emc-wschecksum", checksum.toString() ); } // Sign request signRequest( "PUT", resource, null, headers ); configureRequest( con, "PUT", headers ); con.connect(); // post data OutputStream out = null; try { out = con.getOutputStream(); out.write( data.getBuffer(), data.getOffset(), data.getSize() ); out.close(); } catch ( IOException e ) { silentClose( out ); con.disconnect(); throw new EsuException( "Error posting data", e ); } // Check response if ( con.getResponseCode() > 299 ) { handleError( con ); } con.disconnect(); } catch ( MalformedURLException e ) { throw new EsuException( "Invalid URL", e ); } catch ( IOException e ) { throw new EsuException( "Error connecting to server", e ); } catch ( GeneralSecurityException e ) { throw new EsuException( "Error computing request signature", e ); } catch ( URISyntaxException e ) { throw new EsuException( "Invalid URL", e ); } } }