View Javadoc
1   /**
2    * Copyright (c) 2014, stateful.co
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 stateful.co 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 co.stateful.core;
31  
32  import co.stateful.spi.Counter;
33  import co.stateful.spi.Counters;
34  import com.amazonaws.services.dynamodbv2.model.AttributeValue;
35  import com.google.common.base.Function;
36  import com.google.common.base.Predicates;
37  import com.google.common.collect.Iterables;
38  import com.google.common.collect.Iterators;
39  import com.jcabi.aspects.Cacheable;
40  import com.jcabi.aspects.Immutable;
41  import com.jcabi.aspects.Loggable;
42  import com.jcabi.dynamo.Attributes;
43  import com.jcabi.dynamo.Conditions;
44  import com.jcabi.dynamo.Item;
45  import com.jcabi.dynamo.QueryValve;
46  import com.jcabi.dynamo.Table;
47  import com.jcabi.urn.URN;
48  import java.io.IOException;
49  import java.util.concurrent.TimeUnit;
50  import lombok.EqualsAndHashCode;
51  import lombok.ToString;
52  
53  /**
54   * Counters in DynamoDB.
55   *
56   * @author Yegor Bugayenko (yegor@tpc2.com)
57   * @version $Id$
58   */
59  @Immutable
60  @ToString
61  @EqualsAndHashCode(of = "owner")
62  @Loggable(Loggable.DEBUG)
63  final class DyCounters implements Counters {
64  
65      /**
66       * Table name.
67       */
68      public static final String TBL = "counters";
69  
70      /**
71       * Hash.
72       */
73      public static final String HASH = "urn";
74  
75      /**
76       * Range.
77       */
78      public static final String RANGE = "name";
79  
80      /**
81       * Value attribute.
82       */
83      public static final String ATTR_VALUE = "value";
84  
85      /**
86       * Dynamo table.
87       */
88      private final transient Table table;
89  
90      /**
91       * Name of the user.
92       */
93      private final transient URN owner;
94  
95      /**
96       * Ctor.
97       * @param tbl Dynamo table
98       * @param urn Owner of them
99       */
100     DyCounters(final Table tbl, final URN urn) {
101         this.table = tbl;
102         this.owner = urn;
103     }
104 
105     @Override
106     @Cacheable.FlushAfter
107     public void create(final String name) throws IOException {
108         this.table.put(
109             new Attributes()
110                 .with(DyCounters.HASH, this.owner)
111                 .with(DyCounters.RANGE, name)
112                 .with(DyCounters.ATTR_VALUE, new AttributeValue().withN("0"))
113         );
114     }
115 
116     @Override
117     @Cacheable.FlushAfter
118     public void delete(final String name) {
119         Iterators.removeIf(
120             this.table.frame()
121                 .through(new QueryValve().withLimit(1))
122                 .where(DyCounters.HASH, Conditions.equalTo(this.owner))
123                 .where(DyCounters.RANGE, Conditions.equalTo(name))
124                 .iterator(),
125             Predicates.alwaysTrue()
126         );
127     }
128 
129     @Override
130     @Cacheable(lifetime = 1, unit = TimeUnit.HOURS)
131     public Counter get(final String name) {
132         return new DyCounter(
133             this.table.frame()
134                 .through(new QueryValve().withLimit(1))
135                 .where(DyCounters.HASH, Conditions.equalTo(this.owner))
136                 .where(DyCounters.RANGE, Conditions.equalTo(name))
137                 .iterator()
138                 .next()
139         );
140     }
141 
142     @Override
143     @Cacheable(lifetime = 1, unit = TimeUnit.HOURS)
144     public Iterable<String> names() {
145         return Iterables.transform(
146             this.table.frame()
147                 .through(new QueryValve())
148                 .where(DyCounters.HASH, Conditions.equalTo(this.owner)),
149             new Function<Item, String>() {
150                 @Override
151                 public String apply(final Item item) {
152                     try {
153                         return item.get(DyCounters.RANGE).getS();
154                     } catch (final IOException ex) {
155                         throw new IllegalStateException(ex);
156                     }
157                 }
158             }
159         );
160     }
161 }