Mercurial > hg > release > thermostat-0.15
changeset 1263:ac5c298a9dae
Implement support for prepared writes.
Reviewed-by: omajid
Review-thread: http://icedtea.classpath.org/pipermail/thermostat/2013-September/008174.html
line wrap: on
line diff
--- a/main/src/main/resources/com/redhat/thermostat/main/impl/bootstrapbundles.properties Mon Sep 30 16:38:50 2013 +0200 +++ b/main/src/main/resources/com/redhat/thermostat/main/impl/bootstrapbundles.properties Tue Sep 03 14:19:10 2013 +0200 @@ -1,5 +1,14 @@ +# FIXME: Remove commons-beanutils, commons-codec, commons-logging, +# commons-collections from bootstrap list once storage-core +# does no longer depend on beanutils. It has been made +# dependent on this because it was required as an interim +# measure to switch over to prepared writes. bundles=thermostat-shared-config-${project.version}.jar, \ thermostat-keyring-${project.version}.jar, \ + commons-codec-${commons-codec.version}.jar, \ + commons-logging-${commons-logging.version}.jar, \ + commons-collections-${commons-collections.version}.jar, \ + commons-beanutils-${commons-beanutils.version}.jar, \ thermostat-storage-core-${project.version}.jar, \ thermostat-common-core-${project.version}.jar, \ thermostat-plugin-validator-${project.version}.jar, \
--- a/storage/core/pom.xml Mon Sep 30 16:38:50 2013 +0200 +++ b/storage/core/pom.xml Tue Sep 03 14:19:10 2013 +0200 @@ -106,6 +106,18 @@ <artifactId>org.apache.felix.framework</artifactId> </dependency> <dependency> + <groupId>commons-beanutils</groupId> + <artifactId>commons-beanutils</artifactId> + </dependency> + <dependency> + <groupId>commons-collections</groupId> + <artifactId>commons-collections</artifactId> + </dependency> + <dependency> + <groupId>commons-logging</groupId> + <artifactId>commons-logging</artifactId> + </dependency> + <dependency> <groupId>com.redhat.thermostat</groupId> <artifactId>thermostat-annotations</artifactId> <version>${project.version}</version>
--- a/storage/core/src/main/java/com/redhat/thermostat/storage/core/StatementDescriptor.java Mon Sep 30 16:38:50 2013 +0200 +++ b/storage/core/src/main/java/com/redhat/thermostat/storage/core/StatementDescriptor.java Tue Sep 03 14:19:10 2013 +0200 @@ -21,7 +21,7 @@ * * @return The statement descriptor. */ - public String getQueryDescriptor() { + public String getDescriptor() { return desc; }
--- a/storage/core/src/main/java/com/redhat/thermostat/storage/internal/dao/AgentInfoDAOImpl.java Mon Sep 30 16:38:50 2013 +0200 +++ b/storage/core/src/main/java/com/redhat/thermostat/storage/internal/dao/AgentInfoDAOImpl.java Tue Sep 03 14:19:10 2013 +0200 @@ -43,7 +43,6 @@ import java.util.logging.Logger; import com.redhat.thermostat.common.utils.LoggingUtils; -import com.redhat.thermostat.storage.core.Add; import com.redhat.thermostat.storage.core.Category; import com.redhat.thermostat.storage.core.CategoryAdapter; import com.redhat.thermostat.storage.core.Cursor; @@ -79,6 +78,19 @@ + CATEGORY.getName() + " WHERE '" + ALIVE_KEY.getName() + "' = ?b"; + // ADD agent-config SET + // 'agentId' = ?s , \ + // 'startTime' = ?l , \ + // 'stopTime' = ?l , \ + // 'alive' = ?b , \ + // 'configListenAddress' = ?s + static final String DESC_ADD_AGENT_INFO = "ADD " + CATEGORY.getName() + " SET " + + "'" + Key.AGENT_ID.getName() + "' = ?s , " + + "'" + AgentInfoDAOImpl.START_TIME_KEY.getName() + "' = ?l , " + + "'" + AgentInfoDAOImpl.STOP_TIME_KEY.getName() + "' = ?l , " + + "'" + AgentInfoDAOImpl.ALIVE_KEY.getName() + "' = ?b , " + + "'" + AgentInfoDAOImpl.CONFIG_LISTEN_ADDRESS.getName() + "' = ?s"; + private final Storage storage; private final Category<AggregateCount> aggregateCategory; private final ExpressionFactory factory; @@ -182,9 +194,21 @@ @Override public void addAgentInformation(AgentInformation agentInfo) { - Add<AgentInformation> replace = storage.createAdd(CATEGORY); - replace.setPojo(agentInfo); - replace.apply(); + StatementDescriptor<AgentInformation> desc = new StatementDescriptor<>(CATEGORY, DESC_ADD_AGENT_INFO); + PreparedStatement<AgentInformation> prepared; + try { + prepared = storage.prepareStatement(desc); + prepared.setString(0, agentInfo.getAgentId()); + prepared.setLong(1, agentInfo.getStartTime()); + prepared.setLong(2, agentInfo.getStopTime()); + prepared.setBoolean(3, agentInfo.isAlive()); + prepared.setString(4, agentInfo.getConfigListenAddress()); + prepared.execute(); + } catch (DescriptorParsingException e) { + logger.log(Level.SEVERE, "Preparing stmt '" + desc + "' failed!", e); + } catch (StatementExecutionException e) { + logger.log(Level.SEVERE, "Executing stmt '" + desc + "' failed!", e); + } } @Override
--- a/storage/core/src/main/java/com/redhat/thermostat/storage/internal/statement/ParsedStatementImpl.java Mon Sep 30 16:38:50 2013 +0200 +++ b/storage/core/src/main/java/com/redhat/thermostat/storage/internal/statement/ParsedStatementImpl.java Tue Sep 03 14:19:10 2013 +0200 @@ -36,11 +36,17 @@ package com.redhat.thermostat.storage.internal.statement; +import com.redhat.thermostat.storage.core.Add; import com.redhat.thermostat.storage.core.IllegalPatchException; +import com.redhat.thermostat.storage.core.Key; import com.redhat.thermostat.storage.core.ParsedStatement; import com.redhat.thermostat.storage.core.PreparedParameter; import com.redhat.thermostat.storage.core.Query; +import com.redhat.thermostat.storage.core.Remove; +import com.redhat.thermostat.storage.core.Replace; import com.redhat.thermostat.storage.core.Statement; +import com.redhat.thermostat.storage.core.Update; +import com.redhat.thermostat.storage.internal.statement.PatchedSetListPojoConverter.IllegalPojoException; import com.redhat.thermostat.storage.model.Pojo; import com.redhat.thermostat.storage.query.Expression; @@ -53,12 +59,15 @@ */ class ParsedStatementImpl<T extends Pojo> implements ParsedStatement<T> { + private final Statement<T> statement; + private final Class<T> dataClass; private int numParams; private SuffixExpression suffixExpn; - private final Statement<T> statement; + private SetList setList; - ParsedStatementImpl(Statement<T> statement) { + ParsedStatementImpl(Statement<T> statement, Class<T> dataClass) { this.statement = statement; + this.dataClass = dataClass; } @Override @@ -78,6 +87,7 @@ IllegalStateException expn = new IllegalStateException(msg); throw new IllegalPatchException(expn); } + patchSetList(params); patchWhere(params); patchSort(params); patchLimit(params); @@ -86,6 +96,45 @@ return statement; } + private void patchSetList(PreparedParameter[] params) throws IllegalPatchException { + if (setList.getValues().size() == 0) { + // no set list, nothing to do + return; + } + // do the patching + PatchedSetList patchedSetList = setList.patch(params); + // set the values + if (statement instanceof Add) { + T pojo = convertToPojo(patchedSetList); + Add<T> add = (Add<T>)statement; + add.setPojo(pojo); + } + if (statement instanceof Replace) { + T pojo = convertToPojo(patchedSetList); + Replace<T> replace = (Replace<T>)statement; + replace.setPojo(pojo); + } + if (statement instanceof Update) { + Update<T> update = (Update<T>)statement; + for (PatchedSetListMember mem: patchedSetList.getSetListMembers()) { + @SuppressWarnings("unchecked") + Key<Object> key = (Key<Object>)mem.getKey(); + update.set(key, mem.getValue()); + } + } + } + + private T convertToPojo(PatchedSetList setList) throws IllegalPatchException { + PatchedSetListPojoConverter<T> converter = new PatchedSetListPojoConverter<>(setList, dataClass); + T pojo = null; + try { + pojo = converter.convertToPojo(); + } catch (IllegalPojoException e) { + throw new IllegalPatchException(e); + } + return pojo; + } + private void patchLimit(PreparedParameter[] params) throws IllegalPatchException { LimitExpression expn = suffixExpn.getLimitExpn(); if (expn == null) { @@ -97,7 +146,7 @@ Query<T> query = (Query<T>) statement; query.limit(patchedExp.getLimitValue()); } else { - String msg = "Patching of non-query types not (yet) supported! Class was:" + String msg = "Patching 'limit' of non-query types not supported! Class was:" + statement.getClass().getName(); IllegalStateException invalid = new IllegalStateException(msg); throw new IllegalPatchException(invalid); @@ -118,7 +167,7 @@ query.sort(members[i].getSortKey(), members[i].getDirection()); } } else { - String msg = "Patching of non-query types not (yet) supported! Class was:" + String msg = "Patching 'sort' of non-query types not supported! Class was:" + statement.getClass().getName(); IllegalStateException invalid = new IllegalStateException(msg); throw new IllegalPatchException(invalid); @@ -138,8 +187,17 @@ if (statement instanceof Query) { Query<T> query = (Query<T>) statement; query.where(whereClause); + } else if (statement instanceof Replace) { + Replace<T> replace = (Replace<T>) statement; + replace.where(whereClause); + } else if (statement instanceof Update) { + Update<T> update = (Update<T>) statement; + update.where(whereClause); + } else if (statement instanceof Remove) { + Remove<T> remove = (Remove<T>) statement; + remove.where(whereClause); } else { - String msg = "Patching of non-query types not (yet) supported! Class was:" + String msg = "Patching of where clause not supported! Class was:" + statement.getClass().getName(); IllegalStateException invalid = new IllegalStateException(msg); throw new IllegalPatchException(invalid); @@ -158,4 +216,12 @@ return suffixExpn; } + SetList getSetList() { + return setList; + } + + void setSetList(SetList setList) { + this.setList = setList; + } + }
--- a/storage/core/src/main/java/com/redhat/thermostat/storage/internal/statement/PatchedExpression.java Mon Sep 30 16:38:50 2013 +0200 +++ b/storage/core/src/main/java/com/redhat/thermostat/storage/internal/statement/PatchedExpression.java Tue Sep 03 14:19:10 2013 +0200 @@ -96,4 +96,23 @@ */ int getLimitValue(); +} + +interface PatchedSetListMemberExpression extends PatchedExpression { + + /** + * + * @return The patched set list member. + */ + PatchedSetListMember getSetListMember(); +} + +interface PatchedSetList extends PatchedExpression { + + /** + * @return The patched set list + * + */ + PatchedSetListMember[] getSetListMembers(); + } \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/storage/core/src/main/java/com/redhat/thermostat/storage/internal/statement/PatchedSetListMember.java Tue Sep 03 14:19:10 2013 +0200 @@ -0,0 +1,62 @@ +/* + * Copyright 2012, 2013 Red Hat, Inc. + * + * This file is part of Thermostat. + * + * Thermostat is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published + * by the Free Software Foundation; either version 2, or (at your + * option) any later version. + * + * Thermostat 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 + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Thermostat; see the file COPYING. If not see + * <http://www.gnu.org/licenses/>. + * + * Linking this code with other modules is making a combined work + * based on this code. Thus, the terms and conditions of the GNU + * General Public License cover the whole combination. + * + * As a special exception, the copyright holders of this code give + * you permission to link this code with independent modules to + * produce an executable, regardless of the license terms of these + * independent modules, and to copy and distribute the resulting + * executable under terms of your choice, provided that you also + * meet, for each linked independent module, the terms and conditions + * of the license of that module. An independent module is a module + * which is not derived from or based on this code. If you modify + * this code, you may extend this exception to your version of the + * library, but you are not obligated to do so. If you do not wish + * to do so, delete this exception statement from your version. + */ + +package com.redhat.thermostat.storage.internal.statement; + +import com.redhat.thermostat.storage.core.Key; + +/** + * Data structure representing patched SET lists members. + * + */ +class PatchedSetListMember { + + private final Key<?> key; + private final Object value; + + PatchedSetListMember(Key<?> key, Object value) { + this.key = key; + this.value = value; + } + + Key<?> getKey() { + return key; + } + + Object getValue() { + return value; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/storage/core/src/main/java/com/redhat/thermostat/storage/internal/statement/PatchedSetListPojoConverter.java Tue Sep 03 14:19:10 2013 +0200 @@ -0,0 +1,95 @@ +/* + * Copyright 2012, 2013 Red Hat, Inc. + * + * This file is part of Thermostat. + * + * Thermostat is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published + * by the Free Software Foundation; either version 2, or (at your + * option) any later version. + * + * Thermostat 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 + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Thermostat; see the file COPYING. If not see + * <http://www.gnu.org/licenses/>. + * + * Linking this code with other modules is making a combined work + * based on this code. Thus, the terms and conditions of the GNU + * General Public License cover the whole combination. + * + * As a special exception, the copyright holders of this code give + * you permission to link this code with independent modules to + * produce an executable, regardless of the license terms of these + * independent modules, and to copy and distribute the resulting + * executable under terms of your choice, provided that you also + * meet, for each linked independent module, the terms and conditions + * of the license of that module. An independent module is a module + * which is not derived from or based on this code. If you modify + * this code, you may extend this exception to your version of the + * library, but you are not obligated to do so. If you do not wish + * to do so, delete this exception statement from your version. + */ + +package com.redhat.thermostat.storage.internal.statement; + +import java.beans.PropertyDescriptor; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +import org.apache.commons.beanutils.PropertyUtils; + +import com.redhat.thermostat.storage.model.Pojo; + +class PatchedSetListPojoConverter<T extends Pojo> { + + private final PatchedSetList setList; + private final Class<T> dataClass; + + PatchedSetListPojoConverter(PatchedSetList setList, Class<T> dataClass) { + this.setList = setList; + this.dataClass = dataClass; + } + + T convertToPojo() throws IllegalPojoException { + T instance = null; + try { + instance = dataClass.newInstance(); + PatchedSetListMember[] members = setList.getSetListMembers(); + for (PatchedSetListMember mem: members) { + String propName = mem.getKey().getName(); + PropertyDescriptor desc = PropertyUtils.getPropertyDescriptor(instance, propName); + if (desc == null) { + throw new IllegalPojoException("Property " + propName + " not found in Pojo: " + dataClass); + } + setValueForProperty(instance, desc, mem); + } + } catch (NoSuchMethodException | InstantiationException | IllegalAccessException + | IllegalArgumentException | InvocationTargetException e) { + throw new IllegalPojoException(e); + } + return instance; + } + + private void setValueForProperty(T instance, PropertyDescriptor desc, + PatchedSetListMember mem) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException { + Method writeMethod = desc.getWriteMethod(); + Object value = mem.getValue(); + writeMethod.invoke(instance, value); + } + + @SuppressWarnings("serial") + static class IllegalPojoException extends Exception { + + IllegalPojoException(Throwable cause) { + super(cause); + } + + private IllegalPojoException(String msg) { + super(msg); + } + } +}
--- a/storage/core/src/main/java/com/redhat/thermostat/storage/internal/statement/PreparedStatementImpl.java Mon Sep 30 16:38:50 2013 +0200 +++ b/storage/core/src/main/java/com/redhat/thermostat/storage/internal/statement/PreparedStatementImpl.java Tue Sep 03 14:19:10 2013 +0200 @@ -153,6 +153,6 @@ @Override public String toString() { - return desc.getQueryDescriptor(); + return desc.getDescriptor(); } }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/storage/core/src/main/java/com/redhat/thermostat/storage/internal/statement/SetList.java Tue Sep 03 14:19:10 2013 +0200 @@ -0,0 +1,96 @@ +/* + * Copyright 2012, 2013 Red Hat, Inc. + * + * This file is part of Thermostat. + * + * Thermostat is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published + * by the Free Software Foundation; either version 2, or (at your + * option) any later version. + * + * Thermostat 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 + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Thermostat; see the file COPYING. If not see + * <http://www.gnu.org/licenses/>. + * + * Linking this code with other modules is making a combined work + * based on this code. Thus, the terms and conditions of the GNU + * General Public License cover the whole combination. + * + * As a special exception, the copyright holders of this code give + * you permission to link this code with independent modules to + * produce an executable, regardless of the license terms of these + * independent modules, and to copy and distribute the resulting + * executable under terms of your choice, provided that you also + * meet, for each linked independent module, the terms and conditions + * of the license of that module. An independent module is a module + * which is not derived from or based on this code. If you modify + * this code, you may extend this exception to your version of the + * library, but you are not obligated to do so. If you do not wish + * to do so, delete this exception statement from your version. + */ + +package com.redhat.thermostat.storage.internal.statement; + +import java.util.ArrayList; +import java.util.List; + +import com.redhat.thermostat.storage.core.IllegalPatchException; +import com.redhat.thermostat.storage.core.PreparedParameter; + +/** + * Represents a set list of a prepared write statement. + * + */ +class SetList implements Patchable, Printable { + + private List<SetListValue> values = new ArrayList<>(); + + void addValue(SetListValue newValue) { + values.add(newValue); + } + + List<SetListValue> getValues() { + return values; + } + + @Override + public void print(int level) { + System.out.println("SET:"); + for (SetListValue val: getValues()) { + val.print(level); + } + } + + @Override + public PatchedSetList patch(PreparedParameter[] params) + throws IllegalPatchException { + List<PatchedSetListMember> patchedSetList = new ArrayList<>(); + for (SetListValue val: getValues()) { + PatchedSetListMemberExpression memberExp = val.patch(params); + patchedSetList.add(memberExp.getSetListMember()); + } + PatchedSetListMember[] members = patchedSetList.toArray(new PatchedSetListMember[0]); + PatchedSetListImpl setList = new PatchedSetListImpl(members); + return setList; + } + + private static class PatchedSetListImpl implements PatchedSetList { + + private final PatchedSetListMember[] members; + + private PatchedSetListImpl(PatchedSetListMember[] members) { + this.members = members; + } + + @Override + public PatchedSetListMember[] getSetListMembers() { + return members; + } + + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/storage/core/src/main/java/com/redhat/thermostat/storage/internal/statement/SetListValue.java Tue Sep 03 14:19:10 2013 +0200 @@ -0,0 +1,131 @@ +/* + * Copyright 2012, 2013 Red Hat, Inc. + * + * This file is part of Thermostat. + * + * Thermostat is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published + * by the Free Software Foundation; either version 2, or (at your + * option) any later version. + * + * Thermostat 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 + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Thermostat; see the file COPYING. If not see + * <http://www.gnu.org/licenses/>. + * + * Linking this code with other modules is making a combined work + * based on this code. Thus, the terms and conditions of the GNU + * General Public License cover the whole combination. + * + * As a special exception, the copyright holders of this code give + * you permission to link this code with independent modules to + * produce an executable, regardless of the license terms of these + * independent modules, and to copy and distribute the resulting + * executable under terms of your choice, provided that you also + * meet, for each linked independent module, the terms and conditions + * of the license of that module. An independent module is a module + * which is not derived from or based on this code. If you modify + * this code, you may extend this exception to your version of the + * library, but you are not obligated to do so. If you do not wish + * to do so, delete this exception statement from your version. + */ + +package com.redhat.thermostat.storage.internal.statement; + +import java.util.Objects; + +import com.redhat.thermostat.storage.core.IllegalPatchException; +import com.redhat.thermostat.storage.core.Key; +import com.redhat.thermostat.storage.core.PreparedParameter; +import com.redhat.thermostat.storage.query.LiteralExpression; + +/** + * Represents a value pair in a set list of prepared writes. + * + */ +class SetListValue implements Patchable, Printable { + + private TerminalNode key; + private TerminalNode value; + + TerminalNode getKey() { + return key; + } + void setKey(TerminalNode key) { + this.key = key; + } + TerminalNode getValue() { + return value; + } + void setValue(TerminalNode value) { + this.value = value; + } + + @Override + public boolean equals(Object other) { + if (!(other instanceof SetListValue)) { + return false; + } + SetListValue o = (SetListValue)other; + return Objects.equals(this.getValue(), o.getValue()) && + Objects.equals(this.getKey(), o.getKey()); + } + + @Override + public int hashCode() { + return Objects.hash(this.getKey(), this.getValue()); + } + + @Override + public String toString() { + return "{" + getKey() + " = " + getValue() + "}"; + } + + @Override + public void print(int level) { + for (int i = 0; i < level; i++) { + System.out.print(" "); + } + System.out.println(toString()); + + } + + @Override + public PatchedSetListMemberExpression patch(PreparedParameter[] params) + throws IllegalPatchException { + + // patch LHS + PatchedWhereExpression keyExp = key.patch(params); + @SuppressWarnings("unchecked") // we've generated the key + LiteralExpression<Key<?>> keyLiteral = (LiteralExpression<Key<?>>)keyExp.getExpression(); + Key<?> keyVal = (Key<?>) keyLiteral.getValue(); + + // patch RHS + PatchedWhereExpression valExp = value.patch(params); + LiteralExpression<?> valLiteral = (LiteralExpression<?>)valExp.getExpression(); + Object val = valLiteral.getValue(); + + PatchedSetListMember member = new PatchedSetListMember(keyVal, val); + PatchedSetListMemberExpressionImpl retval = new PatchedSetListMemberExpressionImpl(member); + return retval; + } + + private static class PatchedSetListMemberExpressionImpl implements PatchedSetListMemberExpression { + + private final PatchedSetListMember member; + + private PatchedSetListMemberExpressionImpl(PatchedSetListMember member) { + this.member = member; + } + + @Override + public PatchedSetListMember getSetListMember() { + return member; + } + + } +}
--- a/storage/core/src/main/java/com/redhat/thermostat/storage/internal/statement/StatementDescriptorParser.java Mon Sep 30 16:38:50 2013 +0200 +++ b/storage/core/src/main/java/com/redhat/thermostat/storage/internal/statement/StatementDescriptorParser.java Tue Sep 03 14:19:10 2013 +0200 @@ -40,6 +40,7 @@ import java.util.List; import java.util.StringTokenizer; +import com.redhat.thermostat.storage.core.Add; import com.redhat.thermostat.storage.core.AggregateQuery.AggregateFunction; import com.redhat.thermostat.storage.core.BackingStorage; import com.redhat.thermostat.storage.core.Category; @@ -48,7 +49,11 @@ import com.redhat.thermostat.storage.core.ParsedStatement; import com.redhat.thermostat.storage.core.Query; import com.redhat.thermostat.storage.core.Query.SortDirection; +import com.redhat.thermostat.storage.core.Remove; +import com.redhat.thermostat.storage.core.Replace; +import com.redhat.thermostat.storage.core.Statement; import com.redhat.thermostat.storage.core.StatementDescriptor; +import com.redhat.thermostat.storage.core.Update; import com.redhat.thermostat.storage.model.Pojo; import com.redhat.thermostat.storage.query.BinaryComparisonOperator; import com.redhat.thermostat.storage.query.BinaryLogicalOperator; @@ -57,13 +62,28 @@ * A parser for the string representation of {@link StatementDescriptor}s. * Tokens have to be separated by whitespace. * - * This parser implements the following simple grammar for statement descriptors - * (currently it only supports QUERY statement types): + * This parser implements the following simple grammar for statement descriptors. + * It supports the following statement types: + * <ul> + * <li>QUERY (read)</li> + * <li>QUERY-COUNT (read)</li> + * <li>ADD (write)</li> + * <li>UPDATE (write)</li> + * <li>REPLACE (write)</li> + * <li>REMOVE (write)</li> + * </ul> * + * <p><strong>Grammar:</strong></p> * <pre> - * statementDesc := statementType category suffix - * statementType := 'QUERY' | 'QUERY-COUNT' - * category := literal + * statementDesc := statementType category setList suffix + * statementType := 'QUERY' | 'QUERY-COUNT' | + * 'ADD' | 'REPLACE' | 'UPDATE' | + * 'REMOVE' + * category := string + * setList := 'SET' setValues | \empty + * setValues := valuePair valueList + * valuePair := term '=' term + * valueList := ',' setValues | \empty * suffix := 'WHERE' where | * 'SORT' sortCond | * 'LIMIT' term | \empty @@ -72,7 +92,7 @@ * orCond := 'OR' whereExp | \empty * sort := 'SORT' sortCond | \empty * sortCond := sortPair sortList - * sortPair := literal sortModifier + * sortPair := term sortModifier * sortModifier := 'ASC' | 'DSC' * sortList := ',' sortCond | \empty * limit := 'LIMIT' term | \empty @@ -86,9 +106,11 @@ * sQuote := \' * boolean := <true> | <false> * int := <literal-int> - * long := <literal-long> + * long := <literal-long>longPostFix + * longPostFix := 'l' | 'L' * string := <literal-string-value> - * compExpRHS := '!=' term | '=' term | '<=' term | '>=' term | '<' term | '>' term + * compExpRHS := '!=' term | '=' term | '<=' term | '>=' term | + * '<' term | '>' term * </pre> * * This implements the following logic precedence rules (in this order of @@ -105,10 +127,18 @@ class StatementDescriptorParser<T extends Pojo> { private static final String TOKEN_DELIMS = " \t\r\n\f"; + private static final short IDX_QUERY = 0; + private static final short IDX_QUERY_COUNT = 1; + private static final short IDX_ADD = 2; + private static final short IDX_REPLACE = 3; + private static final short IDX_UPDATE = 4; + private static final short IDX_REMOVE = 5; private static final String[] KNOWN_STATEMENT_TYPES = new String[] { - "QUERY", "QUERY-COUNT" + "QUERY", "QUERY-COUNT", "ADD", "REPLACE", "UPDATE", "REMOVE" }; private static final String SORTLIST_SEP = ","; + private static final String SETLIST_SEP = SORTLIST_SEP; + private static final String KEYWORD_SET = "SET"; private static final String KEYWORD_WHERE = "WHERE"; private static final String KEYWORD_SORT = "SORT"; private static final String KEYWORD_LIMIT = "LIMIT"; @@ -124,9 +154,10 @@ // the parsed statement private ParsedStatementImpl<T> parsedStatement; private SuffixExpression tree; + private SetList setList; StatementDescriptorParser(BackingStorage storage, StatementDescriptor<T> desc) { - this.tokens = getTokens(desc.getQueryDescriptor()); + this.tokens = getTokens(desc.getDescriptor()); this.currTokenIndex = 0; this.placeHolderCount = 0; this.desc = desc; @@ -147,16 +178,126 @@ matchCategory(); // matched so far, create the raw statement createStatement(); + this.setList = new SetList(); + matchSetList(setList); this.tree = new SuffixExpression(); matchSuffix(); if (currTokenIndex != tokens.length) { throw new DescriptorParsingException("Incomplete parse"); } parsedStatement.setNumFreeParams(placeHolderCount); + parsedStatement.setSetList(setList); parsedStatement.setSuffixExpression(tree); + doSemanticAnalysis(); return parsedStatement; } + private void doSemanticAnalysis() throws DescriptorParsingException { + // TODO: + // - Check that ADD/REPLACE specifies all keys judging by the Pojo + // model class. Not sure if good idea though, as this would likely + // introduce dep on beanutils. + Statement<T> stmt = parsedStatement.getRawStatement(); + if (stmt == null) { + // should never be null + throw new NullPointerException(); + } + if (stmt instanceof Add && tree.getWhereExpn() != null) { + String msg = "WHERE clause not allowed for ADD"; + throw new DescriptorParsingException(msg); + } + if (stmt instanceof Replace && tree.getWhereExpn() == null) { + String msg = "WHERE clause required for REPLACE"; + throw new DescriptorParsingException(msg); + } + if (stmt instanceof Update) { + if (tree.getWhereExpn() == null) { + // WHERE required for UPDATE + String msg = "WHERE clause required for UPDATE"; + throw new DescriptorParsingException(msg); + } + if (setList.getValues().size() == 0) { + // SET required for UPDATE + String msg = "SET list required for UPDATE"; + throw new DescriptorParsingException(msg); + } + } + if (stmt instanceof Remove && setList.getValues().size() > 0) { + String msg = "SET not allowed for REMOVE"; + throw new DescriptorParsingException(msg); + } + if (stmt instanceof Query) { + if (setList.getValues().size() > 0) { + // Must not have SET for QUERYs + String msg = "SET not allowed for QUERY/QUERY-COUNT"; + throw new DescriptorParsingException(msg); + } + } else { + // only queries can have sort/limit expressions + if (this.tree.getLimitExpn() != null || this.tree.getSortExpn() != null) { + String msg = "LIMIT/SORT only allowed for QUERY/QUERY-COUNT"; + throw new DescriptorParsingException(msg); + } + } + } + + /* + * Match set list for DML statements. + */ + private void matchSetList(final SetList setList) throws DescriptorParsingException { + if (tokens.length == currTokenIndex) { + // no set list + return; + } + if (tokens[currTokenIndex].equals(KEYWORD_SET)) { + currTokenIndex++; // SET + matchSetValues(setList); + } + // empty, proceed with suffix + } + + /* + * Match list of values in a SET expression + */ + private void matchSetValues(SetList setList) throws DescriptorParsingException { + matchValuePair(setList); + matchValueList(setList); + } + + /* + * Match more value pairs in a SET list + */ + private void matchValueList(SetList setList) throws DescriptorParsingException { + if (currTokenIndex == tokens.length) { + // empty + return; + } + if (tokens[currTokenIndex].equals(SETLIST_SEP)) { + currTokenIndex++; // , + matchSetValues(setList); + } + } + + /* + * Match one pair of values in a + */ + private void matchValuePair(SetList setList) throws DescriptorParsingException { + SetListValue value = new SetListValue(); + TerminalNode lval = new TerminalNode(null); + matchTerm(lval, true); + value.setKey(lval); + if (tokens[currTokenIndex].equals("=")) { + currTokenIndex++; // = + } else { + String msg = "Expected '=' after SET value LHS. Token was ->" + tokens[currTokenIndex] + "<-"; + throw new DescriptorParsingException(msg); + } + TerminalNode rval = new TerminalNode(null); + matchTerm(rval, false); + value.setValue(rval); + setList.addValue(value); + } + /* * Match optional suffixes. */ @@ -579,23 +720,42 @@ } private void createStatement() { - if (tokens[0].equals(KNOWN_STATEMENT_TYPES[0])) { + // matchStatementType and matchCategory advanced currTokenIndex, + // lets use idx of 0 here. + final String statementType = tokens[0]; + Class<T> dataClass = desc.getCategory().getDataClass(); + if (statementType.equals(KNOWN_STATEMENT_TYPES[IDX_QUERY])) { // regular query case Query<T> query = storage.createQuery(desc.getCategory()); - this.parsedStatement = new ParsedStatementImpl<>(query); - } else if (tokens[0].equals(KNOWN_STATEMENT_TYPES[1])) { + this.parsedStatement = new ParsedStatementImpl<>(query, dataClass); + } else if (statementType.equals(KNOWN_STATEMENT_TYPES[IDX_QUERY_COUNT])) { // create aggregate count query Query<T> query = storage.createAggregateQuery(AggregateFunction.COUNT, desc.getCategory()); - this.parsedStatement = new ParsedStatementImpl<>(query); - } - else { - throw new IllegalStateException("Don't know how to create statement type '" + tokens[0] + "'"); + this.parsedStatement = new ParsedStatementImpl<>(query, dataClass); + } else if (statementType.equals(KNOWN_STATEMENT_TYPES[IDX_ADD])) { + // create add + Add<T> add = storage.createAdd(desc.getCategory()); + this.parsedStatement = new ParsedStatementImpl<>(add, dataClass); + } else if (statementType.equals(KNOWN_STATEMENT_TYPES[IDX_REPLACE])) { + // create replace + Replace<T> replace = storage.createReplace(desc.getCategory()); + this.parsedStatement = new ParsedStatementImpl<>(replace, dataClass); + } else if (statementType.equals(KNOWN_STATEMENT_TYPES[IDX_UPDATE])) { + // create replace + Update<T> update = storage.createUpdate(desc.getCategory()); + this.parsedStatement = new ParsedStatementImpl<>(update, dataClass); + } else if (statementType.equals(KNOWN_STATEMENT_TYPES[IDX_REMOVE])) { + // create remove + Remove<T> remove = storage.createRemove(desc.getCategory()); + this.parsedStatement = new ParsedStatementImpl<>(remove, dataClass); + } else { + throw new IllegalStateException("Don't know how to create statement type '" + statementType + "'"); } } private void matchCategory() throws DescriptorParsingException { if (currTokenIndex >= tokens.length) { - throw new DescriptorParsingException("Missing category name in descriptor: '" + desc.getQueryDescriptor() + "'"); + throw new DescriptorParsingException("Missing category name in descriptor: '" + desc.getDescriptor() + "'"); } Category<?> category = desc.getCategory(); if (!tokens[currTokenIndex].equals(category.getName())) { @@ -609,10 +769,23 @@ } private void matchStatementType() throws DescriptorParsingException { - // matches 'QUERY' and 'QUERY-COUNT' only at this point - if (tokens[currTokenIndex].equals(KNOWN_STATEMENT_TYPES[0])) { + if (tokens[currTokenIndex].equals(KNOWN_STATEMENT_TYPES[IDX_QUERY])) { + // QUERY + currTokenIndex++; + } else if (tokens[currTokenIndex].equals(KNOWN_STATEMENT_TYPES[IDX_QUERY_COUNT])) { + // QUERY-COUNT + currTokenIndex++; + } else if (tokens[currTokenIndex].equals(KNOWN_STATEMENT_TYPES[IDX_ADD])) { + // ADD currTokenIndex++; - } else if (tokens[currTokenIndex].equals(KNOWN_STATEMENT_TYPES[1])) { + } else if (tokens[currTokenIndex].equals(KNOWN_STATEMENT_TYPES[IDX_REPLACE])) { + // REPLACE + currTokenIndex++; + } else if (tokens[currTokenIndex].equals(KNOWN_STATEMENT_TYPES[IDX_UPDATE])) { + // UPDATE + currTokenIndex++; + } else if (tokens[currTokenIndex].equals(KNOWN_STATEMENT_TYPES[IDX_REMOVE])) { + // REMOVE currTokenIndex++; } else { throw new DescriptorParsingException("Unknown statement type: '" + tokens[currTokenIndex] + "'");
--- a/storage/core/src/main/java/com/redhat/thermostat/storage/internal/statement/UnfinishedValueNode.java Mon Sep 30 16:38:50 2013 +0200 +++ b/storage/core/src/main/java/com/redhat/thermostat/storage/internal/statement/UnfinishedValueNode.java Tue Sep 03 14:19:10 2013 +0200 @@ -83,7 +83,8 @@ @Override public String toString() { - return "Unfinished value (" + getParameterIndex() + ")"; + return "Unfinished value (" + getParameterIndex() + ") " + getType() + + ":" + ( isLHS ? "LHS" : "RHS" ); } @Override
--- a/storage/core/src/test/java/com/redhat/thermostat/storage/internal/dao/AgentInfoDAOTest.java Mon Sep 30 16:38:50 2013 +0200 +++ b/storage/core/src/test/java/com/redhat/thermostat/storage/internal/dao/AgentInfoDAOTest.java Tue Sep 03 14:19:10 2013 +0200 @@ -51,8 +51,9 @@ import org.junit.Before; import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.Mockito; -import com.redhat.thermostat.storage.core.Add; import com.redhat.thermostat.storage.core.Category; import com.redhat.thermostat.storage.core.Cursor; import com.redhat.thermostat.storage.core.DescriptorParsingException; @@ -102,6 +103,12 @@ assertEquals(expectedAliveAgents, AgentInfoDAOImpl.QUERY_ALIVE_AGENTS); String aggregateAllAgents = "QUERY-COUNT agent-config"; assertEquals(aggregateAllAgents, AgentInfoDAOImpl.AGGREGATE_COUNT_ALL_AGENTS); + String addAgentInfo = "ADD agent-config SET 'agentId' = ?s , " + + "'startTime' = ?l , " + + "'stopTime' = ?l , " + + "'alive' = ?b , " + + "'configListenAddress' = ?s"; + assertEquals(addAgentInfo, AgentInfoDAOImpl.DESC_ADD_AGENT_INFO); } @Test @@ -256,20 +263,30 @@ assertSame(expected, computed); } + @SuppressWarnings("unchecked") @Test - public void verifyAddAgentInformation() { + public void verifyAddAgentInformation() throws StatementExecutionException, DescriptorParsingException { Storage storage = mock(Storage.class); - @SuppressWarnings("unchecked") - Add<AgentInformation> add = mock(Add.class); - when(storage.createAdd(eq(AgentInfoDAO.CATEGORY))).thenReturn(add); + PreparedStatement<AgentInformation> add = mock(PreparedStatement.class); + when(storage.prepareStatement(any(StatementDescriptor.class))).thenReturn(add); AgentInfoDAO dao = new AgentInfoDAOImpl(storage); dao.addAgentInformation(agentInfo1); - verify(storage).createAdd(AgentInfoDAO.CATEGORY); - verify(add).setPojo(agentInfo1); - verify(add).apply(); + @SuppressWarnings("rawtypes") + ArgumentCaptor<StatementDescriptor> captor = ArgumentCaptor.forClass(StatementDescriptor.class); + + verify(storage).prepareStatement(captor.capture()); + StatementDescriptor<?> desc = captor.getValue(); + assertEquals(AgentInfoDAOImpl.DESC_ADD_AGENT_INFO, desc.getDescriptor()); + verify(add).setString(0, agentInfo1.getAgentId()); + verify(add).setLong(1, agentInfo1.getStartTime()); + verify(add).setLong(2, agentInfo1.getStopTime()); + verify(add).setBoolean(3, agentInfo1.isAlive()); + verify(add).setString(4, agentInfo1.getConfigListenAddress()); + verify(add).execute(); + Mockito.verifyNoMoreInteractions(add); } @Test
--- a/storage/core/src/test/java/com/redhat/thermostat/storage/internal/statement/ParsedStatementImplTest.java Mon Sep 30 16:38:50 2013 +0200 +++ b/storage/core/src/test/java/com/redhat/thermostat/storage/internal/statement/ParsedStatementImplTest.java Tue Sep 03 14:19:10 2013 +0200 @@ -48,18 +48,23 @@ import org.junit.Test; import com.redhat.thermostat.common.Pair; +import com.redhat.thermostat.storage.core.Add; import com.redhat.thermostat.storage.core.Cursor; +import com.redhat.thermostat.storage.core.DataModifyingStatement; import com.redhat.thermostat.storage.core.IllegalPatchException; import com.redhat.thermostat.storage.core.Key; import com.redhat.thermostat.storage.core.PreparedParameter; import com.redhat.thermostat.storage.core.Query; import com.redhat.thermostat.storage.core.Query.SortDirection; +import com.redhat.thermostat.storage.core.Replace; +import com.redhat.thermostat.storage.core.Update; import com.redhat.thermostat.storage.model.Pojo; import com.redhat.thermostat.storage.query.BinaryComparisonExpression; import com.redhat.thermostat.storage.query.BinaryComparisonOperator; import com.redhat.thermostat.storage.query.BinaryLogicalExpression; import com.redhat.thermostat.storage.query.BinaryLogicalOperator; import com.redhat.thermostat.storage.query.Expression; +import com.redhat.thermostat.storage.query.ExpressionFactory; import com.redhat.thermostat.storage.query.LiteralExpression; public class ParsedStatementImplTest { @@ -79,10 +84,11 @@ @Test public void canPatchWhereAndExpr() throws IllegalPatchException { // create the parsedStatementImpl we are going to use - ParsedStatementImpl<Pojo> parsedStmt = new ParsedStatementImpl<>(statement); + ParsedStatementImpl<Pojo> parsedStmt = new ParsedStatementImpl<>(statement, null); SuffixExpression suffixExpn = new SuffixExpression(); suffixExpn.setLimitExpn(null); suffixExpn.setSortExpn(null); + parsedStmt.setSetList(new SetList()); // WHERE a = ? AND c = ? WhereExpression expn = new WhereExpression(); BinaryExpressionNode and = new BinaryExpressionNode(expn.getRoot()); @@ -152,10 +158,11 @@ @Test public void canPatchBasicWhereEquals() throws IllegalPatchException { // create the parsedStatementImpl we are going to use - ParsedStatementImpl<Pojo> parsedStmt = new ParsedStatementImpl<>(statement); + ParsedStatementImpl<Pojo> parsedStmt = new ParsedStatementImpl<>(statement, null); SuffixExpression suffixExpn = new SuffixExpression(); suffixExpn.setLimitExpn(null); suffixExpn.setSortExpn(null); + parsedStmt.setSetList(new SetList()); // WHERE a = ? WhereExpression expn = new WhereExpression(); BinaryExpressionNode and = new BinaryExpressionNode(expn.getRoot()); @@ -215,10 +222,11 @@ @Test public void canPatchBasicWhereEqualsLHSKeyAndRHSValue() throws IllegalPatchException { // create the parsedStatementImpl we are going to use - ParsedStatementImpl<Pojo> parsedStmt = new ParsedStatementImpl<>(statement); + ParsedStatementImpl<Pojo> parsedStmt = new ParsedStatementImpl<>(statement, null); SuffixExpression suffixExpn = new SuffixExpression(); suffixExpn.setLimitExpn(null); suffixExpn.setSortExpn(null); + parsedStmt.setSetList(new SetList()); // WHERE ?s = ?b WhereExpression expn = new WhereExpression(); BinaryExpressionNode and = new BinaryExpressionNode(expn.getRoot()); @@ -284,7 +292,8 @@ @Test public void canPatchBasicLimit() throws IllegalPatchException { // create the parsedStatementImpl we are going to use - ParsedStatementImpl<Pojo> parsedStmt = new ParsedStatementImpl<>(statement); + ParsedStatementImpl<Pojo> parsedStmt = new ParsedStatementImpl<>(statement, null); + parsedStmt.setSetList(new SetList()); SuffixExpression suffixExpn = new SuffixExpression(); LimitExpression limitExpnToPatch = new LimitExpression(); UnfinishedLimitValue unfinished = new UnfinishedLimitValue(); @@ -305,10 +314,221 @@ assertEquals(3, q.limitVal); } + private SetList buildSetList() { + // Build this set list, which corresponds to the TestPojo below + // SET 'writerId' = ?s , 'fooTimeStamp' = ?l + SetList setList = new SetList(); + SetListValue writerId = new SetListValue(); + TerminalNode writerKey = new TerminalNode(null); + writerKey.setValue(new Key<>("writerId")); + TerminalNode writerValue = new TerminalNode(null); + UnfinishedValueNode unfinishedWriter = new UnfinishedValueNode(); + unfinishedWriter.setParameterIndex(0); + unfinishedWriter.setType(String.class); + unfinishedWriter.setLHS(false); + writerValue.setValue(unfinishedWriter); + writerId.setKey(writerKey); + writerId.setValue(writerValue); + setList.addValue(writerId); + SetListValue fooTimeStamp = new SetListValue(); + TerminalNode timeStampKey = new TerminalNode(null); + timeStampKey.setValue(new Key<>("fooTimeStamp")); + fooTimeStamp.setKey(timeStampKey); + TerminalNode timeStampVal = new TerminalNode(null); + UnfinishedValueNode timeStampUnfinished = new UnfinishedValueNode(); + timeStampUnfinished.setLHS(false); + timeStampUnfinished.setParameterIndex(1); + timeStampUnfinished.setType(Long.class); + timeStampVal.setValue(timeStampUnfinished); + fooTimeStamp.setValue(timeStampVal); + setList.addValue(fooTimeStamp); + return setList; + } + + /* + * Test for patching of: + * "ADD something SET 'writerId' = ?s, 'fooTimeStamp' = ?l" + */ + @Test + public void canPatchBasicSetListAdd() throws IllegalPatchException { + // create the parsedStatementImpl we are going to use + DataModifyingStatement<TestPojo> stmt = new TestAdd(); + ParsedStatementImpl<TestPojo> parsedStmt = new ParsedStatementImpl<>(stmt, TestPojo.class); + SuffixExpression suffixExpn = new SuffixExpression(); + SetList setList = buildSetList(); + suffixExpn.setLimitExpn(null); + suffixExpn.setSortExpn(null); + suffixExpn.setWhereExpn(null); + parsedStmt.setSuffixExpression(suffixExpn); + assertEquals(2, setList.getValues().size()); + parsedStmt.setSetList(setList); + PreparedStatementImpl<Pojo> preparedStatement = new PreparedStatementImpl<>(2); + preparedStatement.setString(0, "foo-writer"); + preparedStatement.setLong(1, Long.MAX_VALUE); + PreparedParameter[] params = preparedStatement.getParams(); + // finally test the patching + Add<TestPojo> add = (Add<TestPojo>)parsedStmt.patchStatement(params); + assertTrue(add instanceof TestAdd); + TestAdd q = (TestAdd)add; + Pojo testPojo = q.pojo; + assertTrue(testPojo instanceof TestPojo); + TestPojo tPojo = (TestPojo)testPojo; + assertEquals("foo-writer", tPojo.getWriterId()); + assertEquals(Long.MAX_VALUE, tPojo.getFooTimeStamp()); + } + + /* + * Test for patching of: + * "REPLACE something SET ?s = 'foo-bar', 'fooTimeStamp' = ?l WHERE 'foo' = ?i" + */ + @Test + public void canPatchBasicSetListReplace() throws IllegalPatchException { + DataModifyingStatement<TestPojo> stmt = new TestReplace(); + ParsedStatementImpl<TestPojo> parsedStmt = new ParsedStatementImpl<>(stmt, TestPojo.class); + SuffixExpression suffixExpn = new SuffixExpression(); + + // Build this set list, which corresponds to the TestPojo below + // SET ?s = 'foo-bar' , 'fooTimeStamp' = ?l + SetList setList = new SetList(); + SetListValue writerId = new SetListValue(); + TerminalNode writerKey = new TerminalNode(null); + UnfinishedValueNode unfinishedWriterKey = new UnfinishedValueNode(); + unfinishedWriterKey.setParameterIndex(0); + unfinishedWriterKey.setType(String.class); + unfinishedWriterKey.setLHS(true); + writerKey.setValue(unfinishedWriterKey); + writerId.setKey(writerKey); + TerminalNode writerValue = new TerminalNode(null); + writerValue.setValue("foo-bar"); + writerId.setValue(writerValue); + setList.addValue(writerId); + SetListValue fooTimeStamp = new SetListValue(); + TerminalNode timeStampKey = new TerminalNode(null); + timeStampKey.setValue(new Key<>("fooTimeStamp")); + fooTimeStamp.setKey(timeStampKey); + TerminalNode timeStampVal = new TerminalNode(null); + UnfinishedValueNode timeStampUnfinished = new UnfinishedValueNode(); + timeStampUnfinished.setLHS(false); + timeStampUnfinished.setParameterIndex(1); + timeStampUnfinished.setType(Long.class); + timeStampVal.setValue(timeStampUnfinished); + fooTimeStamp.setValue(timeStampVal); + setList.addValue(fooTimeStamp); + suffixExpn.setLimitExpn(null); + suffixExpn.setSortExpn(null); + + // WHERE 'foo' = ?i + WhereExpression where = new WhereExpression(); + BinaryExpressionNode equals = new BinaryExpressionNode(where.getRoot()); + where.getRoot().setValue(equals); + equals.setOperator(BinaryComparisonOperator.EQUALS); + TerminalNode fooPatch = new TerminalNode(equals); + UnfinishedValueNode patch2 = new UnfinishedValueNode(); + patch2.setLHS(false); + patch2.setType(Integer.class); + patch2.setParameterIndex(2); + fooPatch.setValue(patch2); + TerminalNode foo = new TerminalNode(equals); + foo.setValue(new Key<>("foo")); + equals.setLeftChild(foo); + equals.setRightChild(fooPatch); + suffixExpn.setWhereExpn(where); + + parsedStmt.setSuffixExpression(suffixExpn); + assertEquals(2, setList.getValues().size()); + parsedStmt.setSetList(setList); + PreparedStatementImpl<Pojo> preparedStatement = new PreparedStatementImpl<>(3); + preparedStatement.setString(0, "writerId"); + preparedStatement.setLong(1, Long.MAX_VALUE); + preparedStatement.setInt(2, -400); + PreparedParameter[] params = preparedStatement.getParams(); + // finally test the patching + Replace<TestPojo> replace = (Replace<TestPojo>)parsedStmt.patchStatement(params); + assertTrue(replace instanceof TestReplace); + TestReplace q = (TestReplace)replace; + Pojo testPojo = q.pojo; + assertTrue(testPojo instanceof TestPojo); + TestPojo tPojo = (TestPojo)testPojo; + assertEquals("foo-bar", tPojo.getWriterId()); + assertEquals(Long.MAX_VALUE, tPojo.getFooTimeStamp()); + + ExpressionFactory factory = new ExpressionFactory(); + Expression expectedExpression = factory.equalTo(new Key<>("foo"), -400); + assertEquals(expectedExpression, q.where); + } + + /* + * Test for patching of: + * "UPDATE something SET 'writerId' = ?s WHERE 'foo' = ?i" + */ + @Test + public void canPatchBasicSetListUpdate() throws IllegalPatchException { + DataModifyingStatement<TestPojo> stmt = new TestUpdate(); + ParsedStatementImpl<TestPojo> parsedStmt = new ParsedStatementImpl<>(stmt, TestPojo.class); + SuffixExpression suffixExpn = new SuffixExpression(); + + // Build this set list, which corresponds to the TestPojo below + // SET 'writerId' = ?s + SetList setList = new SetList(); + SetListValue writerId = new SetListValue(); + TerminalNode writerKey = new TerminalNode(null); + writerKey.setValue(new Key<>("writerId")); + TerminalNode writerValue = new TerminalNode(null); + UnfinishedValueNode unfinishedWriterValue = new UnfinishedValueNode(); + unfinishedWriterValue.setParameterIndex(0); + unfinishedWriterValue.setType(String.class); + unfinishedWriterValue.setLHS(false); + writerValue.setValue(unfinishedWriterValue); + writerId.setKey(writerKey); + writerId.setValue(writerValue); + setList.addValue(writerId); + suffixExpn.setLimitExpn(null); + suffixExpn.setSortExpn(null); + + // WHERE 'foo' = ?i + WhereExpression where = new WhereExpression(); + BinaryExpressionNode equals = new BinaryExpressionNode(where.getRoot()); + where.getRoot().setValue(equals); + equals.setOperator(BinaryComparisonOperator.EQUALS); + TerminalNode fooPatch = new TerminalNode(equals); + UnfinishedValueNode patch2 = new UnfinishedValueNode(); + patch2.setLHS(false); + patch2.setType(Integer.class); + patch2.setParameterIndex(1); + fooPatch.setValue(patch2); + TerminalNode foo = new TerminalNode(equals); + foo.setValue(new Key<>("foo")); + equals.setLeftChild(foo); + equals.setRightChild(fooPatch); + suffixExpn.setWhereExpn(where); + + parsedStmt.setSuffixExpression(suffixExpn); + assertEquals(1, setList.getValues().size()); + parsedStmt.setSetList(setList); + PreparedStatementImpl<Pojo> preparedStatement = new PreparedStatementImpl<>(2); + preparedStatement.setString(0, "foobar-writer-id"); + preparedStatement.setInt(1, -400); + PreparedParameter[] params = preparedStatement.getParams(); + // finally test the patching + Update<TestPojo> replace = (Update<TestPojo>)parsedStmt.patchStatement(params); + assertTrue(replace instanceof TestUpdate); + TestUpdate q = (TestUpdate)replace; + List<Pair<Object, Object>> updates = q.updates; + assertEquals(1, updates.size()); + Pair<Object, Object> update = updates.get(0); + assertEquals(new Key<>("writerId"), update.getFirst()); + assertEquals("foobar-writer-id", update.getSecond()); + + ExpressionFactory factory = new ExpressionFactory(); + Expression expectedExpression = factory.equalTo(new Key<>("foo"), -400); + assertEquals(expectedExpression, q.where); + } + @Test public void canPatchBasicSort() throws IllegalPatchException { // create the parsedStatementImpl we are going to use - ParsedStatementImpl<Pojo> parsedStmt = new ParsedStatementImpl<>(statement); + ParsedStatementImpl<Pojo> parsedStmt = new ParsedStatementImpl<>(statement, null); + parsedStmt.setSetList(new SetList()); SuffixExpression suffixExpn = new SuffixExpression(); // SORT ? ASC, b DSC SortExpression sortExpn = new SortExpression(); @@ -349,9 +569,63 @@ } @Test + public void failPatchSetListAddWrongType() throws IllegalPatchException { + // create the parsedStatementImpl we are going to use + DataModifyingStatement<TestPojo> stmt = new TestAdd(); + ParsedStatementImpl<TestPojo> parsedStmt = new ParsedStatementImpl<>(stmt, TestPojo.class); + SuffixExpression suffixExpn = new SuffixExpression(); + SetList setList = buildSetList(); + suffixExpn.setLimitExpn(null); + suffixExpn.setSortExpn(null); + suffixExpn.setWhereExpn(null); + parsedStmt.setSuffixExpression(suffixExpn); + assertEquals(2, setList.getValues().size()); + parsedStmt.setSetList(setList); + // set the value for the one unfinished param + PreparedStatementImpl<Pojo> preparedStatement = new PreparedStatementImpl<>(2); + preparedStatement.setLong(0, -1); + preparedStatement.setString(1, "foobar"); + PreparedParameter[] params = preparedStatement.getParams(); + // this should fail since types don't match + try { + parsedStmt.patchStatement(params); + fail("Should have failed to patch, due to type mismatch"); + } catch (IllegalPatchException e) { + assertTrue(e.getMessage().contains("Expected " + String.class.getName())); + // pass + } + } + + @Test + public void failPatchSetListAddInsufficientParams() throws IllegalPatchException { + // create the parsedStatementImpl we are going to use + DataModifyingStatement<TestPojo> stmt = new TestAdd(); + ParsedStatementImpl<TestPojo> parsedStmt = new ParsedStatementImpl<>(stmt, TestPojo.class); + SuffixExpression suffixExpn = new SuffixExpression(); + SetList setList = buildSetList(); + suffixExpn.setLimitExpn(null); + suffixExpn.setSortExpn(null); + suffixExpn.setWhereExpn(null); + parsedStmt.setSuffixExpression(suffixExpn); + assertEquals(2, setList.getValues().size()); + parsedStmt.setSetList(setList); + // set the value for the one unfinished param + PreparedStatementImpl<Pojo> preparedStatement = new PreparedStatementImpl<>(0); + PreparedParameter[] params = preparedStatement.getParams(); + // this should fail since types don't match + try { + parsedStmt.patchStatement(params); + fail("Should have failed to patch, due to type mismatch"); + } catch (IllegalPatchException e) { + // pass + } + } + + @Test public void failPatchWithWrongType() throws IllegalPatchException { // create the parsedStatementImpl we are going to use - ParsedStatementImpl<Pojo> parsedStmt = new ParsedStatementImpl<>(statement); + ParsedStatementImpl<Pojo> parsedStmt = new ParsedStatementImpl<>(statement, null); + parsedStmt.setSetList(new SetList()); SuffixExpression suffixExpn = new SuffixExpression(); suffixExpn.setLimitExpn(null); suffixExpn.setSortExpn(null); @@ -408,10 +682,11 @@ @Test public void failPatchBasicEqualsIfIndexOutofBounds() { // create the parsedStatementImpl we are going to use - ParsedStatementImpl<Pojo> parsedStmt = new ParsedStatementImpl<>(statement); + ParsedStatementImpl<Pojo> parsedStmt = new ParsedStatementImpl<>(statement, null); SuffixExpression suffixExpn = new SuffixExpression(); suffixExpn.setLimitExpn(null); suffixExpn.setSortExpn(null); + parsedStmt.setSetList(new SetList()); // WHERE a = ? WhereExpression expn = new WhereExpression(); BinaryExpressionNode and = new BinaryExpressionNode(expn.getRoot()); @@ -470,14 +745,100 @@ @Override public Cursor<Pojo> execute() { // Not implemented - return null; + throw new AssertionError(); } @Override public Expression getWhereExpression() { // Not implemented - return null; + throw new AssertionError(); + } + + } + + private static class TestAdd implements Add<TestPojo> { + + private Pojo pojo; + + @Override + public void setPojo(Pojo pojo) { + this.pojo = pojo; + } + + @Override + public int apply() { + // not implemented + throw new AssertionError(); + } + + } + + private static class TestReplace implements Replace<TestPojo> { + + private Expression where; + private Pojo pojo; + + @Override + public void setPojo(Pojo pojo) { + this.pojo = pojo; + } + + @Override + public void where(Expression expression) { + where = expression; + } + + @Override + public int apply() { + // not implemented + throw new AssertionError(); } } + + private static class TestUpdate implements Update<TestPojo> { + + private Expression where; + private List<Pair<Object, Object>> updates = new ArrayList<>(); + + @Override + public void where(Expression expr) { + this.where = expr; + } + + @SuppressWarnings({ "rawtypes", "unchecked" }) + @Override + public <S> void set(Key<S> key, S value) { + Pair update = new Pair<>(key, value); + updates.add(update); + } + + @Override + public int apply() { + // not implemented + throw new AssertionError(); + } + + } + + public static class TestPojo implements Pojo { + + private String writerId; + private long fooTimeStamp; + + public String getWriterId() { + return writerId; + } + public void setWriterId(String writerId) { + this.writerId = writerId; + } + public long getFooTimeStamp() { + return fooTimeStamp; + } + public void setFooTimeStamp(long fooTimeStamp) { + this.fooTimeStamp = fooTimeStamp; + } + + + } }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/storage/core/src/test/java/com/redhat/thermostat/storage/internal/statement/PatchedSetListPojoConverterTest.java Tue Sep 03 14:19:10 2013 +0200 @@ -0,0 +1,105 @@ +/* + * Copyright 2012, 2013 Red Hat, Inc. + * + * This file is part of Thermostat. + * + * Thermostat is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published + * by the Free Software Foundation; either version 2, or (at your + * option) any later version. + * + * Thermostat 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 + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Thermostat; see the file COPYING. If not see + * <http://www.gnu.org/licenses/>. + * + * Linking this code with other modules is making a combined work + * based on this code. Thus, the terms and conditions of the GNU + * General Public License cover the whole combination. + * + * As a special exception, the copyright holders of this code give + * you permission to link this code with independent modules to + * produce an executable, regardless of the license terms of these + * independent modules, and to copy and distribute the resulting + * executable under terms of your choice, provided that you also + * meet, for each linked independent module, the terms and conditions + * of the license of that module. An independent module is a module + * which is not derived from or based on this code. If you modify + * this code, you may extend this exception to your version of the + * library, but you are not obligated to do so. If you do not wish + * to do so, delete this exception statement from your version. + */ + +package com.redhat.thermostat.storage.internal.statement; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import com.redhat.thermostat.storage.core.Key; +import com.redhat.thermostat.storage.internal.statement.PatchedSetListPojoConverter.IllegalPojoException; +import com.redhat.thermostat.storage.model.Pojo; + +public class PatchedSetListPojoConverterTest { + + @Test + public void testBasicConversion() throws IllegalPojoException { + PatchedSetListMember mem1 = new PatchedSetListMember(new Key<>("foo"), "foo-val"); + PatchedSetListMember mem2 = new PatchedSetListMember(new Key<>("barKey"), Long.MAX_VALUE); + PatchedSetListMember[] members = new PatchedSetListMember[] { + mem1, + mem2 + }; + PatchedSetList setList = mock(PatchedSetList.class); + when(setList.getSetListMembers()).thenReturn(members); + PatchedSetListPojoConverter<TestMe> converter = new PatchedSetListPojoConverter<>(setList, TestMe.class); + TestMe instance = converter.convertToPojo(); + assertEquals("foo-val", instance.getFoo()); + assertEquals(Long.MAX_VALUE, instance.getBarKey()); + } + + @Test + public void testConversionFailBasic() { + PatchedSetListMember mem1 = new PatchedSetListMember(new Key<>("wrong-Prop"), "foo-val"); + PatchedSetListMember[] members = new PatchedSetListMember[] { + mem1 + }; + PatchedSetList setList = mock(PatchedSetList.class); + when(setList.getSetListMembers()).thenReturn(members); + PatchedSetListPojoConverter<TestMe> converter = new PatchedSetListPojoConverter<>(setList, TestMe.class); + try { + converter.convertToPojo(); + fail("Should not convert, property not present in Pojo"); + } catch (IllegalPojoException e) { + // pass + assertTrue(e.getMessage().contains("Property wrong-Prop not found in Pojo:")); + } + } + + public static class TestMe implements Pojo { + + private String foo; + private long barKey; + + public String getFoo() { + return foo; + } + public void setFoo(String foo) { + this.foo = foo; + } + public long getBarKey() { + return barKey; + } + public void setBarKey(long barKey) { + this.barKey = barKey; + } + } +}
--- a/storage/core/src/test/java/com/redhat/thermostat/storage/internal/statement/PreparedStatementImplTest.java Mon Sep 30 16:38:50 2013 +0200 +++ b/storage/core/src/test/java/com/redhat/thermostat/storage/internal/statement/PreparedStatementImplTest.java Tue Sep 03 14:19:10 2013 +0200 @@ -37,20 +37,30 @@ package com.redhat.thermostat.storage.internal.statement; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +import java.util.ArrayList; +import java.util.List; + import org.junit.Test; +import com.redhat.thermostat.common.Pair; +import com.redhat.thermostat.storage.core.Add; import com.redhat.thermostat.storage.core.BackingStorage; import com.redhat.thermostat.storage.core.Category; import com.redhat.thermostat.storage.core.Cursor; import com.redhat.thermostat.storage.core.Key; +import com.redhat.thermostat.storage.core.PreparedStatement; import com.redhat.thermostat.storage.core.Query; +import com.redhat.thermostat.storage.core.Remove; +import com.redhat.thermostat.storage.core.Replace; import com.redhat.thermostat.storage.core.StatementDescriptor; import com.redhat.thermostat.storage.core.StatementExecutionException; +import com.redhat.thermostat.storage.core.Update; import com.redhat.thermostat.storage.model.Pojo; import com.redhat.thermostat.storage.query.BinaryComparisonExpression; import com.redhat.thermostat.storage.query.BinaryComparisonOperator; @@ -95,7 +105,7 @@ String queryString = "QUERY foo WHERE 'a' = ?s"; @SuppressWarnings("unchecked") StatementDescriptor<Pojo> desc = (StatementDescriptor<Pojo>) mock(StatementDescriptor.class); - when(desc.getQueryDescriptor()).thenReturn(queryString); + when(desc.getDescriptor()).thenReturn(queryString); @SuppressWarnings("unchecked") Category<Pojo> mockCategory = (Category<Pojo>) mock(Category.class); when(desc.getCategory()).thenReturn(mockCategory); @@ -115,11 +125,144 @@ } @Test + public void canDoParsingPatchingAndExecutionForAdd() throws Exception { + String addString = "ADD foo-table SET 'foo' = ?s"; + @SuppressWarnings("unchecked") + StatementDescriptor<FooPojo> desc = (StatementDescriptor<FooPojo>) mock(StatementDescriptor.class); + when(desc.getDescriptor()).thenReturn(addString); + @SuppressWarnings("unchecked") + Category<FooPojo> mockCategory = (Category<FooPojo>) mock(Category.class); + when(desc.getCategory()).thenReturn(mockCategory); + when(mockCategory.getDataClass()).thenReturn(FooPojo.class); + when(mockCategory.getName()).thenReturn("foo-table"); + BackingStorage storage = mock(BackingStorage.class); + TestAdd add = new TestAdd(); + when(storage.createAdd(mockCategory)).thenReturn(add); + PreparedStatement<FooPojo> preparedStatement = new PreparedStatementImpl<FooPojo>(storage, desc); + preparedStatement.setString(0, "foo-val"); + assertFalse(add.executed); + try { + // this should call add.apply(); + preparedStatement.execute(); + } catch (StatementExecutionException e) { + fail(e.getMessage()); + } + assertTrue(add.pojo != null); + assertTrue(add.pojo instanceof FooPojo); + assertTrue(add.executed); + FooPojo fooPojo = (FooPojo)add.pojo; + assertEquals("foo-val", fooPojo.getFoo()); + } + + @Test + public void canDoParsingPatchingAndExecutionForUpdate() throws Exception { + String addString = "UPDATE foo-table SET 'foo' = ?s WHERE 'foo' = ?s"; + @SuppressWarnings("unchecked") + StatementDescriptor<FooPojo> desc = (StatementDescriptor<FooPojo>) mock(StatementDescriptor.class); + when(desc.getDescriptor()).thenReturn(addString); + @SuppressWarnings("unchecked") + Category<FooPojo> mockCategory = (Category<FooPojo>) mock(Category.class); + when(desc.getCategory()).thenReturn(mockCategory); + when(mockCategory.getDataClass()).thenReturn(FooPojo.class); + when(mockCategory.getName()).thenReturn("foo-table"); + BackingStorage storage = mock(BackingStorage.class); + TestUpdate update = new TestUpdate(); + when(storage.createUpdate(mockCategory)).thenReturn(update); + PreparedStatement<FooPojo> preparedStatement = new PreparedStatementImpl<FooPojo>(storage, desc); + preparedStatement.setString(0, "foo-val"); + preparedStatement.setString(1, "nice"); + assertFalse(update.executed); + try { + // this should call apply(); + preparedStatement.execute(); + } catch (StatementExecutionException e) { + fail(e.getMessage()); + } + assertTrue(update.executed); + assertEquals(1, update.updates.size()); + Pair<Key<Object>, Object> item = update.updates.get(0); + assertEquals(new Key<>("foo"), item.getFirst()); + assertEquals("foo-val", item.getSecond()); + LiteralExpression<Key<String>> o1 = new LiteralExpression<>(new Key<String>("foo")); + LiteralExpression<String> o2 = new LiteralExpression<>("nice"); + BinaryComparisonExpression<String> binComp = new BinaryComparisonExpression<>( + o1, BinaryComparisonOperator.EQUALS, o2); + assertEquals(binComp, update.where); + } + + @Test + public void canDoParsingPatchingAndExecutionForReplace() throws Exception { + String addString = "REPLACE foo-table SET 'foo' = ?s WHERE 'foo' = ?s"; + @SuppressWarnings("unchecked") + StatementDescriptor<FooPojo> desc = (StatementDescriptor<FooPojo>) mock(StatementDescriptor.class); + when(desc.getDescriptor()).thenReturn(addString); + @SuppressWarnings("unchecked") + Category<FooPojo> mockCategory = (Category<FooPojo>) mock(Category.class); + when(desc.getCategory()).thenReturn(mockCategory); + when(mockCategory.getDataClass()).thenReturn(FooPojo.class); + when(mockCategory.getName()).thenReturn("foo-table"); + BackingStorage storage = mock(BackingStorage.class); + TestReplace replace = new TestReplace(); + when(storage.createReplace(mockCategory)).thenReturn(replace); + PreparedStatement<FooPojo> preparedStatement = new PreparedStatementImpl<FooPojo>(storage, desc); + preparedStatement.setString(0, "foo-val"); + preparedStatement.setString(1, "bar"); + assertFalse(replace.executed); + try { + // this should call apply(); + preparedStatement.execute(); + } catch (StatementExecutionException e) { + fail(e.getMessage()); + } + assertTrue(replace.pojo != null); + assertTrue(replace.pojo instanceof FooPojo); + assertTrue(replace.executed); + FooPojo fooPojo = (FooPojo)replace.pojo; + assertEquals("foo-val", fooPojo.getFoo()); + LiteralExpression<Key<String>> o1 = new LiteralExpression<>(new Key<String>("foo")); + LiteralExpression<String> o2 = new LiteralExpression<>("bar"); + BinaryComparisonExpression<String> binComp = new BinaryComparisonExpression<>( + o1, BinaryComparisonOperator.EQUALS, o2); + assertEquals(binComp, replace.where); + } + + @Test + public void canDoParsingPatchingAndExecutionForRemove() throws Exception { + String addString = "REMOVE foo-table WHERE 'fooRem' = ?s"; + @SuppressWarnings("unchecked") + StatementDescriptor<FooPojo> desc = (StatementDescriptor<FooPojo>) mock(StatementDescriptor.class); + when(desc.getDescriptor()).thenReturn(addString); + @SuppressWarnings("unchecked") + Category<FooPojo> mockCategory = (Category<FooPojo>) mock(Category.class); + when(desc.getCategory()).thenReturn(mockCategory); + when(mockCategory.getDataClass()).thenReturn(FooPojo.class); + when(mockCategory.getName()).thenReturn("foo-table"); + BackingStorage storage = mock(BackingStorage.class); + TestRemove remove = new TestRemove(); + when(storage.createRemove(mockCategory)).thenReturn(remove); + PreparedStatement<FooPojo> preparedStatement = new PreparedStatementImpl<FooPojo>(storage, desc); + preparedStatement.setString(0, "bar"); + assertFalse(remove.executed); + try { + // this should call apply(); + preparedStatement.execute(); + } catch (StatementExecutionException e) { + fail(e.getMessage()); + } + assertTrue(remove.executed); + LiteralExpression<Key<String>> o1 = new LiteralExpression<>(new Key<String>("fooRem")); + LiteralExpression<String> o2 = new LiteralExpression<>("bar"); + BinaryComparisonExpression<String> binComp = new BinaryComparisonExpression<>( + o1, BinaryComparisonOperator.EQUALS, o2); + assertEquals(binComp, remove.where); + } + + @Test public void failExecutionWithWronglyTypedParams() throws Exception { String queryString = "QUERY foo WHERE 'a' = ?b"; @SuppressWarnings("unchecked") StatementDescriptor<Pojo> desc = (StatementDescriptor<Pojo>) mock(StatementDescriptor.class); - when(desc.getQueryDescriptor()).thenReturn(queryString); + when(desc.getDescriptor()).thenReturn(queryString); @SuppressWarnings("unchecked") Category<Pojo> mockCategory = (Category<Pojo>) mock(Category.class); when(desc.getCategory()).thenReturn(mockCategory); @@ -138,6 +281,105 @@ } } + private static class TestAdd implements Add<FooPojo> { + + private Pojo pojo; + private boolean executed = false; + + @Override + public void setPojo(Pojo pojo) { + this.pojo = pojo; + } + + @Override + public int apply() { + executed = true; + return 0; + } + + } + + private static class TestReplace implements Replace<FooPojo> { + + private Pojo pojo; + private boolean executed = false; + private Expression where; + + @Override + public void setPojo(Pojo pojo) { + this.pojo = pojo; + } + + @Override + public void where(Expression expression) { + this.where = expression; + } + + @Override + public int apply() { + this.executed = true; + return 0; + } + + } + + private static class TestUpdate implements Update<FooPojo> { + + private Expression where; + private List<Pair<Key<Object>, Object>> updates = new ArrayList<>(); + private boolean executed = false; + + @Override + public void where(Expression expr) { + this.where = expr; + } + + @Override + public <S> void set(Key<S> key, S value) { + @SuppressWarnings("unchecked") + Pair<Key<Object>, Object> item = new Pair<>((Key<Object>) key, (Object)value); + updates.add(item); + } + + @Override + public int apply() { + this.executed = true; + return 0; + } + + } + + private static class TestRemove implements Remove<FooPojo> { + + private Expression where; + private boolean executed = false; + + @Override + public void where(Expression where) { + this.where = where; + } + + @Override + public int apply() { + this.executed = true; + return 0; + } + + } + + public static class FooPojo implements Pojo { + + String foo; + + public void setFoo(String foo) { + this.foo = foo; + } + + public String getFoo() { + return this.foo; + } + } + private static class StubQuery implements Query<Pojo> { private Expression expr;
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/storage/core/src/test/java/com/redhat/thermostat/storage/internal/statement/SetListValueTest.java Tue Sep 03 14:19:10 2013 +0200 @@ -0,0 +1,79 @@ +/* + * Copyright 2012, 2013 Red Hat, Inc. + * + * This file is part of Thermostat. + * + * Thermostat is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published + * by the Free Software Foundation; either version 2, or (at your + * option) any later version. + * + * Thermostat 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 + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Thermostat; see the file COPYING. If not see + * <http://www.gnu.org/licenses/>. + * + * Linking this code with other modules is making a combined work + * based on this code. Thus, the terms and conditions of the GNU + * General Public License cover the whole combination. + * + * As a special exception, the copyright holders of this code give + * you permission to link this code with independent modules to + * produce an executable, regardless of the license terms of these + * independent modules, and to copy and distribute the resulting + * executable under terms of your choice, provided that you also + * meet, for each linked independent module, the terms and conditions + * of the license of that module. An independent module is a module + * which is not derived from or based on this code. If you modify + * this code, you may extend this exception to your version of the + * library, but you are not obligated to do so. If you do not wish + * to do so, delete this exception statement from your version. + */ + +package com.redhat.thermostat.storage.internal.statement; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import org.junit.Test; + +public class SetListValueTest { + + @Test + public void testEqualsHashCodeEmpty() { + SetListValue value = new SetListValue(); + assertTrue(value.equals(value)); + SetListValue value2 = new SetListValue(); + assertTrue(value.equals(value2)); + assertEquals(value.hashCode(), value2.hashCode()); + } + + @Test + public void testEqualsHashCodeNonEmpty() { + TerminalNode keyNode = new TerminalNode(null); + keyNode.setValue("foo"); + TerminalNode valNode = new TerminalNode(null); + valNode.setValue("bar-val"); + SetListValue value = new SetListValue(); + value.setKey(keyNode); + value.setValue(valNode); + assertTrue(value.equals(value)); + SetListValue value2 = new SetListValue(); + TerminalNode keyNode2 = new TerminalNode(null); + keyNode2.setValue("foo"); + TerminalNode valNode2 = new TerminalNode(null); + valNode2.setValue("bar-val"); + value2.setKey(keyNode2); + value2.setValue(valNode2); + assertEquals(valNode, valNode2); + assertEquals(keyNode, keyNode2); + assertTrue(value.equals(value2)); + assertEquals(value.hashCode(), value2.hashCode()); + assertFalse(value.equals("something-else")); + } +}
--- a/storage/core/src/test/java/com/redhat/thermostat/storage/internal/statement/StatementDescriptorParserTest.java Mon Sep 30 16:38:50 2013 +0200 +++ b/storage/core/src/test/java/com/redhat/thermostat/storage/internal/statement/StatementDescriptorParserTest.java Tue Sep 03 14:19:10 2013 +0200 @@ -41,7 +41,6 @@ import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; -import static org.mockito.Matchers.any; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -53,32 +52,23 @@ import org.junit.Test; import org.mockito.ArgumentCaptor; +import com.redhat.thermostat.storage.core.Add; import com.redhat.thermostat.storage.core.AggregateQuery; +import com.redhat.thermostat.storage.core.AggregateQuery.AggregateFunction; import com.redhat.thermostat.storage.core.BackingStorage; import com.redhat.thermostat.storage.core.Category; import com.redhat.thermostat.storage.core.CategoryAdapter; import com.redhat.thermostat.storage.core.DescriptorParsingException; import com.redhat.thermostat.storage.core.Key; import com.redhat.thermostat.storage.core.Query; -import com.redhat.thermostat.storage.core.AggregateQuery.AggregateFunction; import com.redhat.thermostat.storage.core.Query.SortDirection; +import com.redhat.thermostat.storage.core.Remove; +import com.redhat.thermostat.storage.core.Replace; import com.redhat.thermostat.storage.core.StatementDescriptor; +import com.redhat.thermostat.storage.core.Update; import com.redhat.thermostat.storage.dao.AgentInfoDAO; import com.redhat.thermostat.storage.model.AgentInformation; import com.redhat.thermostat.storage.model.AggregateCount; -import com.redhat.thermostat.storage.internal.statement.BinaryExpressionNode; -import com.redhat.thermostat.storage.internal.statement.LimitExpression; -import com.redhat.thermostat.storage.internal.statement.NotBooleanExpressionNode; -import com.redhat.thermostat.storage.internal.statement.ParsedStatementImpl; -import com.redhat.thermostat.storage.internal.statement.SortExpression; -import com.redhat.thermostat.storage.internal.statement.SortMember; -import com.redhat.thermostat.storage.internal.statement.StatementDescriptorParser; -import com.redhat.thermostat.storage.internal.statement.SuffixExpression; -import com.redhat.thermostat.storage.internal.statement.TerminalNode; -import com.redhat.thermostat.storage.internal.statement.UnfinishedLimitValue; -import com.redhat.thermostat.storage.internal.statement.UnfinishedSortKey; -import com.redhat.thermostat.storage.internal.statement.UnfinishedValueNode; -import com.redhat.thermostat.storage.internal.statement.WhereExpression; import com.redhat.thermostat.storage.query.BinaryComparisonOperator; import com.redhat.thermostat.storage.query.BinaryLogicalOperator; @@ -87,19 +77,39 @@ private BackingStorage storage; private Query<AgentInformation> mockQuery; private StatementDescriptorParser<AgentInformation> parser; + private Add<AgentInformation> mockAdd; + private Update<AgentInformation> mockUpdate; + private Replace<AgentInformation> mockReplace; + private Remove<AgentInformation> mockRemove; @SuppressWarnings("unchecked") @Before public void setup() { storage = mock(BackingStorage.class); mockQuery = mock(Query.class); - when(storage.createQuery(any(AgentInfoDAO.CATEGORY.getClass()))).thenReturn(mockQuery); + when(storage.createQuery(eq(AgentInfoDAO.CATEGORY))).thenReturn(mockQuery); + // setup for ADD + mockAdd = mock(Add.class); + when(storage.createAdd(eq(AgentInfoDAO.CATEGORY))).thenReturn(mockAdd); + // setup for UPDATE + mockUpdate = mock(Update.class); + when(storage.createUpdate(eq(AgentInfoDAO.CATEGORY))).thenReturn(mockUpdate); + // setup for REMOVE + mockRemove = mock(Remove.class); + when(storage.createRemove(eq(AgentInfoDAO.CATEGORY))).thenReturn(mockRemove); + // setup for REPLACE + mockReplace = mock(Replace.class); + when(storage.createReplace(eq(AgentInfoDAO.CATEGORY))).thenReturn(mockReplace); } @After public void teardown() { storage = null; mockQuery = null; + mockUpdate = null; + mockReplace = null; + mockAdd = null; + mockRemove = null; } @SuppressWarnings({ "rawtypes", "unchecked" }) @@ -1270,6 +1280,124 @@ } @Test + public void testParseAddBasic() throws DescriptorParsingException { + String descString = "ADD " + AgentInfoDAO.CATEGORY.getName() + " SET 'a' = 'b' , 'c' = ?s"; + StatementDescriptor<AgentInformation> desc = new StatementDescriptor<>(AgentInfoDAO.CATEGORY, descString); + parser = new StatementDescriptorParser<>(storage, desc); + ParsedStatementImpl<AgentInformation> statement = null; + try { + statement = (ParsedStatementImpl<AgentInformation>)parser.parse(); + } catch (DescriptorParsingException e) { + e.printStackTrace(); + fail(e.getMessage()); + } + assertTrue(statement.getRawStatement() instanceof Add); + SuffixExpression suffix = statement.getSuffixExpression(); + assertNotNull(suffix); + WhereExpression where = suffix.getWhereExpn(); + LimitExpression limit = suffix.getLimitExpn(); + SortExpression sort = suffix.getSortExpn(); + assertNull(where); + assertNull(limit); + assertNull(sort); + SetList setList = statement.getSetList(); + assertNotNull(setList); + List<SetListValue> valuesList = setList.getValues(); + assertEquals(2, valuesList.size()); + SetListValue first = valuesList.get(0); + SetListValue second = valuesList.get(1); + TerminalNode a = new TerminalNode(null); + a.setValue(new Key<>("a")); + TerminalNode b = new TerminalNode(null); + b.setValue("b"); + SetListValue firstExpected = new SetListValue(); + firstExpected.setKey(a); + firstExpected.setValue(b); + assertEquals(firstExpected, first); + TerminalNode c = new TerminalNode(null); + c.setValue(new Key<>("c")); + UnfinishedValueNode dVal = new UnfinishedValueNode(); + dVal.setType(String.class); + dVal.setLHS(false); + dVal.setParameterIndex(0); + TerminalNode d = new TerminalNode(null); + d.setValue(dVal); + SetListValue secondExpected = new SetListValue(); + secondExpected.setKey(c); + secondExpected.setValue(d); + assertEquals(secondExpected, second); + } + + @Test + public void testParseUpdateBasic() throws DescriptorParsingException { + String descString = "UPDATE " + AgentInfoDAO.CATEGORY.getName() + " SET 'a' = 'b' , 'c' = ?s WHERE 'foo' != ?i"; + StatementDescriptor<AgentInformation> desc = new StatementDescriptor<>(AgentInfoDAO.CATEGORY, descString); + parser = new StatementDescriptorParser<>(storage, desc); + ParsedStatementImpl<AgentInformation> statement = null; + try { + statement = (ParsedStatementImpl<AgentInformation>)parser.parse(); + } catch (DescriptorParsingException e) { + e.printStackTrace(); + fail(e.getMessage()); + } + assertTrue(statement.getRawStatement() instanceof Update); + SuffixExpression suffix = statement.getSuffixExpression(); + assertNotNull(suffix); + WhereExpression where = suffix.getWhereExpn(); + LimitExpression limit = suffix.getLimitExpn(); + SortExpression sort = suffix.getSortExpn(); + assertNotNull(where); + assertNull(limit); + assertNull(sort); + SetList setList = statement.getSetList(); + assertNotNull(setList); + List<SetListValue> valuesList = setList.getValues(); + assertEquals(2, valuesList.size()); + SetListValue first = valuesList.get(0); + SetListValue second = valuesList.get(1); + TerminalNode a = new TerminalNode(null); + a.setValue(new Key<>("a")); + TerminalNode b = new TerminalNode(null); + b.setValue("b"); + SetListValue firstExpected = new SetListValue(); + firstExpected.setKey(a); + firstExpected.setValue(b); + assertEquals(firstExpected, first); + TerminalNode c = new TerminalNode(null); + c.setValue(new Key<>("c")); + UnfinishedValueNode dVal = new UnfinishedValueNode(); + dVal.setType(String.class); + dVal.setLHS(false); + dVal.setParameterIndex(0); + TerminalNode d = new TerminalNode(null); + d.setValue(dVal); + SetListValue secondExpected = new SetListValue(); + secondExpected.setKey(c); + secondExpected.setValue(d); + assertEquals(secondExpected, second); + // Build expected where expn + WhereExpression expectedWhere = new WhereExpression(); + BinaryExpressionNode equality = new BinaryExpressionNode(expectedWhere.getRoot()); + expectedWhere.getRoot().setValue(equality); + equality.setOperator(BinaryComparisonOperator.NOT_EQUAL_TO); + TerminalNode foo = new TerminalNode(equality); + @SuppressWarnings("rawtypes") + Key fooKey = new Key("foo"); + foo.setValue(fooKey); + equality.setLeftChild(foo); + TerminalNode fooVal = new TerminalNode(equality); + UnfinishedValueNode node = new UnfinishedValueNode(); + node.setParameterIndex(1); + node.setLHS(false); + node.setType(Integer.class); + fooVal.setValue(node); + equality.setRightChild(fooVal); + assertTrue(WhereExpressions.equals(expectedWhere, where)); + } + + // TODO: Add basic parse tests for REMOVE/REPLACE + + @Test public void rejectLongValAsIntType() throws DescriptorParsingException { // 30000000003 > Integer.MAX_VALUE; needs to be preceded by 'l/L' String descrString = "QUERY " + AgentInfoDAO.CATEGORY.getName() + " WHERE 'a' != 30000000003"; @@ -1482,4 +1610,153 @@ } } + @Test + public void rejectAddWithInvalidSetList() throws DescriptorParsingException { + String descString = "ADD " + AgentInfoDAO.CATEGORY.getName() + " SET 'a' = , 'c' = 'd'"; + StatementDescriptor<AgentInformation> desc = new StatementDescriptor<>(AgentInfoDAO.CATEGORY, descString); + parser = new StatementDescriptorParser<>(storage, desc); + try { + parser.parse(); + fail("Invalid SET values list."); + } catch (DescriptorParsingException e) { + // pass + assertEquals("Illegal terminal type. Token was ->,<-", e.getMessage()); + } + } + + @Test + public void rejectAddWithWhere() throws DescriptorParsingException { + String descString = "ADD " + AgentInfoDAO.CATEGORY.getName() + " SET 'a' = 'b' , 'c' = 'd' WHERE 'a' = 'b'"; + StatementDescriptor<AgentInformation> desc = new StatementDescriptor<>(AgentInfoDAO.CATEGORY, descString); + parser = new StatementDescriptorParser<>(storage, desc); + try { + parser.parse(); + fail("ADD operation does not support WHERE clauses!"); + } catch (DescriptorParsingException e) { + // pass + assertTrue(e.getMessage().contains("WHERE clause not allowed for ADD")); + } + } + + @Test + public void rejectReplaceWithoutWhere() throws DescriptorParsingException { + String descString = "REPLACE " + AgentInfoDAO.CATEGORY.getName() + " SET 'a' = 'b' , 'c' = 'd'"; + StatementDescriptor<AgentInformation> desc = new StatementDescriptor<>(AgentInfoDAO.CATEGORY, descString); + parser = new StatementDescriptorParser<>(storage, desc); + try { + parser.parse(); + fail("REPLACE operation requires WHERE clause, and should not parse!"); + } catch (DescriptorParsingException e) { + // pass + assertTrue(e.getMessage().contains("WHERE clause required for REPLACE")); + } + } + + @Test + public void rejectUpdateWithoutWhere() throws DescriptorParsingException { + String descString = "UPDATE " + AgentInfoDAO.CATEGORY.getName() + " SET 'a' = 'b' , 'c' = 'd'"; + StatementDescriptor<AgentInformation> desc = new StatementDescriptor<>(AgentInfoDAO.CATEGORY, descString); + parser = new StatementDescriptorParser<>(storage, desc); + try { + parser.parse(); + fail("UPDATE operation requires WHERE clause, and should not parse!"); + } catch (DescriptorParsingException e) { + // pass + assertTrue(e.getMessage().contains("WHERE clause required for UPDATE")); + } + } + + @Test + public void rejectRemoveWithSetList() throws DescriptorParsingException { + String descString = "REMOVE " + AgentInfoDAO.CATEGORY.getName() + " SET 'a' = 'b' WHERE 'a' = 'b'"; + StatementDescriptor<AgentInformation> desc = new StatementDescriptor<>(AgentInfoDAO.CATEGORY, descString); + parser = new StatementDescriptorParser<>(storage, desc); + try { + parser.parse(); + fail("REMOVE does not allow SET list, and should not parse!"); + } catch (DescriptorParsingException e) { + // pass + assertTrue(e.getMessage().contains("SET not allowed for REMOVE")); + } + } + + @Test + public void rejectQueryWithSetList() throws DescriptorParsingException { + String descString = "QUERY " + AgentInfoDAO.CATEGORY.getName() + " SET 'a' = 'b' WHERE 'a' = 'b'"; + StatementDescriptor<AgentInformation> desc = new StatementDescriptor<>(AgentInfoDAO.CATEGORY, descString); + parser = new StatementDescriptorParser<>(storage, desc); + try { + parser.parse(); + fail("QUERY does not allow SET list, and should not parse!"); + } catch (DescriptorParsingException e) { + // pass + assertTrue(e.getMessage().contains("SET not allowed for QUERY")); + } + } + + + private void doRejectWriteSortLimitTest(String descString, String failmsg) { + StatementDescriptor<AgentInformation> desc = new StatementDescriptor<>(AgentInfoDAO.CATEGORY, descString); + parser = new StatementDescriptorParser<>(storage, desc); + try { + parser.parse(); + fail(failmsg); + } catch (DescriptorParsingException e) { + // pass + assertEquals("LIMIT/SORT only allowed for QUERY/QUERY-COUNT", e.getMessage()); + } + } + + @Test + public void rejectWritesWithSortLimit() throws DescriptorParsingException { + // Update rejects + String descString = "UPDATE " + AgentInfoDAO.CATEGORY.getName() + " SET 'a' = 'b' WHERE 'a' = 'b' SORT 'a' DSC"; + String failmsg = "SORT in UPDATE is not allowed, and should not parse!"; + doRejectWriteSortLimitTest(descString, failmsg); + descString = "UPDATE " + AgentInfoDAO.CATEGORY.getName() + " SET 'a' = 'b' WHERE 'a' = 'b' LIMIT 1"; + failmsg = "LIMIT in UPDATE is not allowed, and should not parse!"; + doRejectWriteSortLimitTest(descString, failmsg); + + // Remove rejects + descString = "REMOVE " + AgentInfoDAO.CATEGORY.getName() + " WHERE 'a' = 'b' LIMIT 1"; + failmsg = "LIMIT in REMOVE is not allowed, and should not parse!"; + doRejectWriteSortLimitTest(descString, failmsg); + descString = "REMOVE " + AgentInfoDAO.CATEGORY.getName() + " WHERE 'a' = 'b' SORT 'a' ASC"; + failmsg = "SORT in REMOVE is not allowed, and should not parse!"; + doRejectWriteSortLimitTest(descString, failmsg); + + // Replace rejects + descString = "REPLACE " + AgentInfoDAO.CATEGORY.getName() + " SET 'a' = 'b' WHERE 'a' = 'b' LIMIT 1"; + failmsg = "LIMIT in REPLACE is not allowed, and should not parse!"; + doRejectWriteSortLimitTest(descString, failmsg); + descString = "REPLACE " + AgentInfoDAO.CATEGORY.getName() + " SET 'a' = 'b' WHERE 'a' = 'b' SORT 'a' ASC"; + failmsg = "SORT in REPLACE is not allowed, and should not parse!"; + doRejectWriteSortLimitTest(descString, failmsg); + + // Add rejects + descString = "ADD " + AgentInfoDAO.CATEGORY.getName() + " SET 'a' = 'b' LIMIT 1"; + failmsg = "LIMIT in ADD is not allowed, and should not parse!"; + doRejectWriteSortLimitTest(descString, failmsg); + descString = "ADD " + AgentInfoDAO.CATEGORY.getName() + " SET 'a' = 'b' SORT 'a' ASC"; + failmsg = "SORT in ADD is not allowed, and should not parse!"; + doRejectWriteSortLimitTest(descString, failmsg); + } + + @Test + public void rejectUpdateWithoutSet() throws DescriptorParsingException { + String descString = "UPDATE " + AgentInfoDAO.CATEGORY.getName() + " WHERE 'a' = 'b'"; + StatementDescriptor<AgentInformation> desc = new StatementDescriptor<>(AgentInfoDAO.CATEGORY, descString); + parser = new StatementDescriptorParser<>(storage, desc); + try { + parser.parse(); + fail("UPDATE requires SET list, and should not parse!"); + } catch (DescriptorParsingException e) { + // pass + assertEquals("SET list required for UPDATE", e.getMessage()); + } + } + + // TODO: add tests where set list does not match all props of Pojo class for + // add/replace + }
--- a/storage/mongo/pom.xml Mon Sep 30 16:38:50 2013 +0200 +++ b/storage/mongo/pom.xml Tue Sep 03 14:19:10 2013 +0200 @@ -91,18 +91,6 @@ <artifactId>mongo-java-driver</artifactId> </dependency> <dependency> - <groupId>commons-beanutils</groupId> - <artifactId>commons-beanutils</artifactId> - </dependency> - <dependency> - <groupId>commons-collections</groupId> - <artifactId>commons-collections</artifactId> - </dependency> - <dependency> - <groupId>commons-logging</groupId> - <artifactId>commons-logging</artifactId> - </dependency> - <dependency> <groupId>org.powermock</groupId> <artifactId>powermock-api-mockito</artifactId> <scope>test</scope>
--- a/web/client/src/main/java/com/redhat/thermostat/web/client/internal/WebStorage.java Mon Sep 30 16:38:50 2013 +0200 +++ b/web/client/src/main/java/com/redhat/thermostat/web/client/internal/WebStorage.java Tue Sep 03 14:19:10 2013 +0200 @@ -770,7 +770,7 @@ @Override public <T extends Pojo> PreparedStatement<T> prepareStatement(StatementDescriptor<T> desc) throws DescriptorParsingException { - String strDesc = desc.getQueryDescriptor(); + String strDesc = desc.getDescriptor(); int categoryId = getCategoryId(desc.getCategory()); NameValuePair nameParam = new BasicNameValuePair("query-descriptor", strDesc); @@ -788,7 +788,7 @@ // we've got a descriptor the endpoint doesn't know about or // refuses to accept for security reasons. String msg = "Unknown query descriptor which endpoint of " + WebStorage.class.getName() + " refused to accept!"; - throw new IllegalDescriptorException(msg, desc.getQueryDescriptor()); + throw new IllegalDescriptorException(msg, desc.getDescriptor()); } else if (statementId == WebPreparedStatementResponse.DESCRIPTOR_PARSE_FAILED) { String msg = "Statement descriptor failed to parse. " + "Please check server logs for details!";
--- a/web/server/src/main/java/com/redhat/thermostat/web/server/WebStorageEndPoint.java Mon Sep 30 16:38:50 2013 +0200 +++ b/web/server/src/main/java/com/redhat/thermostat/web/server/WebStorageEndPoint.java Tue Sep 03 14:19:10 2013 +0200 @@ -325,9 +325,9 @@ } StatementDescriptor<T> desc = new StatementDescriptor<>(cat, queryDescrParam); // Check if descriptor is trusted (i.e. known) - if (!knownStatementDescriptors.contains(desc.getQueryDescriptor())) { + if (!knownStatementDescriptors.contains(desc.getDescriptor())) { String msg = "Attempted to prepare a statement descriptor which we " + - "don't trust! Descriptor was: ->" + desc.getQueryDescriptor() + "<-"; + "don't trust! Descriptor was: ->" + desc.getDescriptor() + "<-"; logger.log(Level.WARNING, msg); response.setStatementId(WebPreparedStatementResponse.ILLEGAL_STATEMENT); writeResponse(resp, response, WebPreparedStatementResponse.class); @@ -682,8 +682,8 @@ } StatementDescriptor<T> desc = targetStmtHolder.getStatementDescriptor(); - StatementDescriptorMetadataFactory factory = descMetadataFactories.get(desc.getQueryDescriptor()); - DescriptorMetadata actualMetadata = factory.getDescriptorMetadata(desc.getQueryDescriptor(), params); + StatementDescriptorMetadataFactory factory = descMetadataFactories.get(desc.getDescriptor()); + DescriptorMetadata actualMetadata = factory.getDescriptorMetadata(desc.getDescriptor(), params); UserPrincipal userPrincipal = getUserPrincipal(req); targetQuery = getQueryForPrincipal(userPrincipal, targetQuery, desc, actualMetadata);