blob: cf03b33da527819e50a319ce3ff13efeb0f2b751 [file] [log] [blame]
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 org.apache.gora.dynamodb.compiler;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.util.List;
import java.util.Map;
import org.apache.gora.dynamodb.store.DynamoDBMapping;
import org.apache.gora.dynamodb.store.DynamoDBMapping.DynamoDBMappingBuilder;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.input.SAXBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.amazonaws.services.dynamodb.model.KeySchema;
import com.amazonaws.services.dynamodb.model.KeySchemaElement;
/** Generate specific Java classes for defined schemas. */
public class GoraDynamoDBCompiler {
private File dest;
private Writer out;
private static final Logger log = LoggerFactory.getLogger(GoraDynamoDBCompiler.class);
private GoraDynamoDBCompiler(File dest) {
this.dest = dest; // root directory for output
}
/** Generates Java classes for a schema. */
public static void compileSchema(File src, File dest) throws IOException {
log.info("Compiling " + src + " to " + dest );
GoraDynamoDBCompiler compiler = new GoraDynamoDBCompiler(dest);
DynamoDBMapping dynamoDBMap = compiler.readMapping(src);
if (dynamoDBMap.getTables().isEmpty()) throw new IllegalStateException("There are not tables defined.");
for(String tableName : dynamoDBMap.getTables().keySet()){
compiler.compile(tableName, dynamoDBMap.getKeySchema(tableName), dynamoDBMap.getItems(tableName));
}
}
/**
* Method in charge of compiling a specific table using a key schema and a set of attributes
* @param pTableNameTable name
* @param pKeySchemaKey schema used
* @param pItemsList of items belonging to a specific table
*/
private void compile(String pTableName, KeySchema pKeySchema, List<Map<String, String>> pItems){
// TODO define where the generated will go
try {
startFile(pTableName, pTableName);
setHeaders(null);
line(0, "");
line(0, "@DynamoDBTable(tableName = \"" + pTableName + "\")");
line(0, "public class " + pTableName + " implements Persistent {");
setKeyAttributes(pKeySchema, 2);
setKeyMethods(pKeySchema, 2);
setItems(pItems, 2);
setDefaultMethods(2);
line(0, "}");
out.flush();
out.close();
} catch (IOException e) {
log.error("Error while compiling table " + pTableName);
e.printStackTrace();
}
}
/**
* Receives a list of all items and creates getters and setters for them
* @param pItemsThe items belonging to the table
* @param pIdenThe number of spaces used for identation
* @throws IOException
*/
private void setItems(List<Map<String, String>> pItems, int pIden) throws IOException{
for(Map<String, String> item : pItems){
for (String itemName : item.keySet()){
String itemType = "String";
if (item.get(itemName).toString().equals("N"))
itemType = "double";
if (item.get(itemName).toString().equals("SS"))
itemType = "Set<String>";
if (item.get(itemName).toString().equals("SN"))
itemType = "Set<double>";
line(pIden, "private " + itemType + " " + itemName + ";");
setItemMethods(itemName, itemType, pIden);
}
}
line(0, "");
}
/**
* Creates item getters and setters
* @param pItemNameItem's name
* @param pItemTypeItem's type
* @param pIdenNumber of spaces used for indentation
* @throws IOException
*/
private void setItemMethods(String pItemName, String pItemType, int pIden) throws IOException{
line(pIden, "@DynamoDBAttribute(attributeName = \"" + camelCasify(pItemName) + "\")");
line(pIden, "public " + pItemType + " get" + camelCasify(pItemName) + "() { return " + pItemName + "; }");
line(pIden, "public void set" + camelCasify(pItemName) + "(" + pItemType + " p" + camelCasify(pItemName) + ") { this." + pItemName + " = p"+ camelCasify(pItemName) +"; }");
line(0, "");
}
/**
* Creates key getters and setters
* @param pKeySchemaThe key schema for a specific table
* @param pIdenNumber of spaces used for indentation
* @throws IOException
*/
private void setKeyMethods(KeySchema pKeySchema, int pIden) throws IOException{
KeySchemaElement hashKey = pKeySchema.getHashKeyElement();
KeySchemaElement rangeKey = pKeySchema.getRangeKeyElement();
StringBuilder strBuilder = new StringBuilder();
// hash key
if(hashKey != null){
strBuilder.append("@DynamoDBHashKey(attributeName=\"" + hashKey.getAttributeName() + "\") \n");
strBuilder.append(" public String getHashKey() { return " + hashKey.getAttributeName() + "; } \n");
strBuilder.append(" public void setHashKey(" + (hashKey.getAttributeType().equals("S")?"String ":"double "));
strBuilder.append("p" + camelCasify(hashKey.getAttributeName()) + "){ this." + hashKey.getAttributeName());
strBuilder.append(" = p" + camelCasify(hashKey.getAttributeName()) + "; }");
line(pIden, strBuilder.toString());
}
strBuilder.delete(0, strBuilder.length());
// range key
if(rangeKey != null){
strBuilder.append("@DynamoDBRangeKey(attributeName=\"" + rangeKey.getAttributeName() + "\") \n");
strBuilder.append(" public String getRangeKey() { return " + rangeKey.getAttributeName() + "; } \n");
strBuilder.append(" public void setRangeKey(" + (rangeKey.getAttributeType().equals("S")?"String ":"double "));
strBuilder.append("p" + camelCasify(rangeKey.getAttributeName()) + "){ this." + rangeKey.getAttributeName());
strBuilder.append(" = p" + camelCasify(rangeKey.getAttributeName()) + "; }");
line(pIden, strBuilder.toString());
}
line(0, "");
}
/**
* Creates the key attributes within the generated class
* @param pKeySchemaKey schema
* @param pIdenNumber of spaces used for indentation
* @throws IOException
*/
private void setKeyAttributes(KeySchema pKeySchema, int pIden) throws IOException{
KeySchemaElement hashKey = pKeySchema.getHashKeyElement();
KeySchemaElement rangeKey = pKeySchema.getRangeKeyElement();
StringBuilder strBuilder = new StringBuilder();
// hash key
if(hashKey != null){
strBuilder.append("private " + (hashKey.getAttributeType().equals("S")?"String ":"double "));
strBuilder.append(hashKey.getAttributeName() + ";");
line(pIden, strBuilder.toString());
}
strBuilder.delete(0, strBuilder.length());
// range key
if(rangeKey != null){
strBuilder.append("private " + (rangeKey.getAttributeType().equals("S")?"String ":"double "));
strBuilder.append(rangeKey.getAttributeName() + ";");
line(pIden, strBuilder.toString());
}
line(0, "");
}
/**
* Returns camel case version of a string
* @param sString to be camelcasified
* @return
*/
private static String camelCasify(String s) {
return s.substring(0, 1).toUpperCase() + s.substring(1);
}
/** Recognizes camel case */
private static String toUpperCase(String s) {
StringBuilder builder = new StringBuilder();
for(int i=0; i<s.length(); i++) {
if(i > 0) {
if(Character.isUpperCase(s.charAt(i))
&& Character.isLowerCase(s.charAt(i-1))
&& Character.isLetter(s.charAt(i))) {
builder.append("_");
}
}
builder.append(Character.toUpperCase(s.charAt(i)));
}
return builder.toString();
}
/**
* Starts the java generated class file
* @param nameClass name
* @param space
* @throws IOException
*/
private void startFile(String name, String space) throws IOException {
File dir = new File(dest, space.replace('.', File.separatorChar));
if (!dir.exists())
if (!dir.mkdirs())
throw new IOException("Unable to create " + dir);
name = cap(name) + ".java";
out = new OutputStreamWriter(new FileOutputStream(new File(dir, name)));
}
/**
* Sets the necessary imports for the generated java class to work
* @param namespace
* @throws IOException
*/
private void setHeaders(String namespace) throws IOException {
if(namespace != null) {
line(0, "package "+namespace+";\n");
}
line(0, "import java.util.Set;");
line(0, "import org.apache.gora.persistency.Persistent;");
line(0, "import org.apache.gora.persistency.StateManager;");
line(0, "import com.amazonaws.services.dynamodb.datamodeling.DynamoDBAttribute;");
line(0, "import com.amazonaws.services.dynamodb.datamodeling.DynamoDBHashKey;");
line(0, "import com.amazonaws.services.dynamodb.datamodeling.DynamoDBRangeKey;");
line(0, "import com.amazonaws.services.dynamodb.datamodeling.DynamoDBTable;");
}
/**
* Creates default methods inherited from upper classes
* @param pIdenNumber of spaces used for indentation
* @throws IOException
*/
private void setDefaultMethods(int pIden) throws IOException {
line(pIden, "public void setNew(boolean pNew){}");
line(pIden, "public void setDirty(boolean pDirty){}");
line(pIden, "@Override");
line(pIden, "public StateManager getStateManager() { return null; }");
line(pIden, "@Override");
line(pIden, "public Persistent newInstance(StateManager stateManager) { return null; }");
line(pIden, "@Override");
line(pIden, "public String[] getFields() { return null; }");
line(pIden, "@Override");
line(pIden, "public String getField(int index) {return null; }");
line(pIden, "@Override");
line(pIden, "public int getFieldIndex(String field) { return 0; }");
line(pIden, "@Override");
line(pIden, "public void clear() { }");
line(pIden, "@Override");
line(pIden, "public person clone() {return null; }");
line(pIden, "@Override");
line(pIden, "public boolean isNew() { return false; }");
line(pIden, "@Override");
line(pIden, "public void setNew() { }");
line(pIden, "@Override");
line(pIden, "public void clearNew() {}");
line(pIden, "@Override");
line(pIden, "public boolean isDirty() { return false; }");
line(pIden, "@Override");
line(pIden, "public boolean isDirty(int fieldIndex) { return false; }");
line(pIden, "@Override");
line(pIden, "public boolean isDirty(String field) { return false; }");
line(pIden, "@Override");
line(pIden, "public void setDirty() { }");
line(pIden, "@Override");
line(pIden, "public void setDirty(int fieldIndex) { }");
line(pIden, "@Override");
line(pIden, "public void setDirty(String field) { }");
line(pIden, "@Override");
line(pIden, "public void clearDirty(int fieldIndex) { }");
line(pIden, "@Override");
line(pIden, "public void clearDirty(String field) { }");
line(pIden, "@Override");
line(pIden, "public void clearDirty() { }");
line(pIden, "@Override");
line(pIden, "public boolean isReadable(int fieldIndex) {return false; }");
line(pIden, "@Override");
line(pIden, "public boolean isReadable(String field) { return false; }");
line(pIden, "@Override");
line(pIden, "public void setReadable(int fieldIndex) { }");
line(pIden, "@Override");
line(pIden, "public void setReadable(String field) { }");
line(pIden, "@Override");
line(pIden, "public void clearReadable(int fieldIndex) { }");
line(pIden, "@Override");
line(pIden, "public void clearReadable(String field) { }");
line(pIden, "@Override");
line(pIden, "public void clearReadable() { }");
}
/**
* Writes a line within the output stream
* @param indentNumber of spaces used for indentation
* @param textText to be written
* @throws IOException
*/
private void line(int indent, String text) throws IOException {
for (int i = 0; i < indent; i ++) {
out.append(" ");
}
out.append(text);
out.append("\n");
}
/**
* Returns the string received with the first letter in uppercase
* @param nameString to be converted
* @return
*/
static String cap(String name) {
return name.substring(0,1).toUpperCase()+name.substring(1,name.length());
}
/**
* Start point of the compiler program
* @param argsReceives the schema file to be compiled and where this should be written
* @throws Exception
*/
public static void main(String[] args) throws Exception {
if (args.length < 2) {
System.err.println("Usage: Compiler <schema file> <output dir>");
System.exit(1);
}
compileSchema(new File(args[0]), new File(args[1]));
}
/**
* Reads the schema file and converts it into a data structure to be used
* @param pMapFileThe schema file to be mapped into a table
* @return
* @throws IOException
*/
@SuppressWarnings("unchecked")
private DynamoDBMapping readMapping(File pMapFile) throws IOException {
DynamoDBMappingBuilder mappingBuilder = new DynamoDBMappingBuilder();
try {
SAXBuilder builder = new SAXBuilder();
Document doc = builder.build(pMapFile);
Element root = doc.getRootElement();
List<Element> tableElements = root.getChildren("table");
for(Element tableElement : tableElements) {
String tableName = tableElement.getAttributeValue("name");
long readCapacUnits = Long.parseLong(tableElement.getAttributeValue("readcunit"));
long writeCapacUnits = Long.parseLong(tableElement.getAttributeValue("readcunit"));
mappingBuilder.setTableName(tableName);
mappingBuilder.setProvisionedThroughput(tableName, readCapacUnits, writeCapacUnits);
log.debug("Basic table properties have been set: Name, and Provisioned throughput.");
// Retrieving key's features
List<Element> fieldElements = tableElement.getChildren("key");
for(Element fieldElement : fieldElements) {
String keyName = fieldElement.getAttributeValue("name");
String keyType = fieldElement.getAttributeValue("type");
String keyAttrType = fieldElement.getAttributeValue("att-type");
if(keyType.equals("hash"))
mappingBuilder.setHashKeySchema(tableName, keyName, keyAttrType);
else if(keyType.equals("hashrange"))
mappingBuilder.setHashRangeKeySchema(tableName, keyName, keyAttrType);
}
log.debug("Table key schemas have been set.");
// Retrieving attributes
fieldElements = tableElement.getChildren("attribute");
for(Element fieldElement : fieldElements) {
String attributeName = fieldElement.getAttributeValue("name");
String attributeType = fieldElement.getAttributeValue("type");
mappingBuilder.addAttribute(tableName, attributeName, attributeType, 0);
}
log.info("Table attributes have been read.");
}
} catch(IOException ex) {
log.info("Error while performing xml mapping.");
ex.printStackTrace();
throw ex;
} catch(Exception ex) {
ex.printStackTrace();
throw new IOException(ex);
}
return mappingBuilder.build();
}
}