Mercurial > hg > release > thermostat-0.13
changeset 1179:03ed49a50413
Implement PreparedStatement in WebStorage (Part 1).
Reviewed-by: ebaron
Review-thread: http://icedtea.classpath.org/pipermail/thermostat/2013-July/007565.html
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/storage/core/src/main/java/com/redhat/thermostat/storage/core/IllegalDescriptorException.java Thu Jul 18 16:52:29 2013 +0200 @@ -0,0 +1,56 @@ +/* + * 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.core; + +/** + * Thrown if a prepared statement descriptor was refused by the storage endpoint. + */ +@SuppressWarnings("serial") +public class IllegalDescriptorException extends DescriptorParsingException { + + private final String failedDescriptor; + + public IllegalDescriptorException(String errorMsg, String strDescriptor) { + super(errorMsg); + this.failedDescriptor = strDescriptor; + } + + public String getFailedDescriptor() { + return failedDescriptor; + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/storage/core/src/main/java/com/redhat/thermostat/storage/core/IllegalPatchException.java Thu Jul 18 16:52:29 2013 +0200 @@ -0,0 +1,50 @@ +/* + * 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.core; + +/** + * Thrown if a prepared statement was attempted to patch, which resulted in + * NPEs or a type mismatch for some of the free variables. + * + */ +@SuppressWarnings("serial") +public class IllegalPatchException extends Exception { + + public IllegalPatchException(Throwable cause) { + super(cause); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/storage/core/src/main/java/com/redhat/thermostat/storage/core/ParsedStatement.java Thu Jul 18 16:52:29 2013 +0200 @@ -0,0 +1,71 @@ +/* + * 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.core; + +import com.redhat.thermostat.storage.model.Pojo; + +/** + * An intermediary representation of a {@link PreparedStatement}. + * + */ +public interface ParsedStatement<T extends Pojo> { + + /** + * Patches a statement, setting free variables. After patching, the + * statement is ready for execution. + * + * @param params + * The ordered list of values/types of free parameters. + * @return The statement ready for execution. + * @throws IllegalPatchException + * If the patching fails for some reason. + */ + Statement<T> patchStatement(PreparedParameter[] params) + throws IllegalPatchException; + + /** + * + * @return The number of free variables. + */ + int getNumParams(); + + /** + * + * @return The raw statement. + */ + Statement<T> getRawStatement(); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/storage/core/src/main/java/com/redhat/thermostat/storage/core/PreparedParameter.java Thu Jul 18 16:52:29 2013 +0200 @@ -0,0 +1,73 @@ +/* + * 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.core; + +/** + * Represents a prepared parameter. + * + * @see PreparedParameters + */ +public class PreparedParameter { + + private Object value; + private Class<?> type; + + PreparedParameter(Object value, Class<?> type) { + this.value = value; + this.type = type; + } + + public PreparedParameter() { + // nothing. Exists for serialization purposes. + } + + public Object getValue() { + return value; + } + + public void setValue(Object value) { + this.value = value; + } + + public Class<?> getType() { + return type; + } + + public void setType(Class<?> type) { + this.type = type; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/storage/core/src/main/java/com/redhat/thermostat/storage/core/PreparedParameters.java Thu Jul 18 16:52:29 2013 +0200 @@ -0,0 +1,88 @@ +/* + * 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.core; + +/** + * + * Shared class for setting prepared parameters. + * + */ +public final class PreparedParameters implements PreparedStatementSetter { + + private PreparedParameter[] params; + + public PreparedParameters(int numParams) { + params = new PreparedParameter[numParams]; + } + + @Override + public void setLong(int paramIndex, long paramValue) { + setType(paramIndex, paramValue, Long.class); + } + + @Override + public void setInt(int paramIndex, int paramValue) { + setType(paramIndex, paramValue, Integer.class); + } + + @Override + public void setStringList(int paramIndex, String[] paramValue) { + setType(paramIndex, paramValue, String[].class); + } + + @Override + public void setBoolean(int paramIndex, boolean paramValue) { + setType(paramIndex, paramValue, Boolean.class); + } + + @Override + public void setString(int paramIndex, String paramValue) { + setType(paramIndex, paramValue, String.class); + } + + private void setType(int paramIndex, Object paramValue, Class<?> paramType) { + if (paramIndex >= params.length) { + throw new IllegalArgumentException("Parameter index '" + paramIndex + "' out of range."); + } + PreparedParameter param = new PreparedParameter(paramValue, paramType); + params[paramIndex] = param; + } + + public PreparedParameter[] getParams() { + return params; + } +}
--- a/storage/core/src/main/java/com/redhat/thermostat/storage/core/PreparedStatement.java Fri Jul 19 16:37:43 2013 +0200 +++ b/storage/core/src/main/java/com/redhat/thermostat/storage/core/PreparedStatement.java Thu Jul 18 16:52:29 2013 +0200 @@ -11,7 +11,7 @@ * @see Storage#prepareStatement(StatementDescriptor) * */ -public interface PreparedStatement<T extends Pojo> { +public interface PreparedStatement<T extends Pojo> extends PreparedStatementSetter { void setBoolean(int paramIndex, boolean paramValue); @@ -44,10 +44,8 @@ Cursor<T> executeQuery() throws StatementExecutionException; /** - * - * @return The unique ID of this predefined statement for the underlying - * {@link Storage}. + * @return An intermediary representation of this prepared statement. */ - int getId(); - + ParsedStatement<T> getParsedStatement(); + }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/storage/core/src/main/java/com/redhat/thermostat/storage/core/PreparedStatementSetter.java Thu Jul 18 16:52:29 2013 +0200 @@ -0,0 +1,56 @@ +/* + * 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.core; + +/** + * Package private interface in order to ensure consistency of + * setters between {@link PreparedStatement} and {@link PreparedParamenters}. + * + */ +interface PreparedStatementSetter { + + void setBoolean(int paramIndex, boolean paramValue); + + void setLong(int paramIndex, long paramValue); + + void setInt(int paramIndex, int paramValue); + + void setString(int paramIndex, String paramValue); + + void setStringList(int paramIndex, String[] paramValue); + +}
--- a/storage/core/src/main/java/com/redhat/thermostat/storage/core/Query.java Fri Jul 19 16:37:43 2013 +0200 +++ b/storage/core/src/main/java/com/redhat/thermostat/storage/core/Query.java Thu Jul 18 16:52:29 2013 +0200 @@ -66,6 +66,8 @@ void limit(int n); Cursor<T> execute(); + + Expression getWhereExpression(); }
--- a/storage/core/src/main/java/com/redhat/thermostat/storage/internal/dao/AgentInfoDAOImpl.java Fri Jul 19 16:37:43 2013 +0200 +++ b/storage/core/src/main/java/com/redhat/thermostat/storage/internal/dao/AgentInfoDAOImpl.java Thu Jul 18 16:52:29 2013 +0200 @@ -189,6 +189,6 @@ update.set(CONFIG_LISTEN_ADDRESS, agentInfo.getConfigListenAddress()); update.apply(); } - + }
--- a/storage/core/src/main/java/com/redhat/thermostat/storage/internal/statement/BinaryExpressionNode.java Fri Jul 19 16:37:43 2013 +0200 +++ b/storage/core/src/main/java/com/redhat/thermostat/storage/internal/statement/BinaryExpressionNode.java Thu Jul 18 16:52:29 2013 +0200 @@ -38,7 +38,9 @@ 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.BinaryComparisonExpression; import com.redhat.thermostat.storage.query.BinaryComparisonOperator; import com.redhat.thermostat.storage.query.BinaryLogicalExpression;
--- a/storage/core/src/main/java/com/redhat/thermostat/storage/internal/statement/IllegalPatchException.java Fri Jul 19 16:37:43 2013 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,50 +0,0 @@ -/* - * 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; - -/** - * Thrown if a prepared statement was attempted to patch, which resulted in - * NPEs or a type mismatch for some of the free variables. - * - */ -@SuppressWarnings("serial") -class IllegalPatchException extends Exception { - - IllegalPatchException(Throwable cause) { - super(cause); - } -}
--- a/storage/core/src/main/java/com/redhat/thermostat/storage/internal/statement/LimitExpression.java Fri Jul 19 16:37:43 2013 +0200 +++ b/storage/core/src/main/java/com/redhat/thermostat/storage/internal/statement/LimitExpression.java Thu Jul 18 16:52:29 2013 +0200 @@ -36,6 +36,9 @@ package com.redhat.thermostat.storage.internal.statement; +import com.redhat.thermostat.storage.core.IllegalPatchException; +import com.redhat.thermostat.storage.core.PreparedParameter; + /** * Represents a limit expression in the prepared statement's parse tree. *
--- a/storage/core/src/main/java/com/redhat/thermostat/storage/internal/statement/Node.java Fri Jul 19 16:37:43 2013 +0200 +++ b/storage/core/src/main/java/com/redhat/thermostat/storage/internal/statement/Node.java Thu Jul 18 16:52:29 2013 +0200 @@ -38,6 +38,9 @@ import java.util.Objects; +import com.redhat.thermostat.storage.core.IllegalPatchException; +import com.redhat.thermostat.storage.core.PreparedParameter; + /** * A basic node in the prepared statement parse tree. *
--- a/storage/core/src/main/java/com/redhat/thermostat/storage/internal/statement/NotBooleanExpressionNode.java Fri Jul 19 16:37:43 2013 +0200 +++ b/storage/core/src/main/java/com/redhat/thermostat/storage/internal/statement/NotBooleanExpressionNode.java Thu Jul 18 16:52:29 2013 +0200 @@ -38,6 +38,8 @@ import java.util.Objects; +import com.redhat.thermostat.storage.core.IllegalPatchException; +import com.redhat.thermostat.storage.core.PreparedParameter; import com.redhat.thermostat.storage.query.ComparisonExpression; import com.redhat.thermostat.storage.query.UnaryLogicalExpression; import com.redhat.thermostat.storage.query.UnaryLogicalOperator;
--- a/storage/core/src/main/java/com/redhat/thermostat/storage/internal/statement/ParsedStatement.java Fri Jul 19 16:37:43 2013 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,156 +0,0 @@ -/* - * 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.Query; -import com.redhat.thermostat.storage.core.Statement; -import com.redhat.thermostat.storage.model.Pojo; -import com.redhat.thermostat.storage.query.Expression; - -/** - * Result object as returned by {@link StatementDescriptorParser#parse()}. - * An instance of this plus an {@link PreparedStatementImpl} instance should - * be sufficient to patch up a prepared statement with its real values. - * - * @see PreparedStatementImpl#executeQuery() - */ -class ParsedStatement<T extends Pojo> { - - private int numParams; - private SuffixExpression suffixExpn; - private final Statement<T> statement; - - ParsedStatement(Statement<T> statement) { - this.statement = statement; - } - - public int getNumParams() { - return numParams; - } - - public Statement<T> getRawStatement() { - return statement; - } - - public Statement<T> patchQuery(PreparedStatementImpl<T> stmt) throws IllegalPatchException { - if (suffixExpn == null) { - String msg = "Suffix expression must be set before patching!"; - IllegalStateException expn = new IllegalStateException(msg); - throw new IllegalPatchException(expn); - } - PreparedParameter[] params = stmt.getParams(); - patchWhere(params); - patchSort(params); - patchLimit(params); - // TODO count actual patches and throw an exception if not all vars - // have been patched up. - return statement; - } - - private void patchLimit(PreparedParameter[] params) throws IllegalPatchException { - LimitExpression expn = suffixExpn.getLimitExpn(); - if (expn == null) { - // no limit expn, nothing to do - return; - } - PatchedLimitExpression patchedExp = expn.patch(params); - if (statement instanceof Query) { - Query<T> query = (Query<T>) statement; - query.limit(patchedExp.getLimitValue()); - } else { - String msg = "Patching of non-query types not (yet) supported! Class was:" - + statement.getClass().getName(); - IllegalStateException invalid = new IllegalStateException(msg); - throw new IllegalPatchException(invalid); - } - } - - private void patchSort(PreparedParameter[] params) throws IllegalPatchException { - SortExpression expn = suffixExpn.getSortExpn(); - if (expn == null) { - // no sort expn, nothing to do - return; - } - PatchedSortExpression patchedExp = expn.patch(params); - if (statement instanceof Query) { - Query<T> query = (Query<T>) statement; - PatchedSortMember[] members = patchedExp.getSortMembers(); - for (int i = 0; i < members.length; i++) { - query.sort(members[i].getSortKey(), members[i].getDirection()); - } - } else { - String msg = "Patching of non-query types not (yet) supported! Class was:" - + statement.getClass().getName(); - IllegalStateException invalid = new IllegalStateException(msg); - throw new IllegalPatchException(invalid); - } - } - - private void patchWhere(PreparedParameter[] params) throws IllegalPatchException { - WhereExpression expn = suffixExpn.getWhereExpn(); - if (expn == null) { - // no where, nothing to do - return; - } - // walk the tree, create actual expressions and patch values along - // the way. - PatchedWhereExpression patchedExp = expn.patch(params); - Expression whereClause = patchedExp.getExpression(); - if (statement instanceof Query) { - Query<T> query = (Query<T>) statement; - query.where(whereClause); - } else { - String msg = "Patching of non-query types not (yet) supported! Class was:" - + statement.getClass().getName(); - IllegalStateException invalid = new IllegalStateException(msg); - throw new IllegalPatchException(invalid); - } - } - - public void setNumFreeParams(int num) { - this.numParams = num; - } - - public void setSuffixExpression(SuffixExpression tree) { - this.suffixExpn = tree; - } - - public SuffixExpression getSuffixExpression() { - return suffixExpn; - } - -}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/storage/core/src/main/java/com/redhat/thermostat/storage/internal/statement/ParsedStatementImpl.java Thu Jul 18 16:52:29 2013 +0200 @@ -0,0 +1,161 @@ +/* + * 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.IllegalPatchException; +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.Statement; +import com.redhat.thermostat.storage.model.Pojo; +import com.redhat.thermostat.storage.query.Expression; + +/** + * Result object as returned by {@link StatementDescriptorParser#parse()}. + * An instance of this plus a list of {@link PreparedParameter} should + * be sufficient to patch up a prepared statement with its real values. + * + * @see PreparedStatementImpl#executeQuery() + */ +class ParsedStatementImpl<T extends Pojo> implements ParsedStatement<T> { + + private int numParams; + private SuffixExpression suffixExpn; + private final Statement<T> statement; + + ParsedStatementImpl(Statement<T> statement) { + this.statement = statement; + } + + @Override + public int getNumParams() { + return numParams; + } + + @Override + public Statement<T> getRawStatement() { + return statement; + } + + @Override + public Statement<T> patchStatement(PreparedParameter[] params) throws IllegalPatchException { + if (suffixExpn == null) { + String msg = "Suffix expression must be set before patching!"; + IllegalStateException expn = new IllegalStateException(msg); + throw new IllegalPatchException(expn); + } + patchWhere(params); + patchSort(params); + patchLimit(params); + // TODO count actual patches and throw an exception if not all vars + // have been patched up. + return statement; + } + + private void patchLimit(PreparedParameter[] params) throws IllegalPatchException { + LimitExpression expn = suffixExpn.getLimitExpn(); + if (expn == null) { + // no limit expn, nothing to do + return; + } + PatchedLimitExpression patchedExp = expn.patch(params); + if (statement instanceof Query) { + Query<T> query = (Query<T>) statement; + query.limit(patchedExp.getLimitValue()); + } else { + String msg = "Patching of non-query types not (yet) supported! Class was:" + + statement.getClass().getName(); + IllegalStateException invalid = new IllegalStateException(msg); + throw new IllegalPatchException(invalid); + } + } + + private void patchSort(PreparedParameter[] params) throws IllegalPatchException { + SortExpression expn = suffixExpn.getSortExpn(); + if (expn == null) { + // no sort expn, nothing to do + return; + } + PatchedSortExpression patchedExp = expn.patch(params); + if (statement instanceof Query) { + Query<T> query = (Query<T>) statement; + PatchedSortMember[] members = patchedExp.getSortMembers(); + for (int i = 0; i < members.length; i++) { + query.sort(members[i].getSortKey(), members[i].getDirection()); + } + } else { + String msg = "Patching of non-query types not (yet) supported! Class was:" + + statement.getClass().getName(); + IllegalStateException invalid = new IllegalStateException(msg); + throw new IllegalPatchException(invalid); + } + } + + private void patchWhere(PreparedParameter[] params) throws IllegalPatchException { + WhereExpression expn = suffixExpn.getWhereExpn(); + if (expn == null) { + // no where, nothing to do + return; + } + // walk the tree, create actual expressions and patch values along + // the way. + PatchedWhereExpression patchedExp = expn.patch(params); + Expression whereClause = patchedExp.getExpression(); + if (statement instanceof Query) { + Query<T> query = (Query<T>) statement; + query.where(whereClause); + } else { + String msg = "Patching of non-query types not (yet) supported! Class was:" + + statement.getClass().getName(); + IllegalStateException invalid = new IllegalStateException(msg); + throw new IllegalPatchException(invalid); + } + } + + void setNumFreeParams(int num) { + this.numParams = num; + } + + void setSuffixExpression(SuffixExpression tree) { + this.suffixExpn = tree; + } + + SuffixExpression getSuffixExpression() { + return suffixExpn; + } + +}
--- a/storage/core/src/main/java/com/redhat/thermostat/storage/internal/statement/Patchable.java Fri Jul 19 16:37:43 2013 +0200 +++ b/storage/core/src/main/java/com/redhat/thermostat/storage/internal/statement/Patchable.java Thu Jul 18 16:52:29 2013 +0200 @@ -36,6 +36,9 @@ package com.redhat.thermostat.storage.internal.statement; +import com.redhat.thermostat.storage.core.IllegalPatchException; +import com.redhat.thermostat.storage.core.PreparedParameter; + /** * Interface for patchable objects in a ParsedStatement. *
--- a/storage/core/src/main/java/com/redhat/thermostat/storage/internal/statement/PreparedParameter.java Fri Jul 19 16:37:43 2013 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,61 +0,0 @@ -/* - * 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; - -/** - * Represents a prepared parameter. - * - * @see PreparedStatementImpl - */ -class PreparedParameter { - - private final Object value; - private final Class<?> type; - - PreparedParameter(Object value, Class<?> type) { - this.value = value; - this.type = type; - } - - public Object getValue() { - return value; - } - - public Class<?> getType() { - return type; - } -}
--- a/storage/core/src/main/java/com/redhat/thermostat/storage/internal/statement/PreparedStatementImpl.java Fri Jul 19 16:37:43 2013 +0200 +++ b/storage/core/src/main/java/com/redhat/thermostat/storage/internal/statement/PreparedStatementImpl.java Thu Jul 18 16:52:29 2013 +0200 @@ -39,6 +39,10 @@ import com.redhat.thermostat.storage.core.Cursor; import com.redhat.thermostat.storage.core.DataModifyingStatement; import com.redhat.thermostat.storage.core.DescriptorParsingException; +import com.redhat.thermostat.storage.core.IllegalPatchException; +import com.redhat.thermostat.storage.core.ParsedStatement; +import com.redhat.thermostat.storage.core.PreparedParameter; +import com.redhat.thermostat.storage.core.PreparedParameters; import com.redhat.thermostat.storage.core.PreparedStatement; import com.redhat.thermostat.storage.core.Query; import com.redhat.thermostat.storage.core.Statement; @@ -56,15 +60,15 @@ private StatementDescriptor<T> desc; private Query<T> query; private DataModifyingStatement<T> dmlStatement; - private final PreparedParameter[] params; - private final ParsedStatement<T> parsedStatement; + private final PreparedParameters params; + private final ParsedStatementImpl<T> parsedStatement; public PreparedStatementImpl(Storage storage, StatementDescriptor<T> desc) throws DescriptorParsingException { this.desc = desc; StatementDescriptorParser<T> parser = new StatementDescriptorParser<>(storage, desc); - this.parsedStatement = parser.parse(); + this.parsedStatement = (ParsedStatementImpl<T>)parser.parse(); int numParams = parsedStatement.getNumParams(); - params = new PreparedParameter[numParams]; + params = new PreparedParameters(numParams); Statement<T> statement = parsedStatement.getRawStatement(); if (statement instanceof DataModifyingStatement) { this.dmlStatement = (DataModifyingStatement<T>) statement; @@ -75,36 +79,28 @@ // used for testing ParsedStatements PreparedStatementImpl(int numParams) { - params = new PreparedParameter[numParams]; + params = new PreparedParameters(numParams); this.parsedStatement = null; } @Override public void setLong(int paramIndex, long paramValue) { - setType(paramIndex, paramValue, Long.class); + params.setLong(paramIndex, paramValue); } @Override public void setInt(int paramIndex, int paramValue) { - setType(paramIndex, paramValue, Integer.class); + params.setInt(paramIndex, paramValue); } @Override public void setStringList(int paramIndex, String[] paramValue) { - setType(paramIndex, paramValue, String[].class); + params.setStringList(paramIndex, paramValue); } @Override public void setBoolean(int paramIndex, boolean paramValue) { - setType(paramIndex, paramValue, boolean.class); - } - - private void setType(int paramIndex, Object paramValue, Class<?> paramType) { - if (paramIndex >= params.length) { - throw new IllegalArgumentException("Parameter index '" + paramIndex + "' out of range."); - } - PreparedParameter param = new PreparedParameter(paramValue, paramType); - params[paramIndex] = param; + params.setBoolean(paramIndex, paramValue); } @Override @@ -115,7 +111,7 @@ + DataModifyingStatement.class.getName()); } try { - dmlStatement = (DataModifyingStatement<T>) parsedStatement.patchQuery(this); + dmlStatement = (DataModifyingStatement<T>)parsedStatement.patchStatement(params.getParams()); } catch (Exception e) { throw new StatementExecutionException(e); } @@ -133,7 +129,7 @@ // FIXME: I'm sure we can improve on this. We should avoid walking the // tree each time. Some cache with unfinished nodes and a reference // to the matching expression should be sufficient. - query = (Query<T>) parsedStatement.patchQuery(this); + query = (Query<T>)parsedStatement.patchStatement(params.getParams()); } catch (IllegalPatchException e) { throw new StatementExecutionException(e); } @@ -141,18 +137,18 @@ } @Override - public int getId() { - // not implemented for Mongo - return -1; + public void setString(int paramIndex, String paramValue) { + params.setString(paramIndex, paramValue); + } + + // For testing only + PreparedParameter[] getParams() { + return params.getParams(); } @Override - public void setString(int paramIndex, String paramValue) { - setType(paramIndex, paramValue, String.class); - } - - PreparedParameter[] getParams() { - return params; + public ParsedStatement<T> getParsedStatement() { + return parsedStatement; } @Override
--- a/storage/core/src/main/java/com/redhat/thermostat/storage/internal/statement/SortExpression.java Fri Jul 19 16:37:43 2013 +0200 +++ b/storage/core/src/main/java/com/redhat/thermostat/storage/internal/statement/SortExpression.java Thu Jul 18 16:52:29 2013 +0200 @@ -39,6 +39,9 @@ import java.util.ArrayList; import java.util.List; +import com.redhat.thermostat.storage.core.IllegalPatchException; +import com.redhat.thermostat.storage.core.PreparedParameter; + /** * Represents a sort expression of a prepared statement's parse tree. *
--- a/storage/core/src/main/java/com/redhat/thermostat/storage/internal/statement/SortMember.java Fri Jul 19 16:37:43 2013 +0200 +++ b/storage/core/src/main/java/com/redhat/thermostat/storage/internal/statement/SortMember.java Thu Jul 18 16:52:29 2013 +0200 @@ -36,7 +36,9 @@ package com.redhat.thermostat.storage.internal.statement; +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.SortDirection; /**
--- a/storage/core/src/main/java/com/redhat/thermostat/storage/internal/statement/StatementDescriptorParser.java Fri Jul 19 16:37:43 2013 +0200 +++ b/storage/core/src/main/java/com/redhat/thermostat/storage/internal/statement/StatementDescriptorParser.java Thu Jul 18 16:52:29 2013 +0200 @@ -43,6 +43,7 @@ import com.redhat.thermostat.storage.core.Category; import com.redhat.thermostat.storage.core.DescriptorParsingException; import com.redhat.thermostat.storage.core.Key; +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.StatementDescriptor; @@ -120,7 +121,7 @@ private int currTokenIndex; private int placeHolderCount; // the parsed statement - private ParsedStatement<T> parsedStatement; + private ParsedStatementImpl<T> parsedStatement; private SuffixExpression tree; StatementDescriptorParser(Storage storage, StatementDescriptor<T> desc) { @@ -480,7 +481,7 @@ break; } case 'b': { - type = boolean.class; + type = Boolean.class; break; } default: @@ -571,7 +572,7 @@ if (tokens[0].equals(KNOWN_STATEMENT_TYPES[0])) { // query case Query<T> query = storage.createQuery(desc.getCategory()); - this.parsedStatement = new ParsedStatement<>(query); + this.parsedStatement = new ParsedStatementImpl<>(query); } else { throw new IllegalStateException("Don't know how to create statement type '" + tokens[0] + "'"); }
--- a/storage/core/src/main/java/com/redhat/thermostat/storage/internal/statement/TerminalNode.java Fri Jul 19 16:37:43 2013 +0200 +++ b/storage/core/src/main/java/com/redhat/thermostat/storage/internal/statement/TerminalNode.java Thu Jul 18 16:52:29 2013 +0200 @@ -38,7 +38,9 @@ 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; /**
--- a/storage/core/src/main/java/com/redhat/thermostat/storage/internal/statement/WhereExpression.java Fri Jul 19 16:37:43 2013 +0200 +++ b/storage/core/src/main/java/com/redhat/thermostat/storage/internal/statement/WhereExpression.java Thu Jul 18 16:52:29 2013 +0200 @@ -36,6 +36,9 @@ package com.redhat.thermostat.storage.internal.statement; +import com.redhat.thermostat.storage.core.IllegalPatchException; +import com.redhat.thermostat.storage.core.PreparedParameter; + /** * Data structure representing a where expression. *
--- a/storage/mongo/src/main/java/com/redhat/thermostat/storage/mongodb/internal/MongoQuery.java Fri Jul 19 16:37:43 2013 +0200 +++ b/storage/mongo/src/main/java/com/redhat/thermostat/storage/mongodb/internal/MongoQuery.java Thu Jul 18 16:52:29 2013 +0200 @@ -55,6 +55,7 @@ private Category<T> category; private Class<T> resultClass; private MongoExpressionParser parser; + private Expression expression = null; MongoQuery(MongoStorage storage, Category<T> category) { this(storage, category, new MongoExpressionParser()); @@ -77,6 +78,7 @@ @Override public void where(Expression expr) { + expression = expr; query = parser.parse(expr); hasClauses = true; } @@ -114,5 +116,10 @@ return storage.findAllPojos(this, resultClass); } + @Override + public Expression getWhereExpression() { + return expression; + } + }
--- a/web/client/src/main/java/com/redhat/thermostat/web/client/internal/WebStorage.java Fri Jul 19 16:37:43 2013 +0200 +++ b/web/client/src/main/java/com/redhat/thermostat/web/client/internal/WebStorage.java Thu Jul 18 16:52:29 2013 +0200 @@ -42,8 +42,8 @@ import java.io.InputStreamReader; import java.io.OutputStream; import java.io.Reader; -import java.lang.reflect.Array; import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Type; import java.net.MalformedURLException; import java.net.URL; import java.security.SecureRandom; @@ -96,13 +96,17 @@ import com.redhat.thermostat.storage.core.Connection; import com.redhat.thermostat.storage.core.Cursor; import com.redhat.thermostat.storage.core.DescriptorParsingException; +import com.redhat.thermostat.storage.core.IllegalDescriptorException; +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.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.SecureStorage; import com.redhat.thermostat.storage.core.StatementDescriptor; +import com.redhat.thermostat.storage.core.StatementExecutionException; import com.redhat.thermostat.storage.core.Storage; import com.redhat.thermostat.storage.core.StorageException; import com.redhat.thermostat.storage.core.Update; @@ -111,9 +115,14 @@ import com.redhat.thermostat.storage.query.Operator; import com.redhat.thermostat.web.common.ExpressionSerializer; import com.redhat.thermostat.web.common.OperatorSerializer; +import com.redhat.thermostat.web.common.PreparedParameterSerializer; import com.redhat.thermostat.web.common.ThermostatGSONConverter; import com.redhat.thermostat.web.common.WebInsert; -import com.redhat.thermostat.web.common.WebQuery; +import com.redhat.thermostat.web.common.WebPreparedStatement; +import com.redhat.thermostat.web.common.WebPreparedStatementResponse; +import com.redhat.thermostat.web.common.WebPreparedStatementSerializer; +import com.redhat.thermostat.web.common.WebQueryResponse; +import com.redhat.thermostat.web.common.WebQueryResponseSerializer; import com.redhat.thermostat.web.common.WebRemove; import com.redhat.thermostat.web.common.WebUpdate; @@ -318,20 +327,29 @@ updatePojo(this); } } - - private class WebQueryImpl<T extends Pojo> extends WebQuery<T> { + + private class WebPreparedStatementImpl<T extends Pojo> extends WebPreparedStatement<T> { - private transient Class<T> dataClass; - - WebQueryImpl(int categoryId, Class<T> dataClass) { - super(categoryId); - this.dataClass = dataClass; + // The type of the query result objects we'd get back upon + // statement execution + private final transient Type parametrizedTypeToken; + + public WebPreparedStatementImpl(Type parametrizedTypeToken, int numParams, int statementId) { + super(numParams, statementId); + this.parametrizedTypeToken = parametrizedTypeToken; + } + + @Override + public int execute() throws StatementExecutionException { + throw new IllegalStateException("Not yet implemented!"); } @Override - public Cursor<T> execute() { - return findAllPojos(this, dataClass); + public Cursor<T> executeQuery() + throws StatementExecutionException { + return doExecuteQuery(this, parametrizedTypeToken); } + } private String endpoint; @@ -363,7 +381,11 @@ new ThermostatGSONConverter()) .registerTypeHierarchyAdapter(Expression.class, new ExpressionSerializer()) - .registerTypeHierarchyAdapter(Operator.class, new OperatorSerializer()).create(); + .registerTypeHierarchyAdapter(Operator.class, new OperatorSerializer()) + .registerTypeAdapter(WebPreparedStatement.class, new WebPreparedStatementSerializer()) + .registerTypeAdapter(WebQueryResponse.class, new WebQueryResponseSerializer<>()) + .registerTypeAdapter(PreparedParameter.class, new PreparedParameterSerializer()) + .create(); httpClient = client; random = new SecureRandom(); conn = new WebConnection(); @@ -492,7 +514,10 @@ @Override public <T extends Pojo> Query<T> createQuery(Category<T> category) { - return new WebQueryImpl<>(categoryIds.get(category), category.getDataClass()); + // There isn't going to be a query end-point on server side. + String msg = "createQuery() not supported for Web storage. " + + "Please use prepareStatement() instead."; + throw new IllegalStateException(msg); } @Override @@ -507,14 +532,47 @@ return updateImpl; } - @SuppressWarnings("unchecked") - private <T extends Pojo> Cursor<T> findAllPojos(WebQuery<T> query, Class<T> resultClass) throws StorageException { - NameValuePair queryParam = new BasicNameValuePair("query", gson.toJson(query)); + /** + * Executes a prepared query + * + * @param stmt + * The prepared statement to execute + * @param parametrizedTypeToken + * The parametrized type token to use for deserialization. + * Example as to how this was created: + * <pre> + * Type parametrizedTypeToken = new + * TypeToken<WebQueryResponse<AgentInformation>>().getType(); + * </pre> + * @return A cursor for the generic type. + * @throws StatementExecutionException + * If execution of the statement failed. + */ + private <T extends Pojo> Cursor<T> doExecuteQuery(WebPreparedStatement<T> stmt, Type parametrizedTypeToken) throws StatementExecutionException { + NameValuePair queryParam = new BasicNameValuePair("prepared-stmt", gson.toJson(stmt, WebPreparedStatement.class)); List<NameValuePair> formparams = Arrays.asList(queryParam); - try (CloseableHttpEntity entity = post(endpoint + "/find-all", formparams)) { + WebQueryResponse<T> qResp = null; + try (CloseableHttpEntity entity = post(endpoint + "/query-execute", formparams)) { Reader reader = getContentAsReader(entity); - T[] result = (T[]) gson.fromJson(reader, Array.newInstance(resultClass, 0).getClass()); + qResp = gson.fromJson(reader, parametrizedTypeToken); + } catch (Exception e) { + throw new StatementExecutionException(e); + } + if (qResp.getResponseCode() == WebQueryResponse.SUCCESS) { + T[] result = qResp.getResultList(); return new WebCursor<T>(result); + } else if (qResp.getResponseCode() == WebQueryResponse.ILLEGAL_PATCH) { + String msg = "Illegal statement argument. See server logs for details."; + IllegalArgumentException iae = new IllegalArgumentException(msg); + IllegalPatchException e = new IllegalPatchException(iae); + throw new StatementExecutionException(e); + } else { + // We only handle success responses and illegal patches, like + // we do for other storages. This is just a defensive measure in + // order to fail early in case something unexpected comes back. + String msg = "Unknown response from storage endpoint!"; + IllegalStateException ise = new IllegalStateException(msg); + throw new StatementExecutionException(ise); } } @@ -709,8 +767,38 @@ @Override public <T extends Pojo> PreparedStatement<T> prepareStatement(StatementDescriptor<T> desc) throws DescriptorParsingException { - // TODO Implement - return null; + String strDesc = desc.getQueryDescriptor(); + int categoryId = getCategoryId(desc.getCategory()); + NameValuePair nameParam = new BasicNameValuePair("query-descriptor", + strDesc); + NameValuePair categoryParam = new BasicNameValuePair("category-id", + gson.toJson(categoryId, Integer.class)); + List<NameValuePair> formparams = Arrays + .asList(nameParam, categoryParam); + try (CloseableHttpEntity entity = post(endpoint + "/prepare-statement", + formparams)) { + Reader reader = getContentAsReader(entity); + WebPreparedStatementResponse result = gson.fromJson(reader, WebPreparedStatementResponse.class); + int numParams = result.getNumFreeVariables(); + int statementId = result.getStatementId(); + if (statementId == WebPreparedStatementResponse.ILLEGAL_STATEMENT) { + // 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()); + } else if (statementId == WebPreparedStatementResponse.DESCRIPTOR_PARSE_FAILED) { + String msg = "Statement descriptor failed to parse. " + + "Please check server logs for details!"; + throw new DescriptorParsingException(msg); + } else { + // We need this ugly trick in order for WebQueryResponse + // deserialization to work properly. I.e. GSON needs this type + // info hint. + Class<T> dataClass = desc.getCategory().getDataClass(); + Type typeToken = new WebQueryResponse<T>().getRuntimeParametrizedType(dataClass); + return new WebPreparedStatementImpl<T>(typeToken, numParams, statementId); + } + } } }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/web/common/src/main/java/com/redhat/thermostat/web/common/PreparedParameterSerializer.java Thu Jul 18 16:52:29 2013 +0200 @@ -0,0 +1,174 @@ +/* + * 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.web.common; + +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Set; + +import com.google.gson.JsonArray; +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonDeserializer; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; +import com.google.gson.JsonPrimitive; +import com.google.gson.JsonSerializationContext; +import com.google.gson.JsonSerializer; +import com.redhat.thermostat.storage.core.PreparedParameter; + + +/** + * GSON type adapter for {@link PreparedParameter}. + * + */ +public class PreparedParameterSerializer implements JsonDeserializer<PreparedParameter>, JsonSerializer<PreparedParameter>{ + + private static final String PROP_TYPE = "type"; + private static final String PROP_VALUE = "value"; + // The set of valid classes for types + private static final Set<String> VALID_CLASSNAMES; + + static { + VALID_CLASSNAMES = new HashSet<>(); + VALID_CLASSNAMES.add(String.class.getCanonicalName()); + VALID_CLASSNAMES.add(String[].class.getCanonicalName()); + VALID_CLASSNAMES.add(Integer.class.getCanonicalName()); + VALID_CLASSNAMES.add(Long.class.getCanonicalName()); + VALID_CLASSNAMES.add(Boolean.class.getCanonicalName()); + } + + @Override + public JsonElement serialize(PreparedParameter param, Type type, + JsonSerializationContext ctxt) { + JsonObject result = new JsonObject(); + JsonElement valueElem = serializeValue(param.getValue()); + result.add(PROP_VALUE, valueElem); + JsonPrimitive typeElem = new JsonPrimitive(param.getType().getCanonicalName()); + result.add(PROP_TYPE, typeElem); + return result; + } + + private JsonElement serializeValue(Object value) { + JsonElement element; + if (value instanceof Integer) { + int val = ((Integer)value).intValue(); + element = new JsonPrimitive(val); + } else if (value instanceof Long) { + long val = ((Long)value).longValue(); + element = new JsonPrimitive(val); + } else if (value instanceof String) { + String val = (String)value; + element = new JsonPrimitive(val); + } else if (value instanceof String[]) { + String[] val = (String[])value; + JsonArray array = new JsonArray(); + for (int i = 0; i < val.length; i++) { + array.add(new JsonPrimitive(val[i])); + } + element = array; + } else if (value instanceof Boolean) { + Boolean val = (Boolean)value; + element = new JsonPrimitive(val.booleanValue()); + } else { + throw new IllegalStateException("Unexpected value for serialization '" + value + "'"); + } + return element; + } + + @Override + public PreparedParameter deserialize(JsonElement jsonElem, Type type, + JsonDeserializationContext ctxt) throws JsonParseException { + JsonElement typeElem = jsonElem.getAsJsonObject().get(PROP_TYPE); + String className = typeElem.getAsString(); + // perform some sanity checking on which classes we do forName() :) + validateSaneClassName(className); + Class<?> typeVal = deserializeTypeVal(className); + JsonElement valueElement = jsonElem.getAsJsonObject().get(PROP_VALUE); + Object value = deserializeValue(ctxt, valueElement, typeVal); + PreparedParameter param = new PreparedParameter(); + param.setType(typeVal); + param.setValue(value); + return param; + } + + private Class<?> deserializeTypeVal(String className) { + Class<?> typeVal = null; + if (className.equals(String[].class.getCanonicalName())) { + typeVal = String[].class; + } else { + try { + typeVal = Class.forName(className); + } catch (ClassNotFoundException ignored) { + // we only load valid classes that way + }; + } + return typeVal; + } + + private Object deserializeValue(JsonDeserializationContext ctxt, + JsonElement valueElement, Class<?> valType) { + if (valueElement.isJsonPrimitive()) { + // By telling GSON the type, we get the rightly casted + // value back. + return ctxt.deserialize(valueElement, valType); + } else if (valueElement.isJsonArray()) { + // Only string arrays are supported + List<String> values = new ArrayList<>(); + JsonArray jsonArray = (JsonArray)valueElement; + Iterator<JsonElement> it = jsonArray.iterator(); + while (it.hasNext()) { + JsonElement elem = it.next(); + String strElem = ctxt.deserialize(elem, String.class); + values.add(strElem); + } + return values.toArray(new String[0]); + } else { + throw new IllegalStateException("Illegal json for parameter value"); + } + } + + private void validateSaneClassName(String className) { + if (!VALID_CLASSNAMES.contains(className)) { + throw new IllegalStateException("Illegal type of parameter " + className); + } + } + +}
--- a/web/common/src/main/java/com/redhat/thermostat/web/common/ThermostatGSONConverter.java Fri Jul 19 16:37:43 2013 +0200 +++ b/web/common/src/main/java/com/redhat/thermostat/web/common/ThermostatGSONConverter.java Thu Jul 18 16:52:29 2013 +0200 @@ -81,7 +81,7 @@ @Override public JsonElement serialize(Pojo src, Type typeOfSrc, JsonSerializationContext context) { - Class cls = (Class) typeOfSrc; + Class<?> cls = (Class<?>) typeOfSrc; if (! cls.isAnnotationPresent(Entity.class)) { System.err.println("attempt to serialize non-Entity class: " + cls.getName()); throw new IllegalArgumentException("attempt to serialize non-Entity class: " + cls.getName());
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/web/common/src/main/java/com/redhat/thermostat/web/common/WebPreparedStatement.java Thu Jul 18 16:52:29 2013 +0200 @@ -0,0 +1,123 @@ +/* + * 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.web.common; + +import com.redhat.thermostat.storage.core.Cursor; +import com.redhat.thermostat.storage.core.ParsedStatement; +import com.redhat.thermostat.storage.core.PreparedParameters; +import com.redhat.thermostat.storage.core.PreparedStatement; +import com.redhat.thermostat.storage.core.StatementExecutionException; +import com.redhat.thermostat.storage.model.Pojo; + +public class WebPreparedStatement<T extends Pojo> implements + PreparedStatement<T> { + + private PreparedParameters params; + private int statementId; + + public WebPreparedStatement(int numParams, int statementId) { + this.params = new PreparedParameters(numParams); + this.statementId = statementId; + } + + public WebPreparedStatement() { + // nothing. used for serialization + } + + public int getStatementId() { + return statementId; + } + + public void setStatementId(int statementId) { + this.statementId = statementId; + } + + public PreparedParameters getParams() { + return params; + } + + public void setParams(PreparedParameters params) { + this.params = params; + } + + @Override + public void setBoolean(int paramIndex, boolean paramValue) { + params.setBoolean(paramIndex, paramValue); + } + + @Override + public void setLong(int paramIndex, long paramValue) { + params.setLong(paramIndex, paramValue); + } + + @Override + public void setInt(int paramIndex, int paramValue) { + params.setInt(paramIndex, paramValue); + } + + @Override + public void setString(int paramIndex, String paramValue) { + params.setString(paramIndex, paramValue); + } + + @Override + public void setStringList(int paramIndex, String[] paramValue) { + params.setStringList(paramIndex, paramValue); + } + + @Override + public int execute() throws StatementExecutionException { + // actual implementation should override this + throw new IllegalStateException(); + } + + @Override + public Cursor<T> executeQuery() + throws StatementExecutionException { + // actual implementation should override this + throw new IllegalStateException(); + } + + @Override + public ParsedStatement<T> getParsedStatement() { + // Should never be called on WebPreparedStatement + // It should use the implementation of the backing + // storage implementation instead. + throw new IllegalStateException(); + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/web/common/src/main/java/com/redhat/thermostat/web/common/WebPreparedStatementResponse.java Thu Jul 18 16:52:29 2013 +0200 @@ -0,0 +1,73 @@ +/* + * 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.web.common; + +/** + * Model class as returned upon preparing statements. + */ +public class WebPreparedStatementResponse { + + public static final int ILLEGAL_STATEMENT = -1; + public static final int DESCRIPTOR_PARSE_FAILED = -2; + + public WebPreparedStatementResponse() { + // Should always be set using the setter before it + // is retrieved. Since 0 is a bad default for this, + // we set it to -1 in order to make this an invalid + // value right away. + this.numFreeVariables = -1; + } + + private int numFreeVariables; + private int statementId; + + public int getStatementId() { + return statementId; + } + + public void setStatementId(int statementId) { + this.statementId = statementId; + } + + public int getNumFreeVariables() { + return numFreeVariables; + } + + public void setNumFreeVariables(int freeVars) { + this.numFreeVariables = freeVars; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/web/common/src/main/java/com/redhat/thermostat/web/common/WebPreparedStatementSerializer.java Thu Jul 18 16:52:29 2013 +0200 @@ -0,0 +1,88 @@ +/* + * 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.web.common; + +import java.lang.reflect.Type; + +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonDeserializer; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; +import com.google.gson.JsonPrimitive; +import com.google.gson.JsonSerializationContext; +import com.google.gson.JsonSerializer; +import com.redhat.thermostat.storage.core.PreparedParameters; + +/** + * GSON type adapter for {@link WebPreparedStatement}. + * Depends on {@link PreparedParameterSerializer} being registered as + * type adapter as well. + * + */ +public class WebPreparedStatementSerializer implements + JsonDeserializer<WebPreparedStatement<?>>, + JsonSerializer<WebPreparedStatement<?>> { + + private static final String PROP_PARAMS = "p"; + private static final String PROP_STMT_ID = "sid"; + + @Override + public JsonElement serialize(WebPreparedStatement<?> stmt, Type type, + JsonSerializationContext ctxt) { + JsonObject result = new JsonObject(); + JsonElement parameters = ctxt.serialize(stmt.getParams(), PreparedParameters.class); + result.add(PROP_PARAMS, parameters); + JsonPrimitive stmtIdElem = new JsonPrimitive(stmt.getStatementId()); + result.add(PROP_STMT_ID, stmtIdElem); + return result; + } + + @Override + public WebPreparedStatement<?> deserialize(JsonElement jsonElem, Type type, + JsonDeserializationContext ctxt) throws JsonParseException { + JsonElement paramsElem = jsonElem.getAsJsonObject().get(PROP_PARAMS); + JsonElement stmtIdElem = jsonElem.getAsJsonObject().get(PROP_STMT_ID); + PreparedParameters params = ctxt.deserialize(paramsElem, PreparedParameters.class); + int stmtId = ctxt.deserialize(stmtIdElem, int.class); + WebPreparedStatement<?> stmt = new WebPreparedStatement<>(); + stmt.setStatementId(stmtId); + stmt.setParams(params); + return stmt; + } + +}
--- a/web/common/src/main/java/com/redhat/thermostat/web/common/WebQuery.java Fri Jul 19 16:37:43 2013 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,82 +0,0 @@ -/* - * 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.web.common; - -import com.redhat.thermostat.storage.core.AbstractQuery; -import com.redhat.thermostat.storage.core.Cursor; -import com.redhat.thermostat.storage.model.Pojo; -import com.redhat.thermostat.storage.query.Expression; - -public class WebQuery<T extends Pojo> extends AbstractQuery<T> { - - private Expression expr; - private int categoryId; - - public WebQuery() { - this(-1); - } - - public WebQuery(int categoryId) { - this.categoryId = categoryId; - } - - public int getCategoryId() { - return categoryId; - } - - public void setCategoryId(int categoryId) { - this.categoryId = categoryId; - } - - @Override - public void where(Expression expr) { - this.expr = expr; - } - - public Expression getExpression() { - return expr; - } - - @Override - public Cursor<T> execute() { - // This should only ever be called when created from WebStorage, which provides its own subclass. - throw new IllegalStateException(); - } - -} -
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/web/common/src/main/java/com/redhat/thermostat/web/common/WebQueryResponse.java Thu Jul 18 16:52:29 2013 +0200 @@ -0,0 +1,97 @@ +/* + * 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.web.common; + +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; + +import com.redhat.thermostat.storage.model.Pojo; + +/** + * Model object for prepared query execution responses. + * + */ +public class WebQueryResponse<T extends Pojo> { + + public static final int SUCCESS = 0; + public static final int ILLEGAL_PATCH = -1; + + private int responseCode; + private T[] resultList; + + public int getResponseCode() { + return responseCode; + } + + public void setResponseCode(int responseCode) { + this.responseCode = responseCode; + } + + public T[] getResultList() { + return resultList; + } + + public void setResultList(T[] resultList) { + this.resultList = resultList; + } + + public ParameterizedType getRuntimeParametrizedType(final Class<T> dataClass) { + ParameterizedType webQueryResponseType = new ParameterizedType() { + + @Override + public Type getRawType() { + return WebQueryResponse.class; + } + + @Override + public Type getOwnerType() { + // top-level type, must return null + return null; + } + + @Override + public Type[] getActualTypeArguments() { + // WebQueryResponse has only one type parameter, which + // is the actual data class + return new Type[] { + dataClass + }; + } + }; + return webQueryResponseType; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/web/common/src/main/java/com/redhat/thermostat/web/common/WebQueryResponseSerializer.java Thu Jul 18 16:52:29 2013 +0200 @@ -0,0 +1,102 @@ +/* + * 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.web.common; + +import java.lang.reflect.Array; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; + +import com.google.gson.JsonArray; +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonDeserializer; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; +import com.google.gson.JsonPrimitive; +import com.google.gson.JsonSerializationContext; +import com.google.gson.JsonSerializer; +import com.redhat.thermostat.storage.model.Pojo; + +/** + * GSON type adapter for {@link QueryResponse}. + * Depends on {@link ThermostatGSONConverter} being registered as + * type adapter as well. + * + */ +public class WebQueryResponseSerializer<T extends Pojo> implements + JsonDeserializer<WebQueryResponse<T>>, JsonSerializer<WebQueryResponse<T>> { + + private static final String PROP_RESULT = "payload"; + private static final String PROP_ERROR_CODE = "errno"; + + @Override + public JsonElement serialize(WebQueryResponse<T> qResponse, Type type, + JsonSerializationContext ctxt) { + JsonObject result = new JsonObject(); + JsonElement resultsElem = ctxt.serialize(qResponse.getResultList()); + result.add(PROP_RESULT, resultsElem); + JsonPrimitive errnoElem = new JsonPrimitive(qResponse.getResponseCode()); + result.add(PROP_ERROR_CODE, errnoElem); + return result; + } + + @SuppressWarnings("unchecked") + @Override + public WebQueryResponse<T> deserialize(JsonElement jsonElem, Type type, + JsonDeserializationContext ctxt) throws JsonParseException { + // fromJson() calls need to pass in the right *parameterized* type token: + // example for AgentInformation as T: + // Type queryResponseType = new TypeToken<WebQueryResponse<AgentInformation>>() {}.getType(); + // gson.fromJson(jsonStr, queryResponseType) + Type[] typeParameters = ((ParameterizedType)type).getActualTypeArguments(); + Type queryResponseTypeParam = typeParameters[0]; // WebQueryResponse has only one parameterized type T + JsonArray resultElem = jsonElem.getAsJsonObject().get(PROP_RESULT).getAsJsonArray(); + @SuppressWarnings("rawtypes") + Class typeOfGeneric = (Class)queryResponseTypeParam; + T[] array = (T[])Array.newInstance(typeOfGeneric, resultElem.size()); + for (int i = 0; i < resultElem.size(); i++) { + array[i] = ctxt.deserialize(resultElem.get(i), queryResponseTypeParam); + } + JsonElement errorCodeElem = jsonElem.getAsJsonObject().get(PROP_ERROR_CODE); + int errorCode = ctxt.deserialize(errorCodeElem, int.class); + WebQueryResponse<T> qResponse = new WebQueryResponse<>(); + qResponse.setResponseCode(errorCode); + qResponse.setResultList(array); + return qResponse; + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/web/server/src/main/java/com/redhat/thermostat/web/server/PreparedStatementHolder.java Thu Jul 18 16:52:29 2013 +0200 @@ -0,0 +1,65 @@ +/* + * 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.web.server; + +import com.redhat.thermostat.storage.core.PreparedStatement; +import com.redhat.thermostat.storage.model.Pojo; + +class PreparedStatementHolder<T extends Pojo> { + + private final int id; + private final PreparedStatement<T> stmt; + private final Class<T> dataClass; + + PreparedStatementHolder(int id, PreparedStatement<T> stmt, Class<T> dataClass) { + this.id = id; + this.stmt = stmt; + this.dataClass = dataClass; + } + + int getId() { + return id; + } + + PreparedStatement<T> getStmt() { + return stmt; + } + + Class<T> getDataClass() { + return dataClass; + } +}
--- a/web/server/src/main/java/com/redhat/thermostat/web/server/WebStorageEndPoint.java Fri Jul 19 16:37:43 2013 +0200 +++ b/web/server/src/main/java/com/redhat/thermostat/web/server/WebStorageEndPoint.java Thu Jul 18 16:52:29 2013 +0200 @@ -41,6 +41,7 @@ import java.io.InputStream; import java.io.OutputStream; import java.io.Writer; +import java.lang.reflect.Array; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -68,14 +69,20 @@ import com.redhat.thermostat.common.utils.LoggingUtils; import com.redhat.thermostat.shared.config.Configuration; import com.redhat.thermostat.shared.config.InvalidConfigurationException; -import com.redhat.thermostat.storage.core.AbstractQuery.Sort; import com.redhat.thermostat.storage.core.Category; import com.redhat.thermostat.storage.core.Connection; import com.redhat.thermostat.storage.core.Cursor; +import com.redhat.thermostat.storage.core.DescriptorParsingException; +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.PreparedParameters; +import com.redhat.thermostat.storage.core.PreparedStatement; import com.redhat.thermostat.storage.core.Put; import com.redhat.thermostat.storage.core.Query; import com.redhat.thermostat.storage.core.Remove; +import com.redhat.thermostat.storage.core.StatementDescriptor; import com.redhat.thermostat.storage.core.Storage; import com.redhat.thermostat.storage.core.Update; import com.redhat.thermostat.storage.model.Pojo; @@ -83,10 +90,15 @@ import com.redhat.thermostat.storage.query.Operator; import com.redhat.thermostat.web.common.ExpressionSerializer; import com.redhat.thermostat.web.common.OperatorSerializer; +import com.redhat.thermostat.web.common.PreparedParameterSerializer; import com.redhat.thermostat.web.common.StorageWrapper; import com.redhat.thermostat.web.common.ThermostatGSONConverter; import com.redhat.thermostat.web.common.WebInsert; -import com.redhat.thermostat.web.common.WebQuery; +import com.redhat.thermostat.web.common.WebPreparedStatement; +import com.redhat.thermostat.web.common.WebPreparedStatementResponse; +import com.redhat.thermostat.web.common.WebPreparedStatementSerializer; +import com.redhat.thermostat.web.common.WebQueryResponse; +import com.redhat.thermostat.web.common.WebQueryResponseSerializer; import com.redhat.thermostat.web.common.WebRemove; import com.redhat.thermostat.web.common.WebUpdate; import com.redhat.thermostat.web.server.auth.Roles; @@ -117,6 +129,12 @@ private Map<String, Integer> categoryIds; private Map<Integer, Category<?>> categories; + + private Map<StatementDescriptor<?>, PreparedStatementHolder<?>> preparedStmts; + private Map<Integer, PreparedStatementHolder<?>> preparedStatementIds; + // Lock to be held for setting/getting prepared queries in the above maps + private Object preparedStmtLock = new Object(); + private int currentPreparedStmtId; @Override public void init(ServletConfig config) throws ServletException { @@ -132,9 +150,15 @@ .registerTypeHierarchyAdapter(Expression.class, new ExpressionSerializer()) .registerTypeHierarchyAdapter(Operator.class, - new OperatorSerializer()).create(); + new OperatorSerializer()) + .registerTypeAdapter(WebQueryResponse.class, new WebQueryResponseSerializer<>()) + .registerTypeAdapter(PreparedParameter.class, new PreparedParameterSerializer()) + .registerTypeAdapter(WebPreparedStatement.class, new WebPreparedStatementSerializer()) + .create(); categoryIds = new HashMap<>(); categories = new HashMap<>(); + preparedStatementIds = new HashMap<>(); + preparedStmts = new HashMap<>(); TokenManager tokenManager = new TokenManager(); String timeoutParam = getInitParameter(TOKEN_MANAGER_TIMEOUT_PARAM); if (timeoutParam != null) { @@ -174,8 +198,11 @@ String uri = req.getRequestURI(); int lastPartIdx = uri.lastIndexOf("/"); String cmd = uri.substring(lastPartIdx + 1); - if (cmd.equals("find-all")) { - findAll(req, resp); + if (cmd.equals("prepare-statement")) { + prepareStatement(req, resp); + } + else if (cmd.equals("query-execute")) { + queryExecute(req, resp); } else if (cmd.equals("put-pojo")) { putPojo(req, resp); } else if (cmd.equals("register-category")) { @@ -243,6 +270,69 @@ } } + @SuppressWarnings("unchecked") + @WebStoragePathHandler( path = "prepare-statement" ) + private <T extends Pojo> void prepareStatement(HttpServletRequest req, + HttpServletResponse resp) throws IOException { + if (! isAuthorized(req, resp, Roles.PREPARE_STATEMENT)) { + return; + } + String queryDescrParam = req.getParameter("query-descriptor"); + String categoryIdParam = req.getParameter("category-id"); + Integer catId = gson.fromJson(categoryIdParam, Integer.class); + Category<?> cat = getCategoryFromId(catId); + WebPreparedStatementResponse response = new WebPreparedStatementResponse(); + if (cat == null) { + // bad category? we refuse to accept this + logger.log(Level.WARNING, "Attepted to prepare a statement with an illegal category id"); + response.setStatementId(WebPreparedStatementResponse.ILLEGAL_STATEMENT); + writeResponse(resp, response, WebPreparedStatementResponse.class); + return; + } + StatementDescriptor<?> desc = new StatementDescriptor<>(cat, queryDescrParam); + synchronized (preparedStmtLock) { + // see if we've prepared this query already + if (preparedStmts.containsKey(desc)) { + PreparedStatementHolder<T> holder = (PreparedStatementHolder<T>) preparedStmts + .get(desc); + ParsedStatement<T> parsed = holder.getStmt() + .getParsedStatement(); + int freeVars = parsed.getNumParams(); + response.setNumFreeVariables(freeVars); + response.setStatementId(holder.getId()); + writeResponse(resp, response, + WebPreparedStatementResponse.class); + return; + } + // TODO: Check if descriptor is trusted (i.e. known) + + // Prepare the target statement and put it into our prepared statement + // maps. + PreparedStatement<T> targetPreparedStatement; + try { + targetPreparedStatement = (PreparedStatement<T>) storage + .prepareStatement(desc); + } catch (DescriptorParsingException e) { + logger.log(Level.WARNING, "Descriptor parse error!", e); + response.setStatementId(WebPreparedStatementResponse.DESCRIPTOR_PARSE_FAILED); + writeResponse(resp, response, + WebPreparedStatementResponse.class); + return; + } + PreparedStatementHolder<T> holder = new PreparedStatementHolder<T>( + currentPreparedStmtId, targetPreparedStatement, + (Class<T>) cat.getDataClass()); + preparedStmts.put(desc, holder); + preparedStatementIds.put(currentPreparedStmtId, holder); + ParsedStatement<?> parsed = targetPreparedStatement + .getParsedStatement(); + response.setNumFreeVariables(parsed.getNumParams()); + response.setStatementId(currentPreparedStmtId); + writeResponse(resp, response, WebPreparedStatementResponse.class); + currentPreparedStmtId++; + } + } + @WebStoragePathHandler( path = "ping" ) private void ping(HttpServletRequest req, HttpServletResponse resp) { if (! isAuthorized(req, resp, Roles.LOGIN)) { @@ -449,37 +539,54 @@ } } - @WebStoragePathHandler( path = "find-all" ) - @SuppressWarnings({ "rawtypes", "unchecked" }) - private void findAll(HttpServletRequest req, HttpServletResponse resp) throws IOException { + @SuppressWarnings("unchecked") + @WebStoragePathHandler( path = "query-execute" ) + private <T extends Pojo> void queryExecute(HttpServletRequest req, HttpServletResponse resp) throws IOException { if (! isAuthorized(req, resp, Roles.READ)) { return; } - String queryParam = req.getParameter("query"); - WebQuery query = gson.fromJson(queryParam, WebQuery.class); - Query targetQuery = constructTargetQuery(query); - ArrayList resultList = new ArrayList(); - Cursor result = targetQuery.execute(); - while (result.hasNext()) { - resultList.add(result.next()); + String queryParam = req.getParameter("prepared-stmt"); + WebPreparedStatement<T> stmt = gson.fromJson(queryParam, WebPreparedStatement.class); + + PreparedParameters p = stmt.getParams(); + PreparedStatementHolder<T> targetStmtHolder = getStatementHolderFromId(stmt.getStatementId()); + PreparedStatement<T> targetStmt = targetStmtHolder.getStmt(); + ParsedStatement<T> parsed = targetStmt.getParsedStatement(); + Query<T> targetQuery = null; + ArrayList<T> resultList = new ArrayList<>(); + WebQueryResponse<T> response = new WebQueryResponse<>(); + try { + targetQuery = (Query<T>)parsed.patchStatement(p.getParams()); + response.setResponseCode(WebQueryResponse.SUCCESS); + } catch (IllegalPatchException e) { + response.setResponseCode(WebQueryResponse.ILLEGAL_PATCH); + writeResponse(resp, response, WebQueryResponse.class); + return; } - writeResponse(resp, resultList.toArray()); + // TODO: Do proper query filtering + targetQuery = fixQuery(targetQuery, stmt.getStatementId()); + Cursor<T> cursor = targetQuery.execute(); + while (cursor.hasNext()) { + resultList.add(cursor.next()); + } + T[] results = (T[])Array.newInstance(targetStmtHolder.getDataClass(), resultList.size()); + for (int i = 0; i < resultList.size(); i++) { + results[i] = resultList.get(i); + } + response.setResultList(results); + writeResponse(resp, response, WebQueryResponse.class); } - private Query<?> constructTargetQuery(WebQuery<? extends Pojo> query) { - int categoryId = query.getCategoryId(); - Category<?> category = getCategoryFromId(categoryId); + @SuppressWarnings("rawtypes") + private <T extends Pojo> Query fixQuery(Query<T> targetQuery, int statementId) { + // TODO: Change the expression so as to perform proper filtering. + return targetQuery; + } - Query<?> targetQuery = storage.createQuery(category); - Expression whereExpr = query.getExpression(); - if (whereExpr != null) { - targetQuery.where(whereExpr); - } - for (Sort s : query.getSorts()) { - targetQuery.sort(s.getKey(), s.getDirection()); - } - targetQuery.limit(query.getLimit()); - return targetQuery; + private <T extends Pojo> PreparedStatementHolder<T> getStatementHolderFromId(int statementId) { + @SuppressWarnings("unchecked") // we are the only ones adding them + PreparedStatementHolder<T> holder = (PreparedStatementHolder<T>)preparedStatementIds.get(statementId); + return holder; } private Category<?> getCategoryFromId(int categoryId) { @@ -487,10 +594,11 @@ return category; } - private void writeResponse(HttpServletResponse resp, Object result) throws IOException { + private void writeResponse(HttpServletResponse resp, + Object responseObj, Class<?> typeOfResponseObj) throws IOException { resp.setStatus(HttpServletResponse.SC_OK); resp.setContentType(RESPONSE_JSON_CONTENT_TYPE); - gson.toJson(result, resp.getWriter()); + gson.toJson(responseObj, typeOfResponseObj, resp.getWriter()); resp.flushBuffer(); }
--- a/web/server/src/main/java/com/redhat/thermostat/web/server/auth/Roles.java Fri Jul 19 16:37:43 2013 +0200 +++ b/web/server/src/main/java/com/redhat/thermostat/web/server/auth/Roles.java Thu Jul 18 16:52:29 2013 +0200 @@ -46,6 +46,7 @@ final String REPLACE = "thermostat-replace"; final String UPDATE = "thermostat-update"; final String DELETE = "thermostat-remove"; + final String PREPARE_STATEMENT = "thermostat-prepare-statement"; final String READ = "thermostat-query"; final String GET_COUNT = "thermostat-get-count"; final String LOAD_FILE = "thermostat-load-file"; @@ -60,7 +61,7 @@ final String[] ALL_ROLES = { APPEND, REPLACE, UPDATE, DELETE, READ, GET_COUNT, LOAD_FILE, SAVE_FILE, PURGE, REGISTER_CATEGORY, CMD_CHANNEL_GENERATE, - CMD_CHANNEL_VERIFY, LOGIN, ACCESS_REALM + CMD_CHANNEL_VERIFY, LOGIN, ACCESS_REALM, PREPARE_STATEMENT }; final String[] AGENT_ROLES = { @@ -71,7 +72,7 @@ final String[] CLIENT_ROLES = { ACCESS_REALM, LOGIN, CMD_CHANNEL_GENERATE, LOAD_FILE, - GET_COUNT, READ, REGISTER_CATEGORY + GET_COUNT, READ, REGISTER_CATEGORY, PREPARE_STATEMENT }; }