Coverage Report - com.jcabi.dynamo.mock.H2Data
 
Classes in this File Line Coverage Branch Coverage Complexity
H2Data
80%
68/85
55%
20/36
2.944
H2Data$1
100%
14/14
100%
6/6
2.944
H2Data$2
100%
2/2
N/A
2.944
H2Data$3
100%
2/2
N/A
2.944
H2Data$4
100%
2/2
N/A
2.944
H2Data$5
100%
2/2
N/A
2.944
H2Data$6
80%
4/5
50%
1/2
2.944
H2Data$AjcClosure1
100%
1/1
N/A
2.944
H2Data$AjcClosure11
100%
1/1
N/A
2.944
H2Data$AjcClosure3
100%
1/1
N/A
2.944
H2Data$AjcClosure5
100%
1/1
N/A
2.944
H2Data$AjcClosure7
100%
1/1
N/A
2.944
H2Data$AjcClosure9
100%
1/1
N/A
2.944
 
 1  18
 /**
 2  
  * Copyright (c) 2012-2016, jcabi.com
 3  
  * All rights reserved.
 4  
  *
 5  
  * Redistribution and use in source and binary forms, with or without
 6  
  * modification, are permitted provided that the following conditions
 7  
  * are met: 1) Redistributions of source code must retain the above
 8  
  * copyright notice, this list of conditions and the following
 9  
  * disclaimer. 2) Redistributions in binary form must reproduce the above
 10  
  * copyright notice, this list of conditions and the following
 11  
  * disclaimer in the documentation and/or other materials provided
 12  
  * with the distribution. 3) Neither the name of the jcabi.com nor
 13  
  * the names of its contributors may be used to endorse or promote
 14  
  * products derived from this software without specific prior written
 15  
  * permission.
 16  
  *
 17  
  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 18  
  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT
 19  
  * NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
 20  
  * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
 21  
  * THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
 22  
  * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 23  
  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 24  
  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 25  
  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 26  
  * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 27  
  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
 28  
  * OF THE POSSIBILITY OF SUCH DAMAGE.
 29  
  */
 30  
 package com.jcabi.dynamo.mock;
 31  
 
 32  
 import com.amazonaws.services.dynamodbv2.model.AttributeValue;
 33  
 import com.amazonaws.services.dynamodbv2.model.AttributeValueUpdate;
 34  
 import com.amazonaws.services.dynamodbv2.model.ComparisonOperator;
 35  
 import com.amazonaws.services.dynamodbv2.model.Condition;
 36  
 import com.google.common.base.Function;
 37  
 import com.google.common.base.Joiner;
 38  
 import com.google.common.collect.Iterables;
 39  
 import com.jcabi.aspects.Immutable;
 40  
 import com.jcabi.aspects.Loggable;
 41  
 import com.jcabi.dynamo.AttributeUpdates;
 42  
 import com.jcabi.dynamo.Attributes;
 43  
 import com.jcabi.dynamo.Conditions;
 44  
 import com.jcabi.jdbc.JdbcSession;
 45  
 import com.jcabi.jdbc.ListOutcome;
 46  
 import com.jcabi.jdbc.Outcome;
 47  
 import java.io.File;
 48  
 import java.io.IOException;
 49  
 import java.sql.Connection;
 50  
 import java.sql.ResultSet;
 51  
 import java.sql.ResultSetMetaData;
 52  
 import java.sql.SQLException;
 53  
 import java.sql.Statement;
 54  
 import java.util.Arrays;
 55  
 import java.util.Collection;
 56  
 import java.util.Collections;
 57  
 import java.util.LinkedList;
 58  
 import java.util.Locale;
 59  
 import java.util.Properties;
 60  
 import java.util.regex.Matcher;
 61  
 import java.util.regex.Pattern;
 62  
 import lombok.EqualsAndHashCode;
 63  
 import lombok.ToString;
 64  
 import org.apache.commons.codec.binary.Base32;
 65  
 import org.h2.Driver;
 66  
 
 67  
 /**
 68  
  * Mock data in H2 database.
 69  
  *
 70  
  * @author Yegor Bugayenko (yegor@tpc2.com)
 71  
  * @version $Id: 599dfc99ac6dfe1bd471247b398bf92389e26472 $
 72  
  * @since 0.10
 73  
  * @checkstyle ClassDataAbstractionCouplingCheck (500 lines)
 74  
  */
 75  
 @Immutable
 76  15
 @ToString
 77  
 @Loggable(Loggable.DEBUG)
 78  0
 @EqualsAndHashCode(of = "jdbc")
 79  
 @SuppressWarnings({ "PMD.TooManyMethods", "PMD.ExcessiveImports" })
 80  
 public final class H2Data implements MkData {
 81  
 
 82  
     /**
 83  
      * Fetcher of rows.
 84  
      */
 85  1
     private static final Outcome<Iterable<Attributes>> OUTCOME =
 86  
         // @checkstyle AnonInnerLengthCheck (50 lines)
 87  11
         new Outcome<Iterable<Attributes>>() {
 88  
             @Override
 89  
             public Iterable<Attributes> handle(final ResultSet rset,
 90  
                 final Statement stmt) throws SQLException {
 91  10
                 final Collection<Attributes> items =
 92  
                     new LinkedList<Attributes>();
 93  20
                 while (rset.next()) {
 94  10
                     items.add(this.fetch(rset));
 95  
                 }
 96  10
                 return items;
 97  
             }
 98  
             /**
 99  
              * Convert result set to Attributes.
 100  
              * @param rset Result set
 101  
              * @return Attribs
 102  
              * @throws SQLException If fails
 103  
              */
 104  
             private Attributes fetch(final ResultSet rset) throws SQLException {
 105  10
                 final ResultSetMetaData meta = rset.getMetaData();
 106  10
                 Attributes attrs = new Attributes();
 107  34
                 for (int idx = 0; idx < meta.getColumnCount(); ++idx) {
 108  24
                     final String text = rset.getString(idx + 1);
 109  24
                     AttributeValue value = new AttributeValue().withS(text);
 110  24
                     if (text.matches("[0-9]+")) {
 111  16
                         value = value.withN(text);
 112  
                     }
 113  24
                     attrs = attrs.with(
 114  
                         meta.getColumnName(idx + 1).toLowerCase(Locale.ENGLISH),
 115  
                         value
 116  
                     );
 117  
                 }
 118  10
                 return attrs;
 119  
             }
 120  
         };
 121  
 
 122  
     /**
 123  
      * Where clause.
 124  
      */
 125  1
     private static final Function<String, String> WHERE =
 126  17
         new Function<String, String>() {
 127  
             @Override
 128  
             public String apply(final String key) {
 129  16
                 return String.format("%s = ?", key);
 130  
             }
 131  
         };
 132  
 
 133  
     /**
 134  
      * Create primary key.
 135  
      */
 136  1
     private static final Function<String, String> CREATE_KEY =
 137  10
         new Function<String, String>() {
 138  
             @Override
 139  
             public String apply(final String key) {
 140  9
                 return String.format("%s VARCHAR PRIMARY KEY", key);
 141  
             }
 142  
         };
 143  
 
 144  
     /**
 145  
      * Create attr.
 146  
      */
 147  1
     private static final Function<String, String> CREATE_ATTR =
 148  6
         new Function<String, String>() {
 149  
             @Override
 150  
             public String apply(final String key) {
 151  5
                 return String.format("%s CLOB", key);
 152  
             }
 153  
         };
 154  
 
 155  
     /**
 156  
      * WHERE clauses are joined with this.
 157  
      */
 158  
     private static final String AND = " AND ";
 159  
 
 160  
     /**
 161  
      * JDBC URL.
 162  
      */
 163  
     private final transient String jdbc;
 164  
 
 165  
     /**
 166  
      * Public ctor.
 167  
      * @throws IOException If fails
 168  
      */
 169  
     public H2Data() throws IOException {
 170  8
         this(File.createTempFile("jcabi-dynamo-", ".h2"));
 171  8
     }
 172  
 
 173  
     /**
 174  
      * Public ctor.
 175  
      * @param file Where to keep the database
 176  
      */
 177  8
     public H2Data(final File file) {
 178  8
         this.jdbc = String.format(
 179  
             "jdbc:h2:file:%s",
 180  
             file.getAbsolutePath()
 181  
         );
 182  8
     }
 183  
 
 184  
     @Override
 185  
     public Iterable<String> keys(final String table) throws IOException {
 186  
         try {
 187  14
             return Iterables.transform(
 188  
                 new JdbcSession(this.connection())
 189  
                     // @checkstyle LineLength (1 line)
 190  
                     .sql("SELECT SQL FROM INFORMATION_SCHEMA.CONSTRAINTS WHERE TABLE_NAME = ?")
 191  
                     .set(H2Data.encodeTableName(table))
 192  
                     .select(
 193  
                         new ListOutcome<String>(
 194  14
                             new ListOutcome.Mapping<String>() {
 195  
                                 @Override
 196  
                                 public String map(final ResultSet rset)
 197  
                                     throws SQLException {
 198  7
                                     return rset.getString(1);
 199  
                                 }
 200  
                             }
 201  
                         )
 202  
                     ),
 203  26
                 new Function<String, String>() {
 204  
                     @Override
 205  
                     public String apply(final String input) {
 206  19
                         final Matcher matcher = Pattern.compile(
 207  
                             "PRIMARY KEY\\((.*)\\)"
 208  
                         ).matcher(input);
 209  19
                         if (!matcher.find()) {
 210  0
                             throw new IllegalStateException(
 211  
                                 String.format(
 212  
                                     "something is wrong here: \"%s\"", input
 213  
                                 )
 214  
                             );
 215  
                         }
 216  19
                         return matcher.group(1).toLowerCase(Locale.ENGLISH);
 217  
                     }
 218  
                 }
 219  
             );
 220  0
         } catch (final SQLException ex) {
 221  0
             throw new IOException(ex);
 222  
         }
 223  
     }
 224  
 
 225  
     @Override
 226  
     public Iterable<Attributes> iterate(final String table,
 227  
         final Conditions conds) throws IOException {
 228  
         try {
 229  20
             final StringBuilder sql = new StringBuilder("SELECT * FROM ")
 230  
                 .append(H2Data.encodeTableName(table));
 231  10
             if (!conds.isEmpty()) {
 232  7
                 sql.append(" WHERE ");
 233  7
                 Joiner.on(H2Data.AND).appendTo(
 234  
                     sql,
 235  
                     Iterables.transform(conds.keySet(), H2Data.WHERE)
 236  
                 );
 237  
             }
 238  10
             JdbcSession session = new JdbcSession(this.connection())
 239  
                 .sql(sql.toString());
 240  10
             for (final Condition cond : conds.values()) {
 241  7
                 if (cond.getAttributeValueList().size() != 1) {
 242  0
                     throw new UnsupportedOperationException(
 243  
                         "at the moment only one value of condition is supported"
 244  
                     );
 245  
                 }
 246  7
                 if (!cond.getComparisonOperator()
 247  
                     .equals(ComparisonOperator.EQ.toString())) {
 248  0
                     throw new UnsupportedOperationException(
 249  
                         String.format(
 250  
                             "at the moment only EQ operator is supported: %s",
 251  
                             cond.getComparisonOperator()
 252  
                         )
 253  
                     );
 254  
                 }
 255  7
                 final AttributeValue val = cond.getAttributeValueList().get(0);
 256  7
                 session = session.set(H2Data.value(val));
 257  7
             }
 258  10
             return session.select(H2Data.OUTCOME);
 259  0
         } catch (final SQLException ex) {
 260  0
             throw new IOException(ex);
 261  
         }
 262  
     }
 263  
 
 264  
     @Override
 265  
     public void put(final String table, final Attributes attrs)
 266  
         throws IOException {
 267  
         try {
 268  12
             JdbcSession session = new JdbcSession(this.connection());
 269  6
             for (final AttributeValue value : attrs.values()) {
 270  11
                 session = session.set(H2Data.value(value));
 271  11
             }
 272  6
             session.sql(
 273  
                 String.format(
 274  
                     "INSERT INTO %s (%s) VALUES (%s)",
 275  
                     H2Data.encodeTableName(table),
 276  
                     Joiner.on(',').join(attrs.keySet()),
 277  
                     Joiner.on(',').join(Collections.nCopies(attrs.size(), "?"))
 278  
                 )
 279  
             );
 280  6
             session.execute();
 281  0
         } catch (final SQLException ex) {
 282  0
             throw new IOException(ex);
 283  6
         }
 284  6
     }
 285  
 
 286  
     @Override
 287  
     public void update(final String table, final Attributes keys,
 288  
         final AttributeUpdates attrs)
 289  
         throws IOException {
 290  
         try {
 291  8
             JdbcSession session = new JdbcSession(this.connection());
 292  4
             for (final AttributeValueUpdate value : attrs.values()) {
 293  4
                 session = session.set(H2Data.value(value.getValue()));
 294  4
             }
 295  4
             for (final AttributeValue value : keys.values()) {
 296  4
                 session = session.set(H2Data.value(value));
 297  4
             }
 298  4
             session.sql(
 299  
                 String.format(
 300  
                     "UPDATE %s SET %s WHERE %s",
 301  
                     H2Data.encodeTableName(table),
 302  
                     Joiner.on(',').join(
 303  
                         Iterables.transform(attrs.keySet(), H2Data.WHERE)
 304  
                     ),
 305  
                     Joiner.on(H2Data.AND).join(
 306  
                         Iterables.transform(keys.keySet(), H2Data.WHERE)
 307  
                     )
 308  
                 )
 309  
             );
 310  4
             session.execute();
 311  0
         } catch (final SQLException ex) {
 312  0
             throw new IOException(ex);
 313  4
         }
 314  4
     }
 315  
 
 316  
     @Override
 317  
     public void delete(final String table, final Attributes keys)
 318  
         throws IOException {
 319  
         try {
 320  2
             JdbcSession session = new JdbcSession(this.connection());
 321  1
             for (final AttributeValue value : keys.values()) {
 322  1
                 session = session.set(H2Data.value(value));
 323  1
             }
 324  1
             session.sql(
 325  
                 String.format(
 326  
                     "DELETE FROM %s WHERE %s",
 327  
                     H2Data.encodeTableName(table),
 328  
                     Joiner.on(H2Data.AND).join(
 329  
                         Iterables.transform(keys.keySet(), H2Data.WHERE)
 330  
                     )
 331  
                 )
 332  
             );
 333  1
             session.execute();
 334  0
         } catch (final SQLException ex) {
 335  0
             throw new IOException(ex);
 336  1
         }
 337  1
     }
 338  
 
 339  
     /**
 340  
      * With this table, that has given primary keys.
 341  
      * @param table Table name
 342  
      * @param keys Primary keys
 343  
      * @param attrs Attributes
 344  
      * @return New data, modified
 345  
      * @throws IOException If fails
 346  
      */
 347  
     public H2Data with(final String table, final String[] keys,
 348  
         final String... attrs) throws IOException {
 349  18
         if (keys.length == 0) {
 350  0
             throw new IllegalArgumentException(
 351  
                 String.format("empty list of keys for %s table", table)
 352  
             );
 353  
         }
 354  9
         final StringBuilder sql = new StringBuilder("CREATE TABLE ")
 355  
             .append(H2Data.encodeTableName(table)).append(" (");
 356  9
         Joiner.on(',').appendTo(
 357  
             sql,
 358  
             Iterables.transform(Arrays.asList(keys), H2Data.CREATE_KEY)
 359  
         );
 360  9
         if (attrs.length > 0) {
 361  4
             sql.append(',');
 362  4
             Joiner.on(',').appendTo(
 363  
                 sql,
 364  
                 Iterables.transform(Arrays.asList(attrs), H2Data.CREATE_ATTR)
 365  
             );
 366  
         }
 367  9
         sql.append(')');
 368  
         try {
 369  9
             new JdbcSession(this.connection()).sql(sql.toString()).execute();
 370  0
         } catch (final SQLException ex) {
 371  0
             throw new IOException(ex);
 372  9
         }
 373  9
         return this;
 374  
     }
 375  
     /**
 376  
      * Make data source.
 377  
      * @return Data source for JDBC
 378  
      * @throws SQLException If fails
 379  
      */
 380  
     private Connection connection() throws SQLException {
 381  37
         return new Driver().connect(this.jdbc, new Properties());
 382  
     }
 383  
 
 384  
     /**
 385  
      * Get value from attribute.
 386  
      * @param attr Attribute value
 387  
      * @return Text format
 388  
      */
 389  
     private static String value(final AttributeValue attr) {
 390  27
         String val = attr.getS();
 391  27
         if (val == null) {
 392  7
             val = attr.getN();
 393  
         }
 394  27
         if (val == null) {
 395  0
             throw new IllegalArgumentException(
 396  
                 "we support only N and S at the moment"
 397  
             );
 398  
         }
 399  27
         return val;
 400  
     }
 401  
 
 402  
     /**
 403  
      * Base32-encodes table name for use with H2.
 404  
      * @param table Table name to encode
 405  
      * @return Base-32-encoded table name
 406  
      */
 407  
     private static String encodeTableName(final String table) {
 408  37
         return Joiner.on("").join(
 409  
             "_",
 410  
             new Base32(true, (byte) '_').encodeAsString(table.getBytes())
 411  
         );
 412  
     }
 413  
 
 414  
 }