/* * JBoss, Home of Professional Open Source. * See the COPYRIGHT.txt file distributed with this work for information * regarding copyright ownership. Some portions may be licensed * to Red Hat, Inc. under one or more contributor license agreements. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ package org.teiid.resource.adapter.simpledb; import java.util.ArrayList; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; import javax.resource.ResourceException; import org.teiid.language.visitor.SQLStringVisitor; import org.teiid.metadata.Column; import org.teiid.resource.spi.BasicConnection; import org.teiid.translator.TranslatorException; import org.teiid.translator.simpledb.api.SimpleDBConnection; import org.teiid.translator.simpledb.api.SimpleDBDataTypeManager; import com.amazonaws.AmazonClientException; import com.amazonaws.AmazonServiceException; import com.amazonaws.auth.BasicAWSCredentials; import com.amazonaws.services.simpledb.AmazonSimpleDBClient; import com.amazonaws.services.simpledb.model.*; public class SimpleDBConnectionImpl extends BasicConnection implements SimpleDBConnection { private AmazonSimpleDBClient client; private List<String> domains; public SimpleDBConnectionImpl(String accessKey, String secretAccessKey) { this.client = new AmazonSimpleDBClient(new BasicAWSCredentials(accessKey, secretAccessKey)); } public void close() throws ResourceException { } @Override public void createDomain(String domainName) throws TranslatorException { try { this.client.createDomain(new CreateDomainRequest(domainName)); } catch (AmazonServiceException e) { throw new TranslatorException(e); } catch (AmazonClientException e) { throw new TranslatorException(e); } } @Override public void deleteDomain(String domainName) throws TranslatorException { try { this.client.deleteDomain(new DeleteDomainRequest(domainName)); if (this.domains.contains(domainName)) { this.domains.remove(domainName); } } catch (AmazonServiceException e) { throw new TranslatorException(e); } catch (AmazonClientException e) { throw new TranslatorException(e); } } @Override public List<String> getDomains() throws TranslatorException { return client.listDomains().getDomainNames(); } @Override public Set<SimpleDBAttribute> getAttributeNames(String domainName) throws TranslatorException { DomainMetadataRequest domainMetadataRequest = new DomainMetadataRequest(domainName); DomainMetadataResult metadataResult = client.domainMetadata(domainMetadataRequest); int attributesCount = metadataResult.getAttributeNameCount(); SelectResult selectResult = client.select(new SelectRequest("SELECT * FROM " + domainName)); //$NON-NLS-1$ return getAttributeNamesFromSelectResult(selectResult, attributesCount); } /** * Removes item with given ItemName from domain * @param domainName * @param itemName */ @Override public int performDelete(String domainName, String selectExpression) throws TranslatorException { try { List<DeletableItem> deleteItems = new ArrayList<DeletableItem>(); int count = 0; String nextToken = null; do { SelectResult result = performSelect(selectExpression, nextToken); nextToken = result.getNextToken(); Iterator<Item> iter = result.getItems().iterator(); while (iter.hasNext()) { Item item = iter.next(); deleteItems.add(new DeletableItem(item.getName(), null)); count++; if (count%25 == 0) { BatchDeleteAttributesRequest request = new BatchDeleteAttributesRequest(domainName, deleteItems); this.client.batchDeleteAttributes(request); deleteItems.clear(); } } // http://docs.aws.amazon.com/AmazonSimpleDB/latest/DeveloperGuide/SDB_API_BatchDeleteAttributes.html // 25 limit we may need to batch; but if batch atomicity is gone if (!deleteItems.isEmpty()) { BatchDeleteAttributesRequest request = new BatchDeleteAttributesRequest(domainName, deleteItems); this.client.batchDeleteAttributes(request); } } while (nextToken != null); return count; } catch (AmazonServiceException e) { throw new TranslatorException(e); } catch (AmazonClientException e) { throw new TranslatorException(e); } } /** * Performs select expression. This expression must be in format which is understandable to SimpleDB database * @param selectExpression * @param columns * @return Iterator of List<String> results */ @Override public SelectResult performSelect(String selectExpression, String nextToken) throws TranslatorException{ try { SelectRequest selectRequest = new SelectRequest(selectExpression); if (nextToken != null) { selectRequest.setNextToken(nextToken); } selectRequest.setConsistentRead(true); return client.select(selectRequest); } catch (AmazonServiceException e) { throw new TranslatorException(e); } catch (AmazonClientException e) { throw new TranslatorException(e); } } /** * Performs update on given domain and items * @param domainName * @param items */ @Override public int performUpdate(String domainName, Map<String, Object> updateAttributes, String selectExpression) throws TranslatorException { try { List<ReplaceableAttribute> attributes = new ArrayList<ReplaceableAttribute>(); for (Map.Entry<String, Object> column : updateAttributes.entrySet()) { addAttribute(column.getKey(), column.getValue(), attributes); } List<ReplaceableItem> updateItems = new ArrayList<ReplaceableItem>(); int count = 0; String nextToken = null; do { SelectResult result = performSelect(selectExpression, nextToken); nextToken = result.getNextToken(); Iterator<Item> iter = result.getItems().iterator(); while (iter.hasNext()) { Item item = iter.next(); updateItems.add(new ReplaceableItem(item.getName(), attributes)); count++; if (count%25 == 0) { executeBatch(domainName, updateItems); updateItems.clear(); } } executeBatch(domainName, updateItems); } while (nextToken != null); return count; } catch (AmazonServiceException e) { throw new TranslatorException(e); } catch (AmazonClientException e) { throw new TranslatorException(e); } } /** * Inserts item into given domain. * @param domainName * @param itemName * @param columnsMap * @return */ @Override public int performInsert(String domainName, List<Column> columns, Iterator<? extends List<?>> valueList) throws TranslatorException { try { if (this.domains == null) { this.domains = getDomains(); } if (!this.domains.contains(domainName)) { createDomain(domainName); } int count = 0; List<ReplaceableItem> insertItems = new ArrayList<ReplaceableItem>(); while(valueList.hasNext()) { List<?> values = valueList.next(); List<ReplaceableAttribute> attributes = new ArrayList<ReplaceableAttribute>(); String itemName = null; for (int i = 0; i < columns.size(); i++) { Column column = columns.get(i); if (SQLStringVisitor.getRecordName(column).equals(SimpleDBConnection.ITEM_NAME)) { itemName = (String)values.get(i); } else { addAttribute(SQLStringVisitor.getRecordName(column), SimpleDBDataTypeManager.convertToSimpleDBType(values.get(i), column.getJavaType()), attributes); } } if (itemName == null) { throw new TranslatorException("ItemName() column value is not specified, it can not be null. Please provide a value."); } insertItems.add(new ReplaceableItem(itemName, attributes)); count++; if (count%25 == 0) { executeBatch(domainName, insertItems); insertItems.clear(); } } // http://docs.aws.amazon.com/AmazonSimpleDB/latest/DeveloperGuide/SDB_API_BatchPutAttributes.html // TODO: 25 limit we may need to batch; but if batch atomicity is gone executeBatch(domainName, insertItems); return count; } catch (AmazonServiceException e) { throw new TranslatorException(e); } catch (AmazonClientException e) { throw new TranslatorException(e); } } private void executeBatch(String domainName, List<ReplaceableItem> insertItems) { if (!insertItems.isEmpty()) { BatchPutAttributesRequest request = new BatchPutAttributesRequest(domainName, insertItems); this.client.batchPutAttributes(request); } } void addAttribute(String name, Object value, List<ReplaceableAttribute> attributes) { if (value != null && value.getClass().isArray()) { String[] values = (String[])value; for (int i = 0; i < values.length; i++) { addAttribute(name, values[i], attributes); } } else { ReplaceableAttribute attribute = new ReplaceableAttribute(); attribute.setName(name); attribute.setReplace(true); attribute.setValue((String)value); attributes.add(attribute); } } private Set<SimpleDBAttribute> getAttributeNamesFromSelectResult(SelectResult selectResult, int attributesCount) { Set<SimpleDBAttribute> attributes = new LinkedHashSet<SimpleDBAttribute>(); Iterator<Item> itemsIterator = selectResult.getItems().iterator(); while (attributes.size() < attributesCount) { Item item = itemsIterator.next(); Map<String, List<String>> valueMap = createAttributeMap(item.getAttributes()); for (String attributeName : valueMap.keySet()) { List<String> values = valueMap.get(attributeName); attributes.add(new SimpleDBAttribute(attributeName, values.size() > 1)); } } return attributes; } private Map<String, List<String>> createAttributeMap(List<Attribute> attributes) { Map<String, List<String>> map = new LinkedHashMap<String, List<String>>(); for (Attribute attribute : attributes) { if (map.get(attribute.getName()) == null) { List<String> list = new ArrayList<String>(); list.add(attribute.getValue()); map.put(attribute.getName(), list); } else { map.get(attribute.getName()).add(attribute.getValue()); } } return map; } }