You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cloudstack.apache.org by ah...@apache.org on 2013/07/27 00:01:56 UTC
[07/10] Moved the DB layer code into framework-db and change only the
necessary projects to refer to it. Cut down on the dependencies introduced
with all the code in utils.
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/f5e5b39c/framework/db/src/com/cloud/utils/db/SearchCriteriaService.java
----------------------------------------------------------------------
diff --git a/framework/db/src/com/cloud/utils/db/SearchCriteriaService.java b/framework/db/src/com/cloud/utils/db/SearchCriteriaService.java
new file mode 100755
index 0000000..2947255
--- /dev/null
+++ b/framework/db/src/com/cloud/utils/db/SearchCriteriaService.java
@@ -0,0 +1,29 @@
+// 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
+// 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.cloud.utils.db;
+
+import java.util.List;
+
+import com.cloud.utils.db.SearchCriteria.Op;
+
+public interface SearchCriteriaService<T, K> {
+ public void selectField(Object... useless);
+ public void addAnd(Object useless, Op op, Object...values);
+ public List<K> list();
+ public T getEntity();
+ public <K> K find();
+}
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/f5e5b39c/framework/db/src/com/cloud/utils/db/SequenceFetcher.java
----------------------------------------------------------------------
diff --git a/framework/db/src/com/cloud/utils/db/SequenceFetcher.java b/framework/db/src/com/cloud/utils/db/SequenceFetcher.java
new file mode 100644
index 0000000..8823552
--- /dev/null
+++ b/framework/db/src/com/cloud/utils/db/SequenceFetcher.java
@@ -0,0 +1,171 @@
+// 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
+// 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.cloud.utils.db;
+
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.Random;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Future;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+
+import javax.persistence.TableGenerator;
+
+import org.apache.log4j.Logger;
+
+import com.cloud.utils.concurrency.NamedThreadFactory;
+
+/**
+ * Since Mysql does not have sequence support, we have
+ * table retrieval was inside a transaction, the value
+ * gets locked until the transaction is over.
+ *
+ * allocation size.
+ *
+ */
+public class SequenceFetcher {
+ private final static Logger s_logger = Logger.getLogger(SequenceFetcher.class);
+ ExecutorService _executors;
+ private final static Random random = new Random();
+
+ public <T> T getNextSequence(Class<T> clazz, TableGenerator tg) {
+ return getNextSequence(clazz, tg, null, false);
+ }
+
+ public <T> T getNextSequence(Class<T> clazz, TableGenerator tg, Object key) {
+ return getNextSequence(clazz, tg, key, false);
+ }
+
+ public <T> T getRandomNextSequence(Class<T> clazz, TableGenerator tg) {
+ return getNextSequence(clazz, tg, null, true);
+ }
+
+ public <T> T getNextSequence(Class<T> clazz, TableGenerator tg, Object key, boolean isRandom) {
+ Future<T> future = _executors.submit(new Fetcher<T>(clazz, tg, key, isRandom));
+ try {
+ return future.get();
+ } catch (Exception e) {
+ s_logger.warn("Unable to get sequeunce for " + tg.table() + ":" + tg.pkColumnValue(), e);
+ return null;
+ }
+ }
+
+ protected SequenceFetcher() {
+ _executors = new ThreadPoolExecutor(100, 100, 120l, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(250), new NamedThreadFactory("SequenceFetcher"));
+ }
+
+ protected static final SequenceFetcher s_instance = new SequenceFetcher();
+ public static SequenceFetcher getInstance() {
+ return s_instance;
+ }
+
+ protected class Fetcher<T> implements Callable<T> {
+ TableGenerator _tg;
+ Class<T> _clazz;
+ Object _key;
+ boolean isRandom = false;
+
+ protected Fetcher(Class<T> clazz, TableGenerator tg, Object key, boolean isRandom) {
+ _tg = tg;
+ _clazz = clazz;
+ _key = key;
+ this.isRandom = isRandom;
+ }
+
+ @Override @SuppressWarnings("unchecked")
+ public T call() throws Exception {
+ try {
+ PreparedStatement stmt = null;
+ StringBuilder sql = new StringBuilder("SELECT ");
+ sql.append(_tg.valueColumnName()).append(" FROM ").append(_tg.table());
+ sql.append(" WHERE ").append(_tg.pkColumnName()).append(" = ? FOR UPDATE");
+
+ Transaction txn = Transaction.open("Sequence");
+
+ PreparedStatement selectStmt = txn.prepareStatement(sql.toString());
+ if (_key == null) {
+ selectStmt.setString(1, _tg.pkColumnValue());
+ } else {
+ selectStmt.setObject(1, _key);
+ }
+
+ sql = new StringBuilder("UPDATE ");
+ sql.append(_tg.table()).append(" SET ").append(_tg.valueColumnName()).append("=").append("?+?");
+ sql.append(" WHERE ").append(_tg.pkColumnName()).append("=?");
+
+ PreparedStatement updateStmt = txn.prepareStatement(sql.toString());
+ if(isRandom){
+ updateStmt.setInt(2, random.nextInt(10) + 1);
+ } else {
+ updateStmt.setInt(2, _tg.allocationSize());
+ }
+ if (_key == null) {
+ updateStmt.setString(3, _tg.pkColumnValue());
+ } else {
+ updateStmt.setObject(3, _key);
+ }
+
+ ResultSet rs = null;
+ try {
+ txn.start();
+
+ stmt = selectStmt;
+ rs = stmt.executeQuery();
+ Object obj = null;
+ while (rs.next()) {
+ if (_clazz.isAssignableFrom(Long.class)) {
+ obj = rs.getLong(1);
+ } else if (_clazz.isAssignableFrom(Integer.class)) {
+ obj = rs.getInt(1);
+ } else {
+ obj = rs.getObject(1);
+ }
+ }
+
+ if (obj == null) {
+ s_logger.warn("Unable to get a sequence: " + updateStmt.toString());
+ return null;
+ }
+
+ updateStmt.setObject(1, obj);
+ stmt = updateStmt;
+ int rows = stmt.executeUpdate();
+ assert rows == 1 : "Come on....how exactly did we update this many rows " + rows + " for " + updateStmt.toString();
+ txn.commit();
+ return (T)obj;
+ } catch (SQLException e) {
+ s_logger.warn("Caught this exception when running: " + (stmt != null ? stmt.toString() : ""), e);
+ } finally {
+ if (rs != null) {
+ rs.close();
+ }
+ selectStmt.close();
+ updateStmt.close();
+ txn.close();
+ }
+ } catch (Exception e) {
+ s_logger.warn("Caught this exception when running.", e);
+ }
+ return null;
+ }
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/f5e5b39c/framework/db/src/com/cloud/utils/db/SqlGenerator.java
----------------------------------------------------------------------
diff --git a/framework/db/src/com/cloud/utils/db/SqlGenerator.java b/framework/db/src/com/cloud/utils/db/SqlGenerator.java
new file mode 100755
index 0000000..e48fee5
--- /dev/null
+++ b/framework/db/src/com/cloud/utils/db/SqlGenerator.java
@@ -0,0 +1,669 @@
+// 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
+// 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.cloud.utils.db;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.persistence.AttributeOverride;
+import javax.persistence.CollectionTable;
+import javax.persistence.DiscriminatorColumn;
+import javax.persistence.DiscriminatorType;
+import javax.persistence.DiscriminatorValue;
+import javax.persistence.ElementCollection;
+import javax.persistence.Embeddable;
+import javax.persistence.Embedded;
+import javax.persistence.EmbeddedId;
+import javax.persistence.Entity;
+import javax.persistence.FetchType;
+import javax.persistence.PrimaryKeyJoinColumn;
+import javax.persistence.SecondaryTable;
+import javax.persistence.TableGenerator;
+
+import com.cloud.utils.Pair;
+import com.cloud.utils.Ternary;
+import com.cloud.utils.db.Attribute.Flag;
+
+public class SqlGenerator {
+ Class<?> _clazz;
+ ArrayList<Attribute> _attributes;
+ ArrayList<Field> _embeddeds;
+ ArrayList<Class<?>> _tables;
+ LinkedHashMap<String, List<Attribute>> _ids;
+ HashMap<String, TableGenerator> _generators;
+ ArrayList<Attribute> _ecAttrs;
+
+ public SqlGenerator(Class<?> clazz) {
+ _clazz = clazz;
+ _tables = new ArrayList<Class<?>>();
+ _attributes = new ArrayList<Attribute>();
+ _ecAttrs = new ArrayList<Attribute>();
+ _embeddeds = new ArrayList<Field>();
+ _ids = new LinkedHashMap<String, List<Attribute>>();
+ _generators = new HashMap<String, TableGenerator>();
+
+ buildAttributes(clazz, DbUtil.getTableName(clazz), DbUtil.getAttributeOverrides(clazz), false, false);
+ assert (_tables.size() > 0) : "Did you forget to put @Entity on " + clazz.getName();
+ handleDaoAttributes(clazz);
+ findEcAttributes();
+ }
+
+ protected boolean checkMethods(Class<?> clazz, Map<String, Attribute> attrs) {
+ Method[] methods = clazz.getMethods();
+ for (Method method : methods) {
+ String name = method.getName();
+ if (name.startsWith("get")) {
+ String fieldName = Character.toLowerCase(name.charAt(3)) + name.substring(4);
+ assert !attrs.containsKey(fieldName) : "Mismatch in " + clazz.getSimpleName() + " for " + name;
+ } else if (name.startsWith("is")) {
+ String fieldName = Character.toLowerCase(name.charAt(2)) + name.substring(3);
+ assert !attrs.containsKey(fieldName) : "Mismatch in " + clazz.getSimpleName() + " for " + name;
+ } else if (name.startsWith("set")) {
+ String fieldName = Character.toLowerCase(name.charAt(3)) + name.substring(4);
+ assert !attrs.containsKey(fieldName) : "Mismatch in " + clazz.getSimpleName() + " for " + name;
+ }
+ }
+ return true;
+
+ }
+
+ protected void buildAttributes(Class<?> clazz, String tableName, AttributeOverride[] overrides, boolean embedded, boolean isId) {
+ if (!embedded && clazz.getAnnotation(Entity.class) == null) {
+ return;
+ }
+
+ Class<?> parent = clazz.getSuperclass();
+ if (parent != null) {
+ buildAttributes(parent, DbUtil.getTableName(parent), DbUtil.getAttributeOverrides(parent), false, false);
+ }
+
+ if (!embedded) {
+ _tables.add(clazz);
+ _ids.put(tableName, new ArrayList<Attribute>());
+ }
+
+ Field[] fields = clazz.getDeclaredFields();
+ for (Field field : fields) {
+ field.setAccessible(true);
+
+ TableGenerator tg = field.getAnnotation(TableGenerator.class);
+ if (tg != null) {
+ _generators.put(field.getName(), tg);
+ }
+
+ if (!DbUtil.isPersistable(field)) {
+ continue;
+ }
+
+ if (field.getAnnotation(Embedded.class) != null) {
+ _embeddeds.add(field);
+ Class<?> embeddedClass = field.getType();
+ assert (embeddedClass.getAnnotation(Embeddable.class) != null) : "Class is not annotated with Embeddable: " + embeddedClass.getName();
+ buildAttributes(embeddedClass, tableName, DbUtil.getAttributeOverrides(field), true, false);
+ continue;
+ }
+
+ if (field.getAnnotation(EmbeddedId.class) != null) {
+ _embeddeds.add(field);
+ Class<?> embeddedClass = field.getType();
+ assert (embeddedClass.getAnnotation(Embeddable.class) != null) : "Class is not annotated with Embeddable: " + embeddedClass.getName();
+ buildAttributes(embeddedClass, tableName, DbUtil.getAttributeOverrides(field), true, true);
+ continue;
+ }
+
+ Attribute attr = new Attribute(clazz, overrides, field, tableName, embedded, isId);
+
+ if (attr.getColumnName().equals(GenericDao.REMOVED_COLUMN)) {
+ attr.setColumnName(GenericDao.REMOVED);
+ attr.setTrue(Attribute.Flag.DaoGenerated);
+ attr.setFalse(Attribute.Flag.Insertable);
+ attr.setFalse(Attribute.Flag.Updatable);
+ attr.setTrue(Attribute.Flag.TimeStamp);
+ attr.setFalse(Attribute.Flag.Time);
+ attr.setFalse(Attribute.Flag.Date);
+ attr.setTrue(Attribute.Flag.Nullable);
+ attr.setTrue(Attribute.Flag.Removed);
+ }
+
+ if (attr.isId()) {
+ List<Attribute> attrs = _ids.get(tableName);
+ attrs.add(attr);
+ }
+
+ _attributes.add(attr);
+ }
+ }
+
+ protected void findEcAttributes() {
+ for (Attribute attr : _attributes) {
+ if (attr.field == null) {
+ continue;
+ }
+ ElementCollection ec = attr.field.getAnnotation(ElementCollection.class);
+ if (ec != null) {
+ Attribute idAttr = _ids.get(attr.table).get(0);
+ assert supportsElementCollection(attr.field) : "Doesn't support ElementCollection for " + attr.field.getName();
+ attr.attache = new EcInfo(attr, idAttr);
+ _ecAttrs.add(attr);
+ }
+ }
+ }
+
+ protected boolean supportsElementCollection(Field field) {
+ ElementCollection otm = field.getAnnotation(ElementCollection.class);
+ if (otm.fetch() == FetchType.LAZY) {
+ assert (false) : "Doesn't support laz fetch: " + field.getName();
+ return false;
+ }
+
+ CollectionTable ct = field.getAnnotation(CollectionTable.class);
+ if (ct == null) {
+ assert (false) : "No collection table sepcified for " + field.getName();
+ return false;
+ }
+
+ return true;
+ }
+
+ public Map<String, TableGenerator> getTableGenerators() {
+ return _generators;
+ }
+
+ protected void handleDaoAttributes(Class<?> clazz) {
+ Attribute attr;
+ Class<?> current = clazz;
+ while (current != null && current.getAnnotation(Entity.class) != null) {
+ DiscriminatorColumn column = current.getAnnotation(DiscriminatorColumn.class);
+ if (column != null) {
+ String columnName = column.name();
+ attr = findAttribute(columnName);
+ if (attr != null) {
+ attr.setTrue(Attribute.Flag.DaoGenerated);
+ attr.setTrue(Attribute.Flag.Insertable);
+ attr.setTrue(Attribute.Flag.Updatable);
+ attr.setFalse(Attribute.Flag.Nullable);
+ attr.setTrue(Attribute.Flag.DC);
+ } else {
+ attr = new Attribute(DbUtil.getTableName(current), column.name());
+ attr.setFalse(Flag.Selectable);
+ attr.setTrue(Flag.Insertable);
+ attr.setTrue(Flag.DaoGenerated);
+ attr.setTrue(Flag.DC);
+ _attributes.add(attr);
+ }
+ if (column.discriminatorType() == DiscriminatorType.CHAR) {
+ attr.setTrue(Attribute.Flag.CharDT);
+ } else if (column.discriminatorType() == DiscriminatorType.STRING) {
+ attr.setTrue(Attribute.Flag.StringDT);
+ } else if (column.discriminatorType() == DiscriminatorType.INTEGER) {
+ attr.setTrue(Attribute.Flag.IntegerDT);
+ }
+ }
+
+ PrimaryKeyJoinColumn[] pkjcs = DbUtil.getPrimaryKeyJoinColumns(current);
+ if (pkjcs != null) {
+ for (PrimaryKeyJoinColumn pkjc : pkjcs) {
+ String tableName = DbUtil.getTableName(current);
+ attr = findAttribute(pkjc.name());
+ if (attr == null || !tableName.equals(attr.table)) {
+ Attribute id = new Attribute(DbUtil.getTableName(current), pkjc.name());
+ if (pkjc.referencedColumnName().length() > 0) {
+ attr = findAttribute(pkjc.referencedColumnName());
+ assert (attr != null) : "Couldn't find referenced column name " + pkjc.referencedColumnName();
+ }
+ id.field = attr.field;
+ id.setTrue(Flag.Id);
+ id.setTrue(Flag.Insertable);
+ id.setFalse(Flag.Updatable);
+ id.setFalse(Flag.Nullable);
+ id.setFalse(Flag.Selectable);
+ _attributes.add(id);
+ List<Attribute> attrs = _ids.get(id.table);
+ attrs.add(id);
+ }
+ }
+ }
+ current = current.getSuperclass();
+ }
+
+ attr = findAttribute(GenericDao.CREATED_COLUMN);
+ if (attr != null && attr.field.getType() == Date.class) {
+ attr.setTrue(Attribute.Flag.DaoGenerated);
+ attr.setTrue(Attribute.Flag.Insertable);
+ attr.setFalse(Attribute.Flag.Updatable);
+ attr.setFalse(Attribute.Flag.Date);
+ attr.setFalse(Attribute.Flag.Time);
+ attr.setTrue(Attribute.Flag.TimeStamp);
+ attr.setFalse(Attribute.Flag.Nullable);
+ attr.setTrue(Attribute.Flag.Created);
+ }
+
+ attr = findAttribute(GenericDao.XID_COLUMN);
+ if (attr != null && attr.field.getType() == String.class) {
+ attr.setTrue(Attribute.Flag.DaoGenerated);
+ attr.setTrue(Attribute.Flag.Insertable);
+ attr.setFalse(Attribute.Flag.Updatable);
+ attr.setFalse(Attribute.Flag.TimeStamp);
+ attr.setFalse(Attribute.Flag.Time);
+ attr.setFalse(Attribute.Flag.Date);
+ attr.setFalse(Attribute.Flag.Nullable);
+ attr.setFalse(Attribute.Flag.Removed);
+ }
+ }
+
+ public List<Attribute> getElementCollectionAttributes() {
+ return _ecAttrs;
+ }
+
+ public Attribute findAttribute(String name) {
+ for (Attribute attr : _attributes) {
+
+ if (attr.columnName.equalsIgnoreCase(name)) {
+ if (attr.columnName.equalsIgnoreCase(GenericDao.REMOVED) && attr.isUpdatable()) {
+ return null;
+ }
+ return attr;
+ }
+ }
+
+ return null;
+ }
+
+ public static StringBuilder buildUpdateSql(String tableName, List<Attribute> attrs) {
+ StringBuilder sql = new StringBuilder("UPDATE ");
+ sql.append(tableName).append(" SET ");
+ for (Attribute attr : attrs) {
+ sql.append(attr.columnName).append(" = ?, ");
+ }
+ sql.delete(sql.length() - 2, sql.length());
+ sql.append(" WHERE ");
+
+ return sql;
+ }
+
+ public List<Pair<StringBuilder, Attribute[]>> buildUpdateSqls() {
+ ArrayList<Pair<StringBuilder, Attribute[]>> sqls = new ArrayList<Pair<StringBuilder, Attribute[]>>(_tables.size());
+ for (Class<?> table : _tables) {
+ ArrayList<Attribute> attrs = new ArrayList<Attribute>();
+ String tableName = DbUtil.getTableName(table);
+ for (Attribute attr : _attributes) {
+ if (attr.isUpdatable() && tableName.equals(attr.table)) {
+ attrs.add(attr);
+ }
+ }
+ if (attrs.size() != 0) {
+ Pair<StringBuilder, Attribute[]> pair =
+ new Pair<StringBuilder, Attribute[]>(buildUpdateSql(tableName, attrs), attrs.toArray(new Attribute[attrs.size()]));
+ sqls.add(pair);
+ }
+ }
+ return sqls;
+ }
+
+ public static StringBuilder buildMysqlUpdateSql(String joins, Collection<Ternary<Attribute, Boolean, Object>> setters) {
+ if (setters.size() == 0) {
+ return null;
+ }
+
+ StringBuilder sql = new StringBuilder("UPDATE ");
+
+ sql.append(joins);
+
+ sql.append(" SET ");
+
+ for (Ternary<Attribute, Boolean, Object> setter : setters) {
+ Attribute attr = setter.first();
+ sql.append(attr.table).append(".").append(attr.columnName).append("=");
+ if (setter.second() != null) {
+ sql.append(attr.table).append(".").append(attr.columnName).append(setter.second() ? "+" : "-");
+ }
+ sql.append("?, ");
+ }
+
+ sql.delete(sql.length() - 2, sql.length());
+
+ sql.append(" WHERE ");
+
+ return sql;
+ }
+
+ public List<Pair<String, Attribute[]>> buildInsertSqls() {
+ LinkedHashMap<String, ArrayList<Attribute>> map = new LinkedHashMap<String, ArrayList<Attribute>>();
+ for (Class<?> table : _tables) {
+ map.put(DbUtil.getTableName(table), new ArrayList<Attribute>());
+ }
+
+ for (Attribute attr : _attributes) {
+ if (attr.isInsertable()) {
+ ArrayList<Attribute> attrs = map.get(attr.table);
+ assert (attrs != null) : "Null set of attributes for " + attr.table;
+ attrs.add(attr);
+ }
+ }
+
+ List<Pair<String, Attribute[]>> sqls = new ArrayList<Pair<String, Attribute[]>>(map.size());
+ for (Map.Entry<String, ArrayList<Attribute>> entry : map.entrySet()) {
+ ArrayList<Attribute> attrs = entry.getValue();
+ StringBuilder sql = buildInsertSql(entry.getKey(), attrs);
+ Pair<String, Attribute[]> pair = new Pair<String, Attribute[]>(sql.toString(), attrs.toArray(new Attribute[attrs.size()]));
+ sqls.add(pair);
+ }
+
+ return sqls;
+ }
+
+ protected StringBuilder buildInsertSql(String table, ArrayList<Attribute> attrs) {
+ StringBuilder sql = new StringBuilder("INSERT INTO ");
+ sql.append(table).append(" (");
+ for (Attribute attr : attrs) {
+ sql.append(table).append(".").append(attr.columnName).append(", ");
+ }
+ if (attrs.size() > 0) {
+ sql.delete(sql.length() - 2, sql.length());
+ }
+
+ sql.append(") VALUES (");
+ for (Attribute attr : attrs) {
+ sql.append("?, ");
+ }
+
+ if (attrs.size() > 0) {
+ sql.delete(sql.length() - 2, sql.length());
+ }
+
+ sql.append(")");
+
+ return sql;
+ }
+
+ protected List<Pair<String, Attribute[]>> buildDeleteSqls() {
+ LinkedHashMap<String, ArrayList<Attribute>> map = new LinkedHashMap<String, ArrayList<Attribute>>();
+ for (Class<?> table : _tables) {
+ map.put(DbUtil.getTableName(table), new ArrayList<Attribute>());
+ }
+
+ for (Attribute attr : _attributes) {
+ if (attr.isId()) {
+ ArrayList<Attribute> attrs = map.get(attr.table);
+ assert (attrs != null) : "Null set of attributes for " + attr.table;
+ attrs.add(attr);
+ }
+ }
+
+ List<Pair<String, Attribute[]>> sqls = new ArrayList<Pair<String, Attribute[]>>(map.size());
+ for (Map.Entry<String, ArrayList<Attribute>> entry : map.entrySet()) {
+ ArrayList<Attribute> attrs = entry.getValue();
+ String sql = buildDeleteSql(entry.getKey(), attrs);
+ Pair<String, Attribute[]> pair = new Pair<String, Attribute[]>(sql, attrs.toArray(new Attribute[attrs.size()]));
+ sqls.add(pair);
+ }
+
+ Collections.reverse(sqls);
+ return sqls;
+ }
+
+ protected String buildDeleteSql(String table, ArrayList<Attribute> attrs) {
+ StringBuilder sql = new StringBuilder("DELETE FROM ");
+ sql.append(table).append(" WHERE ");
+ for (Attribute attr : attrs) {
+ sql.append(table).append(".").append(attr.columnName).append("= ? AND ");
+ }
+ sql.delete(sql.length() - 5, sql.length());
+ return sql.toString();
+ }
+
+ public Pair<String, Attribute[]> buildRemoveSql() {
+ Attribute attribute = findAttribute(GenericDao.REMOVED);
+ if (attribute == null) {
+ return null;
+ }
+
+ StringBuilder sql = new StringBuilder("UPDATE ");
+ sql.append(attribute.table).append(" SET ");
+ sql.append(attribute.columnName).append(" = ? WHERE ");
+
+ List<Attribute> ids = _ids.get(attribute.table);
+
+ // if ids == null, that means the removed column was added as a JOIN
+ // value to another table. We ignore it here.
+ if (ids == null) {
+ return null;
+ }
+ if (ids.size() == 0) {
+ return null;
+ }
+
+ for (Attribute id : ids) {
+ sql.append(id.table).append(".").append(id.columnName).append(" = ? AND ");
+ }
+
+ sql.delete(sql.length() - 5, sql.length());
+
+ Attribute[] attrs = ids.toArray(new Attribute[ids.size() + 1]);
+ attrs[attrs.length - 1] = attribute;
+
+ return new Pair<String, Attribute[]>(sql.toString(), attrs);
+ }
+
+ public Map<String, Attribute[]> getIdAttributes() {
+ LinkedHashMap<String, Attribute[]> ids = new LinkedHashMap<String, Attribute[]>(_ids.size());
+
+ for (Map.Entry<String, List<Attribute>> entry : _ids.entrySet()) {
+ ids.put(entry.getKey(), entry.getValue().toArray(new Attribute[entry.getValue().size()]));
+ }
+
+ return ids;
+ }
+
+ /**
+ * @return a map of tables and maps of field names to attributes.
+ */
+ public Map<String, Attribute> getAllAttributes() {
+ Map<String, Attribute> attrs = new LinkedHashMap<String, Attribute>(_attributes.size());
+ for (Attribute attr : _attributes) {
+ if (attr.field != null) {
+ attrs.put(attr.field.getName(), attr);
+ }
+ }
+
+ return attrs;
+ }
+
+ public Map<Pair<String, String>, Attribute> getAllColumns() {
+ Map<Pair<String, String>, Attribute> attrs = new LinkedHashMap<Pair<String, String>, Attribute>(_attributes.size());
+ for (Attribute attr : _attributes) {
+ if (attr.columnName != null) {
+ attrs.put(new Pair<String, String>(attr.table, attr.columnName), attr);
+ }
+ }
+
+ return attrs;
+ }
+
+ protected static void addPrimaryKeyJoinColumns(StringBuilder sql, String fromTable, String toTable, String joinType, PrimaryKeyJoinColumn[] pkjcs) {
+ if ("right".equalsIgnoreCase(joinType)) {
+ sql.append(" RIGHT JOIN ").append(toTable).append(" ON ");
+ } else if ("left".equalsIgnoreCase(joinType)) {
+ sql.append(" LEFT JOIN ").append(toTable).append(" ON ");
+ } else {
+ sql.append(" INNER JOIN ").append(toTable).append(" ON ");
+ }
+ for (PrimaryKeyJoinColumn pkjc : pkjcs) {
+ sql.append(fromTable).append(".").append(pkjc.name());
+ String refColumn = DbUtil.getReferenceColumn(pkjc);
+ sql.append("=").append(toTable).append(".").append(refColumn).append(" ");
+ }
+ }
+
+ public Pair<String, Attribute> getRemovedAttribute() {
+ Attribute removed = findAttribute(GenericDao.REMOVED);
+ if (removed == null) {
+ return null;
+ }
+
+ if (removed.field.getType() != Date.class) {
+ return null;
+ }
+
+ StringBuilder sql = new StringBuilder();
+ sql.append(removed.table).append(".").append(removed.columnName).append(" IS NULL ");
+
+ return new Pair<String, Attribute>(sql.toString(), removed);
+ }
+
+ protected static void buildJoins(StringBuilder innerJoin, Class<?> clazz) {
+ String tableName = DbUtil.getTableName(clazz);
+
+ SecondaryTable[] sts = DbUtil.getSecondaryTables(clazz);
+ ArrayList<String> secondaryTables = new ArrayList<String>();
+ for (SecondaryTable st : sts) {
+ JoinType jt = clazz.getAnnotation(JoinType.class);
+ String join = null;
+ if (jt != null) {
+ join = jt.type();
+ }
+ addPrimaryKeyJoinColumns(innerJoin, tableName, st.name(), join, st.pkJoinColumns());
+ secondaryTables.add(st.name());
+ }
+
+ Class<?> parent = clazz.getSuperclass();
+ if (parent.getAnnotation(Entity.class) != null) {
+ String table = DbUtil.getTableName(parent);
+ PrimaryKeyJoinColumn[] pkjcs = DbUtil.getPrimaryKeyJoinColumns(clazz);
+ assert (pkjcs != null) : "No Join columns specified for the super class";
+ addPrimaryKeyJoinColumns(innerJoin, tableName, table, null, pkjcs);
+ }
+ }
+
+
+ public String buildTableReferences() {
+ StringBuilder sql = new StringBuilder();
+ sql.append(DbUtil.getTableName(_tables.get(_tables.size() - 1)));
+
+ for (Class<?> table : _tables) {
+ buildJoins(sql, table);
+ }
+
+ return sql.toString();
+ }
+
+ public Pair<StringBuilder, Attribute[]> buildSelectSql(boolean enable_query_cache) {
+ StringBuilder sql = new StringBuilder("SELECT ");
+
+ sql.append(enable_query_cache ? "SQL_CACHE ": "");
+
+ ArrayList<Attribute> attrs = new ArrayList<Attribute>();
+
+ for (Attribute attr : _attributes) {
+ if (attr.isSelectable()) {
+ attrs.add(attr);
+ sql.append(attr.table).append(".").append(attr.columnName).append(", ");
+ }
+ }
+
+ if (attrs.size() > 0) {
+ sql.delete(sql.length() - 2, sql.length());
+ }
+
+ sql.append(" FROM ").append(buildTableReferences());
+
+ sql.append(" WHERE ");
+
+ sql.append(buildDiscriminatorClause().first());
+
+ return new Pair<StringBuilder, Attribute[]>(sql, attrs.toArray(new Attribute[attrs.size()]));
+ }
+
+ public Pair<StringBuilder, Attribute[]> buildSelectSql(Attribute[] attrs) {
+ StringBuilder sql = new StringBuilder("SELECT ");
+
+ for (Attribute attr : attrs) {
+ sql.append(attr.table).append(".").append(attr.columnName).append(", ");
+ }
+
+ if (attrs.length > 0) {
+ sql.delete(sql.length() - 2, sql.length());
+ }
+
+ sql.append(" FROM ").append(buildTableReferences());
+
+ sql.append(" WHERE ");
+
+ sql.append(buildDiscriminatorClause().first());
+
+ return new Pair<StringBuilder, Attribute[]>(sql, attrs);
+ }
+
+ /**
+ * buildDiscriminatorClause builds the join clause when there are multiple tables.
+ *
+ * @return
+ */
+ public Pair<StringBuilder, Map<String, Object>> buildDiscriminatorClause() {
+ StringBuilder sql = new StringBuilder();
+ Map<String, Object> values = new HashMap<String, Object>();
+
+ for (Class<?> table : _tables) {
+ DiscriminatorValue dv = table.getAnnotation(DiscriminatorValue.class);
+ if (dv != null) {
+ Class<?> parent = table.getSuperclass();
+ String tableName = DbUtil.getTableName(parent);
+ DiscriminatorColumn dc = parent.getAnnotation(DiscriminatorColumn.class);
+ assert(dc != null) : "Parent does not have discrminator column: " + parent.getName();
+ sql.append(tableName);
+ sql.append(".");
+ sql.append(dc.name()).append("=");
+ Object value = null;
+ if (dc.discriminatorType() == DiscriminatorType.INTEGER) {
+ sql.append(dv.value());
+ value = Integer.parseInt(dv.value());
+ } else if (dc.discriminatorType() == DiscriminatorType.CHAR) {
+ sql.append(dv.value());
+ value = dv.value().charAt(0);
+ } else if (dc.discriminatorType() == DiscriminatorType.STRING) {
+ String v = dv.value();
+ v = v.substring(0, v.length() < dc.length() ? v.length() : dc.length());
+ sql.append("'").append(v).append("'");
+ value = v;
+ }
+ values.put(dc.name(), value);
+ sql.append(" AND ");
+ }
+ }
+
+ return new Pair<StringBuilder, Map<String, Object>>(sql, values);
+ }
+
+ public Field[] getEmbeddedFields() {
+ return _embeddeds.toArray(new Field[_embeddeds.size()]);
+ }
+
+ public String buildCountSql() {
+ StringBuilder sql = new StringBuilder();
+
+ return sql.append("SELECT COUNT(*) FROM ").append(buildTableReferences()).
+ append(" WHERE ").append(buildDiscriminatorClause().first()).toString();
+ }
+}
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/f5e5b39c/framework/db/src/com/cloud/utils/db/StateMachine.java
----------------------------------------------------------------------
diff --git a/framework/db/src/com/cloud/utils/db/StateMachine.java b/framework/db/src/com/cloud/utils/db/StateMachine.java
new file mode 100644
index 0000000..a667d9b
--- /dev/null
+++ b/framework/db/src/com/cloud/utils/db/StateMachine.java
@@ -0,0 +1,30 @@
+// 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
+// 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.cloud.utils.db;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+@Target(FIELD)
+@Retention(RUNTIME)
+public @interface StateMachine {
+ public Class<?> state();
+ public Class<?> event();
+}
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/f5e5b39c/framework/db/src/com/cloud/utils/db/Transaction.java
----------------------------------------------------------------------
diff --git a/framework/db/src/com/cloud/utils/db/Transaction.java b/framework/db/src/com/cloud/utils/db/Transaction.java
new file mode 100755
index 0000000..37ea8cf
--- /dev/null
+++ b/framework/db/src/com/cloud/utils/db/Transaction.java
@@ -0,0 +1,1174 @@
+// 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
+// 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.cloud.utils.db;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Savepoint;
+import java.sql.Statement;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Properties;
+import java.util.concurrent.atomic.AtomicLong;
+
+import javax.sql.DataSource;
+
+import org.apache.commons.dbcp.ConnectionFactory;
+import org.apache.commons.dbcp.DriverManagerConnectionFactory;
+import org.apache.commons.dbcp.PoolableConnectionFactory;
+import org.apache.commons.dbcp.PoolingDataSource;
+import org.apache.commons.pool.KeyedObjectPoolFactory;
+import org.apache.commons.pool.impl.GenericObjectPool;
+import org.apache.commons.pool.impl.StackKeyedObjectPoolFactory;
+import org.apache.log4j.Logger;
+import org.jasypt.encryption.pbe.StandardPBEStringEncryptor;
+import org.jasypt.properties.EncryptableProperties;
+
+import com.cloud.utils.Pair;
+import com.cloud.utils.PropertiesUtil;
+import com.cloud.utils.crypt.EncryptionSecretKeyChecker;
+import com.cloud.utils.exception.CloudRuntimeException;
+import com.cloud.utils.mgmt.JmxUtil;
+
+/**
+ * Transaction abstracts away the Connection object in JDBC. It allows the
+ * following things that the Connection object does not.
+ *
+ * 1. Transaction can be started at an entry point and whether the DB
+ * actions should be auto-commit or not determined at that point.
+ * 2. DB Connection is allocated only when it is needed.
+ * 3. Code does not need to know if a transaction has been started or not.
+ * It just starts/ends a transaction and we resolve it correctly with
+ * the previous actions.
+ *
+ * Note that this class is not synchronous but it doesn't need to be because
+ * it is stored with TLS and is one per thread. Use appropriately.
+ */
+public class Transaction {
+ private static final Logger s_logger = Logger.getLogger(Transaction.class.getName() + "." + "Transaction");
+ private static final Logger s_stmtLogger = Logger.getLogger(Transaction.class.getName() + "." + "Statement");
+ private static final Logger s_lockLogger = Logger.getLogger(Transaction.class.getName() + "." + "Lock");
+ private static final Logger s_connLogger = Logger.getLogger(Transaction.class.getName() + "." + "Connection");
+
+ private static final ThreadLocal<Transaction> tls = new ThreadLocal<Transaction>();
+ private static final String START_TXN = "start_txn";
+ private static final String CURRENT_TXN = "current_txn";
+ private static final String CREATE_TXN = "create_txn";
+ private static final String CREATE_CONN = "create_conn";
+ private static final String STATEMENT = "statement";
+ private static final String ATTACHMENT = "attachment";
+
+ public static final short CLOUD_DB = 0;
+ public static final short USAGE_DB = 1;
+ public static final short AWSAPI_DB = 2;
+ public static final short SIMULATOR_DB = 3;
+ public static final short CONNECTED_DB = -1;
+ public static int s_region_id;
+
+ private static AtomicLong s_id = new AtomicLong();
+ private static final TransactionMBeanImpl s_mbean = new TransactionMBeanImpl();
+ static {
+ try {
+ JmxUtil.registerMBean("Transaction", "Transaction", s_mbean);
+ } catch (Exception e) {
+ s_logger.error("Unable to register mbean for transaction", e);
+ }
+
+ /* FIXME: We need a better solution for this
+ * Initialize encryption if we need it for db.properties
+ */
+ EncryptionSecretKeyChecker enc = new EncryptionSecretKeyChecker();
+ enc.check();
+ }
+
+ private final LinkedList<StackElement> _stack;
+ private long _id;
+
+ private final LinkedList<Pair<String, Long>> _lockTimes = new LinkedList<Pair<String, Long>>();
+
+ private String _name;
+ private Connection _conn;
+ private boolean _txn;
+ private short _dbId;
+ private long _txnTime;
+ private Statement _stmt;
+ private String _creator;
+
+ private Transaction _prev = null;
+
+ public static Transaction currentTxn() {
+ Transaction txn = tls.get();
+ assert txn != null : "No Transaction on stack. Did you mark the method with @DB?";
+
+ assert checkAnnotation(3, txn) : "Did you even read the guide to use Transaction...IOW...other people's code? Try method can't be private. What about @DB? hmmm... could that be it? " + txn;
+ return txn;
+ }
+
+ public static Transaction open(final short databaseId) {
+ String name = buildName();
+ if (name == null) {
+ name = CURRENT_TXN;
+ }
+ return open(name, databaseId, true);
+ }
+
+ //
+ // Usage of this transaction setup should be limited, it will always open a new transaction context regardless of whether or not there is other
+ // transaction context in the stack. It is used in special use cases that we want to control DB connection explicitly and in the mean time utilize
+ // the existing DAO features
+ //
+ public void transitToUserManagedConnection(Connection conn) {
+ assert(_conn == null /*&& _stack.size() <= 1*/) : "Can't change to a user managed connection unless the stack is empty and the db connection is null, you may have forgotten to invoke transitToAutoManagedConnection to close out the DB connection: " + toString();
+ _conn = conn;
+ _dbId = CONNECTED_DB;
+ }
+
+ public void transitToAutoManagedConnection(short dbId) {
+ // assert(_stack.size() <= 1) : "Can't change to auto managed connection unless your stack is empty";
+ _dbId = dbId;
+ _conn = null;
+ }
+
+ public static Transaction open(final String name) {
+ return open(name, CLOUD_DB, false);
+ }
+
+ public static Transaction open(final String name, final short databaseId, final boolean forceDbChange) {
+ Transaction txn = tls.get();
+ boolean isNew = false;
+ if (txn == null) {
+ if (s_logger.isTraceEnabled()) {
+ s_logger.trace("Creating the transaction: " + name);
+ }
+ txn = new Transaction(name, false, databaseId);
+ tls.set(txn);
+ isNew = true;
+ } else if (forceDbChange) {
+ final short currentDbId = txn.getDatabaseId();
+ if (currentDbId != databaseId) {
+ // we need to end the current transaction and switch databases
+ txn.close(txn.getName());
+
+ txn = new Transaction(name, false, databaseId);
+ tls.set(txn);
+ isNew = true;
+ }
+ }
+
+ txn.takeOver(name, false);
+ if (isNew) {
+ s_mbean.addTransaction(txn);
+ }
+ return txn;
+ }
+
+ protected StackElement peekInStack(Object obj) {
+ final Iterator<StackElement> it = _stack.iterator();
+ while (it.hasNext()) {
+ StackElement next = it.next();
+ if (next.type == obj) {
+ return next;
+ }
+ }
+ return null;
+ }
+
+ public void registerLock(String sql) {
+ if (_txn && s_lockLogger.isDebugEnabled()) {
+ Pair<String, Long> time = new Pair<String, Long>(sql, System.currentTimeMillis());
+ _lockTimes.add(time);
+ }
+ }
+
+ public boolean dbTxnStarted() {
+ return _txn;
+ }
+
+ public static Connection getStandaloneConnectionWithException() throws SQLException {
+ Connection conn = s_ds.getConnection();
+ if (s_connLogger.isTraceEnabled()) {
+ s_connLogger.trace("Retrieving a standalone connection: dbconn" + System.identityHashCode(conn));
+ }
+ return conn;
+ }
+
+ public static Connection getStandaloneConnection() {
+ try {
+ return getStandaloneConnectionWithException();
+ } catch (SQLException e) {
+ s_logger.error("Unexpected exception: ", e);
+ return null;
+ }
+ }
+
+ public static Connection getStandaloneUsageConnection() {
+ try {
+ Connection conn = s_usageDS.getConnection();
+ if (s_connLogger.isTraceEnabled()) {
+ s_connLogger.trace("Retrieving a standalone connection for usage: dbconn" + System.identityHashCode(conn));
+ }
+ return conn;
+ } catch (SQLException e) {
+ s_logger.warn("Unexpected exception: ", e);
+ return null;
+ }
+ }
+
+ public static Connection getStandaloneAwsapiConnection() {
+ try {
+ Connection conn = s_awsapiDS.getConnection();
+ if (s_connLogger.isTraceEnabled()) {
+ s_connLogger.trace("Retrieving a standalone connection for usage: dbconn" + System.identityHashCode(conn));
+ }
+ return conn;
+ } catch (SQLException e) {
+ s_logger.warn("Unexpected exception: ", e);
+ return null;
+ }
+ }
+
+ public static Connection getStandaloneSimulatorConnection() {
+ try {
+ Connection conn = s_simulatorDS.getConnection();
+ if (s_connLogger.isTraceEnabled()) {
+ s_connLogger.trace("Retrieving a standalone connection for simulator: dbconn" + System.identityHashCode(conn));
+ }
+ return conn;
+ } catch (SQLException e) {
+ s_logger.warn("Unexpected exception: ", e);
+ return null;
+ }
+ }
+
+ protected void attach(TransactionAttachment value) {
+ _stack.push(new StackElement(ATTACHMENT, value));
+ }
+
+ protected TransactionAttachment detach(String name) {
+ Iterator<StackElement> it = _stack.descendingIterator();
+ while (it.hasNext()) {
+ StackElement element = it.next();
+ if (element.type == ATTACHMENT) {
+ TransactionAttachment att = (TransactionAttachment)element.ref;
+ if (name.equals(att.getName())) {
+ it.remove();
+ return att;
+ }
+ }
+ }
+ assert false : "Are you sure you attached this: " + name;
+ return null;
+ }
+
+ public static void attachToTxn(TransactionAttachment value) {
+ Transaction txn = tls.get();
+ assert txn != null && txn.peekInStack(CURRENT_TXN) != null: "Come on....how can we attach something to the transaction if you haven't started it?";
+
+ txn.attach(value);
+ }
+
+ public static TransactionAttachment detachFromTxn(String name) {
+ Transaction txn = tls.get();
+ assert txn != null : "No Transaction in TLS";
+ return txn.detach(name);
+ }
+
+ protected static boolean checkAnnotation(int stack, Transaction txn) {
+ final StackTraceElement[] stacks = Thread.currentThread().getStackTrace();
+ StackElement se = txn.peekInStack(CURRENT_TXN);
+ if (se == null) {
+ return false;
+ }
+ for (; stack < stacks.length; stack++) {
+ String methodName = stacks[stack].getMethodName();
+ if (methodName.equals(se.ref)){
+ return true;
+ }
+ }
+ return false;
+ }
+
+ protected static String buildName() {
+ if (s_logger.isDebugEnabled()) {
+ final StackTraceElement[] stacks = Thread.currentThread().getStackTrace();
+ final StringBuilder str = new StringBuilder();
+ int i = 3, j = 3;
+ while (j < 15 && i < stacks.length) {
+ StackTraceElement element = stacks[i];
+ String filename = element.getFileName();
+ String method = element.getMethodName();
+ if ((filename != null && filename.equals("<generated>")) || (method != null && method.equals("invokeSuper"))) {
+ i++;
+ continue;
+ }
+
+ str.append("-").append(stacks[i].getClassName().substring(stacks[i].getClassName().lastIndexOf(".") + 1)).append(".").append(stacks[i].getMethodName()).append(":").append(stacks[i].getLineNumber());
+ j++;
+ i++;
+ }
+ return str.toString();
+ }
+
+ return "";
+ }
+
+ public Transaction(final String name, final boolean forLocking, final short databaseId) {
+ _name = name;
+ _conn = null;
+ _stack = new LinkedList<StackElement>();
+ _txn = false;
+ _dbId = databaseId;
+ _id = s_id.incrementAndGet();
+ _creator = Thread.currentThread().getName();
+ }
+
+ public String getCreator() {
+ return _creator;
+ }
+
+ public long getId() {
+ return _id;
+ }
+
+ public String getName() {
+ return _name;
+ }
+
+ public Short getDatabaseId() {
+ return _dbId;
+ }
+
+ @Override
+ public String toString() {
+ final StringBuilder str = new StringBuilder((_name != null ? _name : ""));
+ str.append(" : ");
+ for (final StackElement se : _stack) {
+ if (se.type == CURRENT_TXN) {
+ str.append(se.ref).append(", ");
+ }
+ }
+
+ return str.toString();
+ }
+
+ protected void mark(final String name) {
+ _stack.push(new StackElement(CURRENT_TXN, name));
+ }
+
+ public boolean lock(final String name, final int timeoutSeconds) {
+ Merovingian2 lockMaster = Merovingian2.getLockMaster();
+ if (lockMaster == null) {
+ throw new CloudRuntimeException("There's no support for locking yet");
+ }
+ return lockMaster.acquire(name, timeoutSeconds);
+ }
+
+ public boolean release(final String name) {
+ Merovingian2 lockMaster = Merovingian2.getLockMaster();
+ if (lockMaster == null) {
+ throw new CloudRuntimeException("There's no support for locking yet");
+ }
+ return lockMaster.release(name);
+ }
+
+ public void start() {
+ if (s_logger.isTraceEnabled()) {
+ s_logger.trace("txn: start requested by: " + buildName());
+ }
+
+ _stack.push(new StackElement(START_TXN, null));
+
+ if (_txn) {
+ s_logger.trace("txn: has already been started.");
+ return;
+ }
+
+ _txn = true;
+
+ _txnTime = System.currentTimeMillis();
+ if (_conn != null) {
+ try {
+ s_logger.trace("txn: set auto commit to false");
+ _conn.setAutoCommit(false);
+ } catch (final SQLException e) {
+ s_logger.warn("Unable to set auto commit: ", e);
+ throw new CloudRuntimeException("Unable to set auto commit: ", e);
+ }
+ }
+ }
+
+ protected void closePreviousStatement() {
+ if (_stmt != null) {
+ try {
+ if (s_stmtLogger.isTraceEnabled()) {
+ s_stmtLogger.trace("Closing: " + _stmt.toString());
+ }
+ try {
+ ResultSet rs = _stmt.getResultSet();
+ if (rs != null && _stmt.getResultSetHoldability() != ResultSet.HOLD_CURSORS_OVER_COMMIT) {
+ rs.close();
+ }
+ } catch(SQLException e) {
+ s_stmtLogger.trace("Unable to close resultset");
+ }
+ _stmt.close();
+ } catch (final SQLException e) {
+ s_stmtLogger.trace("Unable to close statement: " + _stmt.toString());
+ } finally {
+ _stmt = null;
+ }
+ }
+ }
+
+ /**
+ * Prepares an auto close statement. The statement is closed automatically if it is
+ * retrieved with this method.
+ *
+ * @param sql sql String
+ * @return PreparedStatement
+ * @throws SQLException if problem with JDBC layer.
+ *
+ * @see java.sql.Connection
+ */
+ public PreparedStatement prepareAutoCloseStatement(final String sql) throws SQLException {
+ PreparedStatement stmt = prepareStatement(sql);
+ closePreviousStatement();
+ _stmt = stmt;
+ return stmt;
+ }
+
+ public PreparedStatement prepareStatement(final String sql) throws SQLException {
+ final Connection conn = getConnection();
+ final PreparedStatement pstmt = conn.prepareStatement(sql);
+ if (s_stmtLogger.isTraceEnabled()) {
+ s_stmtLogger.trace("Preparing: " + sql);
+ }
+ return pstmt;
+ }
+
+ /**
+ * Prepares an auto close statement. The statement is closed automatically if it is
+ * retrieved with this method.
+ *
+ * @param sql sql String
+ * @param autoGeneratedKeys keys that are generated
+ * @return PreparedStatement
+ * @throws SQLException if problem with JDBC layer.
+ *
+ * @see java.sql.Connection
+ */
+ public PreparedStatement prepareAutoCloseStatement(final String sql, final int autoGeneratedKeys) throws SQLException {
+ final Connection conn = getConnection();
+ final PreparedStatement pstmt = conn.prepareStatement(sql, autoGeneratedKeys);
+ if (s_stmtLogger.isTraceEnabled()) {
+ s_stmtLogger.trace("Preparing: " + sql);
+ }
+ closePreviousStatement();
+ _stmt = pstmt;
+ return pstmt;
+ }
+
+ /**
+ * Prepares an auto close statement. The statement is closed automatically if it is
+ * retrieved with this method.
+ *
+ * @param sql sql String
+ * @param columnNames names of the columns
+ * @return PreparedStatement
+ * @throws SQLException if problem with JDBC layer.
+ *
+ * @see java.sql.Connection
+ */
+ public PreparedStatement prepareAutoCloseStatement(final String sql, final String[] columnNames) throws SQLException {
+ final Connection conn = getConnection();
+ final PreparedStatement pstmt = conn.prepareStatement(sql, columnNames);
+ if (s_stmtLogger.isTraceEnabled()) {
+ s_stmtLogger.trace("Preparing: " + sql);
+ }
+ closePreviousStatement();
+ _stmt = pstmt;
+ return pstmt;
+ }
+
+ /**
+ * Prepares an auto close statement. The statement is closed automatically if it is
+ * retrieved with this method.
+ *
+ * @param sql sql String
+ * @return PreparedStatement
+ * @throws SQLException if problem with JDBC layer.
+ *
+ * @see java.sql.Connection
+ */
+ public PreparedStatement prepareAutoCloseStatement(String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException {
+ final Connection conn = getConnection();
+ final PreparedStatement pstmt = conn.prepareStatement(sql, resultSetType, resultSetConcurrency, resultSetHoldability);
+ if (s_stmtLogger.isTraceEnabled()) {
+ s_stmtLogger.trace("Preparing: " + sql);
+ }
+ closePreviousStatement();
+ _stmt = pstmt;
+ return pstmt;
+ }
+
+ /**
+ * Returns the db connection.
+ *
+ * Note: that you can call getConnection() but beaware that
+ * all prepare statements from the Connection are not garbage
+ * collected!
+ *
+ * @return DB Connection but make sure you understand that
+ * you are responsible for closing the PreparedStatement.
+ * @throws SQLException
+ */
+ public Connection getConnection() throws SQLException {
+ if (_conn == null) {
+ switch (_dbId) {
+ case CLOUD_DB:
+ if(s_ds != null) {
+ _conn = s_ds.getConnection();
+ } else {
+ s_logger.warn("A static-initialized variable becomes null, process is dying?");
+ throw new CloudRuntimeException("Database is not initialized, process is dying?");
+ }
+ break;
+ case USAGE_DB:
+ if(s_usageDS != null) {
+ _conn = s_usageDS.getConnection();
+ } else {
+ s_logger.warn("A static-initialized variable becomes null, process is dying?");
+ throw new CloudRuntimeException("Database is not initialized, process is dying?");
+ }
+ break;
+ case AWSAPI_DB:
+ if(s_awsapiDS != null) {
+ _conn = s_awsapiDS.getConnection();
+ } else {
+ s_logger.warn("A static-initialized variable becomes null, process is dying?");
+ throw new CloudRuntimeException("Database is not initialized, process is dying?");
+ }
+ break;
+
+ case SIMULATOR_DB:
+ if(s_simulatorDS != null) {
+ _conn = s_simulatorDS.getConnection();
+ } else {
+ s_logger.warn("A static-initialized variable becomes null, process is dying?");
+ throw new CloudRuntimeException("Database is not initialized, process is dying?");
+ }
+ break;
+ default:
+
+ throw new CloudRuntimeException("No database selected for the transaction");
+ }
+ _conn.setAutoCommit(!_txn);
+
+ //
+ // MySQL default transaction isolation level is REPEATABLE READ,
+ // to reduce chances of DB deadlock, we will use READ COMMITED isolation level instead
+ // see http://dev.mysql.com/doc/refman/5.0/en/innodb-deadlocks.html
+ //
+ _stack.push(new StackElement(CREATE_CONN, null));
+ if (s_connLogger.isTraceEnabled()) {
+ s_connLogger.trace("Creating a DB connection with " + (_txn ? " txn: " : " no txn: ") + " for " + _dbId + ": dbconn" + System.identityHashCode(_conn) + ". Stack: " + buildName());
+ }
+ } else {
+ s_logger.trace("conn: Using existing DB connection");
+ }
+
+ return _conn;
+ }
+
+ protected boolean takeOver(final String name, final boolean create) {
+ if (_stack.size() != 0) {
+ if (!create) {
+ // If it is not a create transaction, then let's just use the current one.
+ if (s_logger.isTraceEnabled()) {
+ s_logger.trace("Using current transaction: " + toString());
+ }
+ mark(name);
+ return false;
+ }
+
+ final StackElement se = _stack.getFirst();
+ if (se.type == CREATE_TXN) {
+ // This create is called inside of another create. Which is ok?
+ // We will let that create be responsible for cleaning up.
+ if (s_logger.isTraceEnabled()) {
+ s_logger.trace("Create using current transaction: " + toString());
+ }
+ mark(name);
+ return false;
+ }
+
+ s_logger.warn("Encountered a transaction that has leaked. Cleaning up. " + toString());
+ cleanup();
+ }
+
+ if (s_logger.isTraceEnabled()) {
+ s_logger.trace("Took over the transaction: " + name);
+ }
+ _stack.push(new StackElement(create ? CREATE_TXN : CURRENT_TXN, name));
+ _name = name;
+ return true;
+ }
+
+ public void cleanup() {
+ closePreviousStatement();
+
+ removeUpTo(null, null);
+ if (_txn) {
+ rollbackTransaction();
+ }
+ _txn = false;
+ _name = null;
+
+ closeConnection();
+
+ _stack.clear();
+ Merovingian2 lockMaster = Merovingian2.getLockMaster();
+ if (lockMaster != null) {
+ lockMaster.cleanupThread();
+ }
+ }
+
+ public void close() {
+ removeUpTo(CURRENT_TXN, null);
+
+ if (_stack.size() == 0) {
+ s_logger.trace("Transaction is done");
+ cleanup();
+ }
+ }
+
+ /**
+ * close() is used by endTxn to close the connection. This method only
+ * closes the connection if the name is the same as what's stored.
+ *
+ * @param name
+ * @return true if this close actually closes the connection. false if not.
+ */
+ public boolean close(final String name) {
+ if (_name == null) { // Already cleaned up.
+ if (s_logger.isTraceEnabled()) {
+ s_logger.trace("Already cleaned up." + buildName());
+ }
+ return true;
+ }
+
+ if (!_name.equals(name)) {
+ close();
+ return false;
+ }
+
+ if (s_logger.isDebugEnabled() && _stack.size() > 2) {
+ s_logger.debug("Transaction is not closed properly: " + toString() + ". Called by " + buildName());
+ }
+
+ cleanup();
+
+ s_logger.trace("All done");
+ return true;
+ }
+
+ protected boolean hasTxnInStack() {
+ return peekInStack(START_TXN) != null;
+ }
+
+ protected void clearLockTimes() {
+ if (s_lockLogger.isDebugEnabled()) {
+ for (Pair<String, Long> time : _lockTimes) {
+ s_lockLogger.trace("SQL " + time.first() + " took " + (System.currentTimeMillis() - time.second()));
+ }
+ _lockTimes.clear();
+ }
+ }
+
+ public boolean commit() {
+ if (!_txn) {
+ s_logger.warn("txn: Commit called when it is not a transaction: " + buildName());
+ return false;
+ }
+
+ Iterator<StackElement> it = _stack.iterator();
+ while (it.hasNext()) {
+ StackElement st = it.next();
+ if (st.type == START_TXN) {
+ it.remove();
+ break;
+ }
+ }
+
+ if (hasTxnInStack()) {
+ if (s_logger.isTraceEnabled()) {
+ s_logger.trace("txn: Not committing because transaction started elsewhere: " + buildName() + " / " + toString());
+ }
+ return false;
+ }
+
+ _txn = false;
+ try {
+ if (_conn != null) {
+ _conn.commit();
+ s_logger.trace("txn: DB Changes committed. Time = " + (System.currentTimeMillis() - _txnTime));
+ clearLockTimes();
+ closeConnection();
+ }
+ return true;
+ } catch (final SQLException e) {
+ rollbackTransaction();
+ throw new CloudRuntimeException("Unable to commit or close the connection. ", e);
+ }
+ }
+
+ protected void closeConnection() {
+ closePreviousStatement();
+
+ if (_conn == null) {
+ return;
+ }
+
+ if (_txn) {
+ s_connLogger.trace("txn: Not closing DB connection because we're still in a transaction.");
+ return;
+ }
+
+ try {
+ // we should only close db connection when it is not user managed
+ if (this._dbId != CONNECTED_DB) {
+ if (s_connLogger.isTraceEnabled()) {
+ s_connLogger.trace("Closing DB connection: dbconn" + System.identityHashCode(_conn));
+ }
+ _conn.close();
+ _conn = null;
+ }
+
+ } catch (final SQLException e) {
+ s_logger.warn("Unable to close connection", e);
+ }
+ }
+
+ protected void removeUpTo(String type, Object ref) {
+ boolean rollback = false;
+ Iterator<StackElement> it = _stack.iterator();
+ while (it.hasNext()) {
+ StackElement item = it.next();
+
+ it.remove();
+
+ try {
+ if (item.type == type && (ref == null || item.ref == ref)) {
+ break;
+ }
+
+ if (item.type == CURRENT_TXN) {
+ if (s_logger.isTraceEnabled()) {
+ s_logger.trace("Releasing the current txn: " + (item.ref != null ? item.ref : ""));
+ }
+ } else if (item.type == CREATE_CONN) {
+ closeConnection();
+ } else if (item.type == START_TXN) {
+ if (item.ref == null) {
+ rollback = true;
+ } else {
+ try {
+ _conn.rollback((Savepoint)ref);
+ rollback = false;
+ } catch (final SQLException e) {
+ s_logger.warn("Unable to rollback Txn.", e);
+ }
+ }
+ } else if (item.type == STATEMENT) {
+ try {
+ if (s_stmtLogger.isTraceEnabled()) {
+ s_stmtLogger.trace("Closing: " + ref.toString());
+ }
+ Statement stmt = (Statement)ref;
+ try {
+ ResultSet rs = stmt.getResultSet();
+ if (rs != null) {
+ rs.close();
+ }
+ } catch(SQLException e) {
+ s_stmtLogger.trace("Unable to close resultset");
+ }
+ stmt.close();
+ } catch (final SQLException e) {
+ s_stmtLogger.trace("Unable to close statement: " + item);
+ }
+ } else if (item.type == ATTACHMENT) {
+ TransactionAttachment att = (TransactionAttachment)item.ref;
+ if (s_logger.isTraceEnabled()) {
+ s_logger.trace("Cleaning up " + att.getName());
+ }
+ att.cleanup();
+ }
+ } catch(Exception e) {
+ s_logger.error("Unable to clean up " + item, e);
+ }
+ }
+
+ if (rollback) {
+ rollback();
+ }
+ }
+
+ protected void rollbackTransaction() {
+ closePreviousStatement();
+ if (!_txn) {
+ if (s_logger.isTraceEnabled()) {
+ s_logger.trace("Rollback called for " + _name + " when there's no transaction: " + buildName());
+ }
+ return;
+ }
+ assert (!hasTxnInStack()) : "Who's rolling back transaction when there's still txn in stack?";
+ _txn = false;
+ try {
+ if (_conn != null) {
+ if (s_logger.isDebugEnabled()) {
+ s_logger.debug("Rolling back the transaction: Time = " + (System.currentTimeMillis() - _txnTime) + " Name = " + _name + "; called by " + buildName());
+ }
+ _conn.rollback();
+ }
+ clearLockTimes();
+ closeConnection();
+ } catch(final SQLException e) {
+ s_logger.warn("Unable to rollback", e);
+ }
+ }
+
+ protected void rollbackSavepoint(Savepoint sp) {
+ try {
+ if (_conn != null) {
+ _conn.rollback(sp);
+ }
+ } catch (SQLException e) {
+ s_logger.warn("Unable to rollback to savepoint " + sp);
+ }
+
+ if (!hasTxnInStack()) {
+ _txn = false;
+ closeConnection();
+ }
+ }
+
+ public void rollback() {
+ Iterator<StackElement> it = _stack.iterator();
+ while (it.hasNext()) {
+ StackElement st = it.next();
+ if (st.type == START_TXN) {
+ if (st.ref == null) {
+ it.remove();
+ } else {
+ rollback((Savepoint)st.ref);
+ return;
+ }
+ }
+ }
+
+ rollbackTransaction();
+ }
+
+ public Savepoint setSavepoint() throws SQLException {
+ _txn = true;
+ StackElement st = new StackElement(START_TXN, null);
+ _stack.push(st);
+ final Connection conn = getConnection();
+ final Savepoint sp = conn.setSavepoint();
+ st.ref = sp;
+
+ return sp;
+ }
+
+ public Savepoint setSavepoint(final String name) throws SQLException {
+ _txn = true;
+ StackElement st = new StackElement(START_TXN, null);
+ _stack.push(st);
+ final Connection conn = getConnection();
+ final Savepoint sp = conn.setSavepoint(name);
+ st.ref = sp;
+
+ return sp;
+ }
+
+ public void releaseSavepoint(final Savepoint sp) throws SQLException {
+ removeTxn(sp);
+ if (_conn != null) {
+ _conn.releaseSavepoint(sp);
+ }
+
+ if (!hasTxnInStack()) {
+ _txn = false;
+ closeConnection();
+ }
+ }
+
+ protected boolean hasSavepointInStack(Savepoint sp) {
+ Iterator<StackElement> it = _stack.iterator();
+ while (it.hasNext()) {
+ StackElement se = it.next();
+ if (se.type == START_TXN && se.ref == sp) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ protected void removeTxn(Savepoint sp) {
+ assert hasSavepointInStack(sp) : "Removing a save point that's not in the stack";
+
+ if (!hasSavepointInStack(sp)) {
+ return;
+ }
+
+ Iterator<StackElement> it = _stack.iterator();
+ while (it.hasNext()) {
+ StackElement se = it.next();
+ if (se.type == START_TXN) {
+ it.remove();
+ if (se.ref == sp) {
+ return;
+ }
+ }
+ }
+ }
+
+ public void rollback(final Savepoint sp) {
+ removeTxn(sp);
+
+ rollbackSavepoint(sp);
+ }
+
+ public Connection getCurrentConnection() {
+ return _conn;
+ }
+
+ public List<StackElement> getStack() {
+ return _stack;
+ }
+
+ protected Transaction() {
+ _name = null;
+ _conn = null;
+ _stack = null;
+ _txn = false;
+ _dbId = -1;
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ if (!(_conn == null && (_stack == null || _stack.size() == 0))) {
+ assert (false) : "Oh Alex oh alex...something is wrong with how we're doing this";
+ s_logger.error("Something went wrong that a transaction is orphaned before db connection is closed");
+ cleanup();
+ }
+ }
+
+ protected class StackElement {
+ public String type;
+ public Object ref;
+
+ public StackElement (String type, Object ref) {
+ this.type = type;
+ this.ref = ref;
+ }
+
+ @Override
+ public String toString() {
+ return type + "-" + ref;
+ }
+ }
+
+ private static DataSource s_ds;
+ private static DataSource s_usageDS;
+ private static DataSource s_awsapiDS;
+ private static DataSource s_simulatorDS;
+
+ static {
+ // Initialize with assumed db.properties file
+ initDataSource("db.properties");
+ }
+
+ public static void initDataSource(String propsFileName) {
+ try {
+ File dbPropsFile = PropertiesUtil.findConfigFile(propsFileName);
+ final Properties dbProps;
+ if (EncryptionSecretKeyChecker.useEncryption()) {
+ StandardPBEStringEncryptor encryptor = EncryptionSecretKeyChecker.getEncryptor();
+ dbProps = new EncryptableProperties(encryptor);
+ } else {
+ dbProps = new Properties();
+ }
+ try {
+ dbProps.load(new FileInputStream(dbPropsFile));
+ } catch (IOException e) {
+ s_logger.fatal("Unable to load db properties file, pl. check the classpath and file path configuration", e);
+ return;
+ } catch (NullPointerException e) {
+ s_logger.fatal("Unable to locate db properties file within classpath or absolute path: " + propsFileName);
+ return;
+ }
+
+ // FIXME: If params are missing...default them????
+ final int cloudMaxActive = Integer.parseInt(dbProps.getProperty("db.cloud.maxActive"));
+ final int cloudMaxIdle = Integer.parseInt(dbProps.getProperty("db.cloud.maxIdle"));
+ final long cloudMaxWait = Long.parseLong(dbProps.getProperty("db.cloud.maxWait"));
+ final String cloudUsername = dbProps.getProperty("db.cloud.username");
+ final String cloudPassword = dbProps.getProperty("db.cloud.password");
+ final String cloudHost = dbProps.getProperty("db.cloud.host");
+ final int cloudPort = Integer.parseInt(dbProps.getProperty("db.cloud.port"));
+ final String cloudDbName = dbProps.getProperty("db.cloud.name");
+ final boolean cloudAutoReconnect = Boolean.parseBoolean(dbProps.getProperty("db.cloud.autoReconnect"));
+ final String cloudValidationQuery = dbProps.getProperty("db.cloud.validationQuery");
+ final String cloudIsolationLevel = dbProps.getProperty("db.cloud.isolation.level");
+
+ int isolationLevel = Connection.TRANSACTION_READ_COMMITTED;
+ if (cloudIsolationLevel == null) {
+ isolationLevel = Connection.TRANSACTION_READ_COMMITTED;
+ } else if (cloudIsolationLevel.equalsIgnoreCase("readcommitted")) {
+ isolationLevel = Connection.TRANSACTION_READ_COMMITTED;
+ } else if (cloudIsolationLevel.equalsIgnoreCase("repeatableread")) {
+ isolationLevel = Connection.TRANSACTION_REPEATABLE_READ;
+ } else if (cloudIsolationLevel.equalsIgnoreCase("serializable")) {
+ isolationLevel = Connection.TRANSACTION_SERIALIZABLE;
+ } else if (cloudIsolationLevel.equalsIgnoreCase("readuncommitted")) {
+ isolationLevel = Connection.TRANSACTION_READ_UNCOMMITTED;
+ } else {
+ s_logger.warn("Unknown isolation level " + cloudIsolationLevel + ". Using read uncommitted");
+ }
+
+ final boolean cloudTestOnBorrow = Boolean.parseBoolean(dbProps.getProperty("db.cloud.testOnBorrow"));
+ final boolean cloudTestWhileIdle = Boolean.parseBoolean(dbProps.getProperty("db.cloud.testWhileIdle"));
+ final long cloudTimeBtwEvictionRunsMillis = Long.parseLong(dbProps.getProperty("db.cloud.timeBetweenEvictionRunsMillis"));
+ final long cloudMinEvcitableIdleTimeMillis = Long.parseLong(dbProps.getProperty("db.cloud.minEvictableIdleTimeMillis"));
+ final boolean cloudPoolPreparedStatements = Boolean.parseBoolean(dbProps.getProperty("db.cloud.poolPreparedStatements"));
+ final String url = dbProps.getProperty("db.cloud.url.params");
+
+ final boolean useSSL = Boolean.parseBoolean(dbProps.getProperty("db.cloud.useSSL"));
+ if (useSSL) {
+ System.setProperty("javax.net.ssl.keyStore", dbProps.getProperty("db.cloud.keyStore"));
+ System.setProperty("javax.net.ssl.keyStorePassword", dbProps.getProperty("db.cloud.keyStorePassword"));
+ System.setProperty("javax.net.ssl.trustStore", dbProps.getProperty("db.cloud.trustStore"));
+ System.setProperty("javax.net.ssl.trustStorePassword", dbProps.getProperty("db.cloud.trustStorePassword"));
+ }
+
+ String regionId = dbProps.getProperty("region.id");
+ if(regionId == null){
+ s_region_id = 1;
+ } else {
+ s_region_id = Integer.parseInt(regionId);
+ }
+ final GenericObjectPool cloudConnectionPool = new GenericObjectPool(null, cloudMaxActive, GenericObjectPool.DEFAULT_WHEN_EXHAUSTED_ACTION,
+ cloudMaxWait, cloudMaxIdle, cloudTestOnBorrow, false, cloudTimeBtwEvictionRunsMillis, 1, cloudMinEvcitableIdleTimeMillis, cloudTestWhileIdle);
+
+ final ConnectionFactory cloudConnectionFactory = new DriverManagerConnectionFactory("jdbc:mysql://" + cloudHost + ":" + cloudPort + "/" + cloudDbName +
+ "?autoReconnect=" + cloudAutoReconnect + (url != null ? "&" + url : "") + (useSSL ? "&useSSL=true" : ""), cloudUsername, cloudPassword);
+
+ final KeyedObjectPoolFactory poolableObjFactory = (cloudPoolPreparedStatements ? new StackKeyedObjectPoolFactory() : null);
+
+ final PoolableConnectionFactory cloudPoolableConnectionFactory = new PoolableConnectionFactory(cloudConnectionFactory, cloudConnectionPool, poolableObjFactory,
+ cloudValidationQuery, false, false, isolationLevel);
+
+ // Default Data Source for CloudStack
+ s_ds = new PoolingDataSource(cloudPoolableConnectionFactory.getPool());
+
+ // Configure the usage db
+ final int usageMaxActive = Integer.parseInt(dbProps.getProperty("db.usage.maxActive"));
+ final int usageMaxIdle = Integer.parseInt(dbProps.getProperty("db.usage.maxIdle"));
+ final long usageMaxWait = Long.parseLong(dbProps.getProperty("db.usage.maxWait"));
+ final String usageUsername = dbProps.getProperty("db.usage.username");
+ final String usagePassword = dbProps.getProperty("db.usage.password");
+ final String usageHost = dbProps.getProperty("db.usage.host");
+ final int usagePort = Integer.parseInt(dbProps.getProperty("db.usage.port"));
+ final String usageDbName = dbProps.getProperty("db.usage.name");
+ final boolean usageAutoReconnect = Boolean.parseBoolean(dbProps.getProperty("db.usage.autoReconnect"));
+ final String usageUrl = dbProps.getProperty("db.usage.url.params");
+
+ final GenericObjectPool usageConnectionPool = new GenericObjectPool(null, usageMaxActive, GenericObjectPool.DEFAULT_WHEN_EXHAUSTED_ACTION,
+ usageMaxWait, usageMaxIdle);
+
+ final ConnectionFactory usageConnectionFactory = new DriverManagerConnectionFactory("jdbc:mysql://" + usageHost + ":" + usagePort + "/" + usageDbName +
+ "?autoReconnect=" + usageAutoReconnect + (usageUrl != null ? "&" + usageUrl : ""), usageUsername, usagePassword);
+
+ final PoolableConnectionFactory usagePoolableConnectionFactory = new PoolableConnectionFactory(usageConnectionFactory, usageConnectionPool,
+ new StackKeyedObjectPoolFactory(), null, false, false);
+
+ // Data Source for usage server
+ s_usageDS = new PoolingDataSource(usagePoolableConnectionFactory.getPool());
+
+ // Configure awsapi db
+ final String awsapiDbName = dbProps.getProperty("db.awsapi.name");
+ final GenericObjectPool awsapiConnectionPool = new GenericObjectPool(null, usageMaxActive, GenericObjectPool.DEFAULT_WHEN_EXHAUSTED_ACTION,
+ usageMaxWait, usageMaxIdle);
+ final ConnectionFactory awsapiConnectionFactory = new DriverManagerConnectionFactory("jdbc:mysql://" + cloudHost + ":" + cloudPort + "/" + awsapiDbName +
+ "?autoReconnect=" + usageAutoReconnect, cloudUsername, cloudPassword);
+ final PoolableConnectionFactory awsapiPoolableConnectionFactory = new PoolableConnectionFactory(awsapiConnectionFactory, awsapiConnectionPool,
+ new StackKeyedObjectPoolFactory(), null, false, false);
+
+ // Data Source for awsapi
+ s_awsapiDS = new PoolingDataSource(awsapiPoolableConnectionFactory.getPool());
+
+ try {
+ // Configure the simulator db
+ final int simulatorMaxActive = Integer.parseInt(dbProps.getProperty("db.simulator.maxActive"));
+ final int simulatorMaxIdle = Integer.parseInt(dbProps.getProperty("db.simulator.maxIdle"));
+ final long simulatorMaxWait = Long.parseLong(dbProps.getProperty("db.simulator.maxWait"));
+ final String simulatorUsername = dbProps.getProperty("db.simulator.username");
+ final String simulatorPassword = dbProps.getProperty("db.simulator.password");
+ final String simulatorHost = dbProps.getProperty("db.simulator.host");
+ final int simulatorPort = Integer.parseInt(dbProps.getProperty("db.simulator.port"));
+ final String simulatorDbName = dbProps.getProperty("db.simulator.name");
+ final boolean simulatorAutoReconnect = Boolean.parseBoolean(dbProps.getProperty("db.simulator.autoReconnect"));
+
+ final GenericObjectPool simulatorConnectionPool = new GenericObjectPool(null, simulatorMaxActive, GenericObjectPool.DEFAULT_WHEN_EXHAUSTED_ACTION,
+ simulatorMaxWait, simulatorMaxIdle);
+
+ final ConnectionFactory simulatorConnectionFactory = new DriverManagerConnectionFactory("jdbc:mysql://" + simulatorHost + ":" + simulatorPort + "/" + simulatorDbName +
+ "?autoReconnect=" + simulatorAutoReconnect, simulatorUsername, simulatorPassword);
+
+ final PoolableConnectionFactory simulatorPoolableConnectionFactory = new PoolableConnectionFactory(simulatorConnectionFactory, simulatorConnectionPool,
+ new StackKeyedObjectPoolFactory(), null, false, false);
+ s_simulatorDS = new PoolingDataSource(simulatorPoolableConnectionFactory.getPool());
+ } catch (Exception e) {
+ s_logger.debug("Simulator DB properties are not available. Not initializing simulator DS");
+ }
+ } catch (final Exception e) {
+ s_ds = getDefaultDataSource("cloud");
+ s_usageDS = getDefaultDataSource("cloud_usage");
+ s_simulatorDS = getDefaultDataSource("cloud_simulator");
+ s_logger.warn("Unable to load db configuration, using defaults with 5 connections. Falling back on assumed datasource on localhost:3306 using username:password=cloud:cloud. Please check your configuration", e);
+ }
+ }
+
+ private static DataSource getDefaultDataSource(final String database) {
+ final GenericObjectPool connectionPool = new GenericObjectPool(null, 5);
+ final ConnectionFactory connectionFactory = new DriverManagerConnectionFactory(
+ "jdbc:mysql://localhost:3306/" + database, "cloud", "cloud");
+ final PoolableConnectionFactory poolableConnectionFactory = new PoolableConnectionFactory(
+ connectionFactory, connectionPool, null, null, false, true);
+ return new PoolingDataSource(
+ /* connectionPool */poolableConnectionFactory.getPool());
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/f5e5b39c/framework/db/src/com/cloud/utils/db/TransactionAttachment.java
----------------------------------------------------------------------
diff --git a/framework/db/src/com/cloud/utils/db/TransactionAttachment.java b/framework/db/src/com/cloud/utils/db/TransactionAttachment.java
new file mode 100644
index 0000000..198f74c
--- /dev/null
+++ b/framework/db/src/com/cloud/utils/db/TransactionAttachment.java
@@ -0,0 +1,34 @@
+// 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
+// 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.cloud.utils.db;
+
+/**
+ * TransactionAttachment are objects added to Transaction such that when
+ * the in memory transaction is closed, they are automatically closed.
+ *
+ */
+public interface TransactionAttachment {
+ /**
+ * @return a unique name to be inserted.
+ */
+ String getName();
+
+ /**
+ * cleanup() if it wasn't cleaned up before.
+ */
+ void cleanup();
+}