// // typica - A client library for Amazon Web Services // Copyright (C) 2007 Xerox Corporation // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License 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.xerox.amazonws.simpledb; import java.io.InputStream; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.Hashtable; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import javax.xml.bind.JAXBException; import org.xml.sax.SAXException; import org.apache.commons.codec.binary.Base64; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.http.client.HttpClient; import org.apache.http.HttpException; import org.apache.http.client.methods.HttpRequestBase; import org.apache.http.client.methods.HttpGet; import com.xerox.amazonws.common.AWSException; import com.xerox.amazonws.common.AWSQueryConnection; import com.xerox.amazonws.common.ListResult; import com.xerox.amazonws.common.Result; import com.xerox.amazonws.typica.sdb.jaxb.Attribute; import com.xerox.amazonws.typica.sdb.jaxb.BatchDeleteAttributesResponse; import com.xerox.amazonws.typica.sdb.jaxb.BatchPutAttributesResponse; import com.xerox.amazonws.typica.sdb.jaxb.DeleteAttributesResponse; import com.xerox.amazonws.typica.sdb.jaxb.DomainMetadataResponse; import com.xerox.amazonws.typica.sdb.jaxb.GetAttributesResponse; import com.xerox.amazonws.typica.sdb.jaxb.PutAttributesResponse; import com.xerox.amazonws.typica.sdb.jaxb.SelectResponse; /** * This class provides an interface with the Amazon SDB service. It provides methods for * listing and deleting items. * * @author D. Kavanagh * @author developer@dotech.com */ public class Domain { private static Log logger = LogFactory.getLog(Domain.class); private AWSQueryConnection connection; // connection delegate private String domainName; private ItemCache cache; protected Domain(String domainName, AWSQueryConnection connection) throws SDBException { this.domainName = domainName; this.connection = connection; } /** * Returns connection object, so connection params can be tweaked */ public AWSQueryConnection getConnectionDelegate() { return connection; } /** * Gets the name of the domain represented by this object. * * @return the name of the domain */ public String getName() { return domainName; } /** * Returns information about the domain. * * @return the object containing metadata about this domain * @throws SDBException wraps checked exceptions */ public SDBResult<DomainMetadata> getMetadata() throws SDBException { Map<String, String> params = new HashMap<String, String>(); params.put("DomainName", domainName); HttpGet method = new HttpGet(); DomainMetadataResponse response = makeRequestInt(method, "DomainMetadata", params, DomainMetadataResponse.class); com.xerox.amazonws.typica.sdb.jaxb.DomainMetadataResult result = response.getDomainMetadataResult(); return new SDBResult<DomainMetadata>(response.getResponseMetadata().getRequestId(), response.getResponseMetadata().getBoxUsage(), new DomainMetadata(Integer.parseInt(result.getItemCount()), Integer.parseInt(result.getAttributeValueCount()), Integer.parseInt(result.getAttributeNameCount()), Long.parseLong(result.getItemNamesSizeBytes()), Long.parseLong(result.getAttributeValuesSizeBytes()), Long.parseLong(result.getAttributeNamesSizeBytes()), new Date(Long.parseLong(result.getTimestamp())*1000))); } /** * Adds an item. * * @param item the item to add to this domain * @throws SDBException wraps checked exceptions */ public SDBResult addItem(Item item) throws SDBException { return addItem(item.getIdentifier(), item.getAttributes(), null); } /** * Adds an item. This method also works to add attributes to an existing item. * * @param identifier the name of the item to be added * @param attributes the attributes to associate with this item * @param conditions the conditions under which attributes should be put * @throws SDBException wraps checked exceptions */ public SDBResult addItem(String identifier, Map<String, Set<String>> attributes, List<Condition> conditions) throws SDBException { Map<String, String> params = new HashMap<String, String>(); params.put("DomainName", domainName); params.put("ItemName", identifier); int i=1; for (String key : attributes.keySet()) { Set<String> vals = attributes.get(key); if (vals != null && vals.size() > 0) { Iterator<String> iter = vals.iterator(); while (iter.hasNext()) { String val = iter.next(); params.put("Attribute."+i+".Name", key); params.put("Attribute."+i+".Value", val); i++; } } } if (conditions != null) { i=1; for (Condition cond : conditions) { params.put("Expected."+i+".Name", cond.getName()); String value = cond.getValue(); if (value != null) { params.put("Expected."+i+".Value", value); } else { params.put("Expected."+i+".Exists", cond.isExists()?"true":"false"); } i++; } } HttpGet method = new HttpGet(); PutAttributesResponse response = makeRequestInt(method, "PutAttributes", params, PutAttributesResponse.class); if (cache != null) { // create new item object Item newItem = new ItemVO(identifier); Map<String, Set<String>> attrs = newItem.getAttributes(); // throw attrs into it attrs.putAll(attributes); Item old = cache.getItem(identifier); if (old != null) { // merge cached attrs with those just set for (String key: old.getAttributes().keySet()) { Set<String> oldAttrs = old.getAttributes().get(key); Set<String> newAttrs = attrs.get(key); if (newAttrs != null) { newAttrs.addAll(oldAttrs); } else { attrs.put(key, oldAttrs); } } } // place/replace item in cache cache.putItem(newItem); } return new SDBResult(response.getResponseMetadata().getRequestId(), response.getResponseMetadata().getBoxUsage()); } /** * Batch inserts multiple items w/ attributes * * @param attributes list of attributes to add * @throws SDBException wraps checked exceptions */ public SDBResult batchPutAttributes(List<Item> items) throws SDBException { Map<String, String> params = new HashMap<String, String>(); params.put("DomainName", domainName); int k=1; for (Item item : items) { params.put("Item."+k+".ItemName", item.getIdentifier()); int i=1; for (String attr : item.getAttributes().keySet()) { Set<String> vals = item.getAttributeValues(attr); if (vals != null && vals.size() > 0) { for (String val : vals) { params.put("Item."+k+".Attribute."+i+".Name", attr); params.put("Item."+k+".Attribute."+i+".Value", val); i++; // if (attr.isReplace()) { // params.put("Item."+k+".Attribute."+i+".Replace", "true"); // } } } } k++; } HttpGet method = new HttpGet(); BatchPutAttributesResponse response = makeRequestInt(method, "BatchPutAttributes", params, BatchPutAttributesResponse.class); return new SDBResult(response.getResponseMetadata().getRequestId(), response.getResponseMetadata().getBoxUsage()); } /** * Replace attributes on an item. Using this call will force attribute values to be * with the new ones supplied. * * @param identifier the name of the item to be added * @param attributes the attributes to associate with this item * @param conditions the conditions under which attributes should be put * @throws SDBException wraps checked exceptions */ public SDBResult replaceAttributes(String identifier, Map<String, Set<String>> attributes, List<Condition> conditions) throws SDBException { Map<String, String> params = new HashMap<String, String>(); params.put("DomainName", domainName); params.put("ItemName", identifier); int i=1; for (String key : attributes.keySet()) { Set<String> vals = attributes.get(key); if (vals != null && vals.size() > 0) { Iterator<String> iter = vals.iterator(); while (iter.hasNext()) { String val = iter.next(); params.put("Attribute."+i+".Name", key); params.put("Attribute."+i+".Value", val); params.put("Attribute."+i+".Replace", "true"); i++; } } } if (conditions != null) { i=1; for (Condition cond : conditions) { params.put("Expected."+i+".Name", cond.getName()); String value = cond.getValue(); if (value != null) { params.put("Expected."+i+".Value", value); } else { params.put("Expected."+i+".Exists", cond.isExists()?"true":"false"); } i++; } } HttpGet method = new HttpGet(); PutAttributesResponse response = makeRequestInt(method, "PutAttributes", params, PutAttributesResponse.class); if (cache != null) { // create new item object Item newItem = new ItemVO(identifier); Map<String, Set<String>> attrs = newItem.getAttributes(); // throw attrs into it attrs.putAll(attributes); Item old = cache.getItem(identifier); if (old != null) { // merge cached attrs attrs.putAll(old.getAttributes()); } // place/replace item in cache cache.putItem(newItem); } return new SDBResult(response.getResponseMetadata().getRequestId(), response.getResponseMetadata().getBoxUsage()); } /** * Deletes attributes from an item * * @param identifier the name of the item * @param attributes the names of the attributes to be deleted * @param conditions the conditions under which attributes should be put * @throws SDBException wraps checked exceptions */ public SDBResult deleteAttributes(String identifier, Set<String> attributes, List<Condition> conditions) throws SDBException { Map<String, String> params = new HashMap<String, String>(); params.put("DomainName", domainName); params.put("ItemName", identifier); int i=1; if (attributes != null && attributes.size() > 0) { Iterator<String> iter = attributes.iterator(); while (iter.hasNext()) { String val = iter.next(); params.put("Attribute."+i+".Name", val); i++; } } if (conditions != null) { i=1; for (Condition cond : conditions) { params.put("Expected."+i+".Name", cond.getName()); String value = cond.getValue(); if (value != null) { params.put("Expected."+i+".Value", value); } else { params.put("Expected."+i+".Exists", cond.isExists()?"true":"false"); } i++; } } HttpGet method = new HttpGet(); DeleteAttributesResponse response = makeRequestInt(method, "DeleteAttributes", params, DeleteAttributesResponse.class); if (cache != null) { // cache.removeItem(identifier); } return new SDBResult(response.getResponseMetadata().getRequestId(), response.getResponseMetadata().getBoxUsage()); } /** * Batch deletes multiple items w/ attributes * * @param attributes list of attributes to delete * @throws SDBException wraps checked exceptions */ public SDBResult batchDeleteAttributes(List<Item> items) throws SDBException { Map<String, String> params = new HashMap<String, String>(); params.put("DomainName", domainName); int k=1; for (Item item : items) { params.put("Item."+k+".ItemName", item.getIdentifier()); int i=1; for (String attr : item.getAttributes().keySet()) { Set<String> vals = item.getAttributeValues(attr); if (vals != null && vals.size() > 0) { for (String val : vals) { params.put("Item."+k+".Attribute."+i+".Name", attr); params.put("Item."+k+".Attribute."+i+".Value", val); i++; // if (attr.isReplace()) { // params.put("Item."+k+".Attribute."+i+".Replace", "true"); // } } } } k++; } HttpGet method = new HttpGet(); BatchDeleteAttributesResponse response = makeRequestInt(method, "BatchDeleteAttributes", params, BatchDeleteAttributesResponse.class); return new SDBResult(response.getResponseMetadata().getRequestId(), response.getResponseMetadata().getBoxUsage()); } /** * Deletes an item. * * @param identifier the name of the item to be deleted * @throws SDBException wraps checked exceptions */ public SDBResult deleteItem(String identifier) throws SDBException { Map<String, String> params = new HashMap<String, String>(); params.put("DomainName", domainName); params.put("ItemName", identifier); HttpGet method = new HttpGet(); DeleteAttributesResponse response = makeRequestInt(method, "DeleteAttributes", params, DeleteAttributesResponse.class); if (cache != null) { cache.removeItem(identifier); } return new SDBResult(response.getResponseMetadata().getRequestId(), response.getResponseMetadata().getBoxUsage()); } /** * This method returns an item object, which exists locally only. * It must be persisted with addItem, batchPutAttributes or replaceAttributes * * @param identifier the name of the item to be deleted * @return item object * @throws SDBException wraps checked exceptions */ public Item createItem(String identifier) { return new ItemVO(identifier); } /** * Returns an named item. * * @param identifier the name of the item to be deleted * @throws SDBException wraps checked exceptions */ public Result<Item> getItem(String identifier) throws SDBException { if (cache != null) { Item cached = cache.getItem(identifier); if (cached != null) { return new Result<Item>(null, cached); } // else, go fetch it anyway } Map<String, String> params = new HashMap<String, String>(); params.put("DomainName", domainName); params.put("ItemName", identifier); HttpGet method = new HttpGet(); GetAttributesResponse response = makeRequestInt(method, "GetAttributes", params, GetAttributesResponse.class); Item newItem = new ItemVO(identifier); Map<String, Set<String>> attrs = newItem.getAttributes(); for (Attribute a : response.getGetAttributesResult().getAttributes()) { String name = a.getName().getValue(); String encoding = a.getName().getEncoding(); if (encoding != null && encoding.equals("base64")) { name = new String(Base64.decodeBase64(name.getBytes())); } String value = a.getValue().getValue(); encoding = a.getValue().getEncoding(); if (encoding != null && encoding.equals("base64")) { value = new String(Base64.decodeBase64(value.getBytes())); } Set<String> vals = attrs.get(name); if (vals == null) { vals = Collections.synchronizedSet(new HashSet<String>()); attrs.put(name, vals); } vals.add(value); } if (cache != null) { cache.putItem(newItem); } return new Result<Item>(null, newItem); } /** * Performs a query against this domain. A set of items is returned which may not be * complete. If the nextToken in the result is set, this method can be called again * with the same query and the supplied nextToken. * * @param selectExpression the query to be run * @param nextToken an optional token to get more results * @param consistent if true, consistency is assured * @throws SDBException wraps checked exceptions */ public SDBListResult<Item> selectItems(String selectExpression, String nextToken, boolean consistent) throws SDBException { Map<String, String> params = new HashMap<String, String>(); params.put("SelectExpression", selectExpression); if (nextToken != null) { params.put("NextToken", nextToken); } if (consistent) { params.put("ConsistentRead", "true"); } HttpGet method = new HttpGet(); SelectResponse response = makeRequestInt(method, "Select", params, SelectResponse.class); SDBListResult<Item> ret = new SDBListResult<Item>( response.getSelectResult().getNextToken(), response.getResponseMetadata().getRequestId(), response.getResponseMetadata().getBoxUsage()); List<Item> results = ret.getItems(); for (com.xerox.amazonws.typica.sdb.jaxb.Item i : response.getSelectResult().getItems()) { String iName = i.getName().getValue(); String encoding = i.getName().getEncoding(); if (encoding != null && encoding.equals("base64")) { iName = new String(Base64.decodeBase64(iName.getBytes())); } Item newItem = new ItemVO(iName); Map<String, Set<String>> attrs = newItem.getAttributes(); for (Attribute a : i.getAttributes()) { String name = a.getName().getValue(); encoding = a.getName().getEncoding(); if (encoding != null && encoding.equals("base64")) { name = new String(Base64.decodeBase64(name.getBytes())); } String value = a.getValue().getValue(); encoding = a.getValue().getEncoding(); if (encoding != null && encoding.equals("base64")) { value = new String(Base64.decodeBase64(value.getBytes())); } Set<String> vals = attrs.get(name); if (vals == null) { vals = Collections.synchronizedSet(new HashSet<String>()); attrs.put(name, vals); } vals.add(value); } results.add(newItem); } return ret; } public ItemCache getItemCache() { return this.cache; } public void setCacheProvider(ItemCache cache) { this.cache = cache; } static List<Domain> createList(String [] domainNames, AWSQueryConnection connection) throws SDBException { ArrayList<Domain> ret = new ArrayList<Domain>(); for (int i=0; i<domainNames.length; i++) { Domain dom = new Domain(domainNames[i], connection); ret.add(dom); } return ret; } protected <T> T makeRequestInt(HttpRequestBase method, String action, Map<String, String> params, Class<T> respType) throws SDBException { try { return connection.makeRequest(method, action, params, respType); } catch (AWSException ex) { throw new SDBException(ex); } catch (JAXBException ex) { throw new SDBException("Problem parsing returned message.", ex); } catch (SAXException ex) { throw new SDBException("Problem parsing returned message.", ex); } catch (HttpException ex) { throw new SDBException(ex.getMessage(), ex); } catch (IOException ex) { throw new SDBException(ex.getMessage(), ex); } } }