View Javadoc
1   /**
2    * Copyright (c) 2012-2017, s3auth.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 s3auth.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.s3auth.hosts;
31  
32  import com.jcabi.aspects.Immutable;
33  import com.jcabi.aspects.Loggable;
34  import com.jcabi.jdbc.JdbcSession;
35  import com.jcabi.jdbc.Outcome;
36  import java.io.File;
37  import java.io.IOException;
38  import java.sql.Connection;
39  import java.sql.ResultSet;
40  import java.sql.SQLException;
41  import java.sql.Statement;
42  import java.util.HashMap;
43  import java.util.Map;
44  import java.util.Properties;
45  import lombok.EqualsAndHashCode;
46  import org.h2.Driver;
47  
48  /**
49   * Storage of {@link Stats} per domain with H2 Database.
50   *
51   * @author Carlos Miranda (miranda.cma@gmail.com)
52   * @version $Id: ddaa83d1fff1c1b2db9310b1e08741bfacd822ea $
53   * @checkstyle ClassDataAbstractionCouplingCheck (500 lines)
54   */
55  @Immutable
56  @EqualsAndHashCode(of = "jdbc")
57  @Loggable(Loggable.DEBUG)
58  final class H2DomainStatsData implements DomainStatsData {
59      /**
60       * Create Table statement.
61       */
62      private static final String CREATE = new StringBuilder("CREATE TABLE ")
63          .append("IF NOT EXISTS DOMAIN_STATS( ")
64          .append("ID INT IDENTITY,")
65          .append("DOMAIN VARCHAR(255),")
66          .append("BYTES INT,")
67          .append("CREATE_TIME TIMESTAMP")
68          .append(" )").toString();
69  
70      /**
71       * Insert statement.
72       */
73      private static final String INSERT = new StringBuilder("INSERT INTO ")
74          .append("DOMAIN_STATS (DOMAIN, BYTES, CREATE_TIME) ")
75          .append("values (?, ?, CURRENT_TIMESTAMP())").toString();
76  
77      /**
78       * Outcome for obtaining a single Stats per domain.
79       */
80      private static final Outcome<Stats> STATS = new Outcome<Stats>() {
81          @Override
82          public Stats handle(final ResultSet rset, final Statement stmt)
83              throws SQLException {
84              rset.next();
85              return new Stats.Simple(rset.getLong(1));
86          }
87      };
88  
89      /**
90       * Outcome for obtaining a single Stats for all domains.
91       */
92      private static final Outcome<Map<String, Stats>> STATS_ALL =
93          new Outcome<Map<String, Stats>>() {
94              @Override
95              @SuppressWarnings("PMD.UseConcurrentHashMap")
96              public Map<String, Stats> handle(final ResultSet rset,
97                  final Statement stmt) throws SQLException {
98                  final Map<String, Stats> stats = new HashMap<String, Stats>();
99                  while (rset.next()) {
100                     stats.put(
101                         rset.getString(1), new Stats.Simple(rset.getLong(2))
102                     );
103                 }
104                 return stats;
105             }
106         };
107 
108     /**
109      * The JDBC URL.
110      */
111     private final transient String jdbc;
112 
113     /**
114      * Public ctor.
115      * @throws IOException If an IO Exception occurs.
116      */
117     H2DomainStatsData() throws IOException {
118         this(new File("s3auth-domainStats"));
119     }
120 
121     /**
122      * Public ctor.
123      * @param file The file pointing to the database to use.
124      * @throws IOException If an IO Exception occurs.
125      */
126     H2DomainStatsData(final File file) throws IOException {
127         this.jdbc = String.format("jdbc:h2:file:%s", file.getAbsolutePath());
128         try {
129             new JdbcSession(this.connection()).sql(CREATE).execute();
130         } catch (final SQLException ex) {
131             throw new IOException(ex);
132         }
133     }
134 
135     @Override
136     public void put(final String domain, final Stats stats) throws IOException {
137         try {
138             new JdbcSession(this.connection())
139                 .sql(INSERT)
140                 .set(domain)
141                 .set(stats.bytesTransferred())
142                 .execute();
143         } catch (final SQLException ex) {
144             throw new IOException(ex);
145         }
146     }
147 
148     @Override
149     public Stats get(final String domain) throws IOException {
150         try {
151             final JdbcSession session = new JdbcSession(this.connection())
152                 .autocommit(false);
153             // @checkstyle LineLength (2 lines)
154             final Stats result = session
155                 .sql("SELECT SUM(BYTES) FROM DOMAIN_STATS WHERE DOMAIN = ? FOR UPDATE")
156                 .set(domain)
157                 .select(STATS);
158             session.sql("DELETE FROM DOMAIN_STATS WHERE DOMAIN = ?")
159                 .set(domain)
160                 .execute()
161                 .commit();
162             return result;
163         } catch (final SQLException ex) {
164             throw new IOException(ex);
165         }
166     }
167 
168     @Override
169     @SuppressWarnings("PMD.UseConcurrentHashMap")
170     public Map<String, Stats> all() throws IOException {
171         try {
172             final JdbcSession session = new JdbcSession(this.connection())
173                 .autocommit(false);
174             // @checkstyle LineLength (2 lines)
175             final Map<String, Stats> result = session
176                 .sql("SELECT DOMAIN, SUM(BYTES) FROM DOMAIN_STATS GROUP BY DOMAIN FOR UPDATE")
177                 .select(STATS_ALL);
178             session.sql("DELETE FROM DOMAIN_STATS").execute().commit();
179             return result;
180         } catch (final SQLException ex) {
181             throw new IOException(ex);
182         }
183     }
184 
185     /**
186      * Make data source.
187      * @return Data source for JDBC
188      * @throws SQLException If it fails
189      */
190     private Connection connection() throws SQLException {
191         return new Driver().connect(this.jdbc, new Properties());
192     }
193 }