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.Locks;
33  import com.amazonaws.AmazonClientException;
34  import com.amazonaws.services.dynamodbv2.AmazonDynamoDB;
35  import com.amazonaws.services.dynamodbv2.model.ConditionalCheckFailedException;
36  import com.amazonaws.services.dynamodbv2.model.ExpectedAttributeValue;
37  import com.amazonaws.services.dynamodbv2.model.PutItemRequest;
38  import com.google.common.base.Predicate;
39  import com.google.common.base.Predicates;
40  import com.google.common.collect.ImmutableMap;
41  import com.google.common.collect.Iterables;
42  import com.jcabi.aspects.Immutable;
43  import com.jcabi.aspects.Loggable;
44  import com.jcabi.dynamo.Attributes;
45  import com.jcabi.dynamo.Conditions;
46  import com.jcabi.dynamo.Item;
47  import com.jcabi.dynamo.QueryValve;
48  import com.jcabi.dynamo.Table;
49  import com.jcabi.urn.URN;
50  import java.io.IOException;
51  import java.util.Iterator;
52  import java.util.Map;
53  import lombok.EqualsAndHashCode;
54  import lombok.ToString;
55  
56  /**
57   * Locks in DynamoDB.
58   *
59   * @author Yegor Bugayenko (yegor@tpc2.com)
60   * @version $Id$
61   * @since 1.1
62   */
63  @Immutable
64  @ToString
65  @EqualsAndHashCode(of = "owner")
66  @Loggable(Loggable.DEBUG)
67  final class DyLocks implements Locks {
68  
69      /**
70       * Table name.
71       */
72      public static final String TBL = "locks";
73  
74      /**
75       * Hash.
76       */
77      public static final String HASH = "urn";
78  
79      /**
80       * Range.
81       */
82      public static final String RANGE = "name";
83  
84      /**
85       * Label.
86       */
87      public static final String ATTR_LABEL = "label";
88  
89      /**
90       * Dynamo table.
91       */
92      private final transient Table table;
93  
94      /**
95       * Name of the user.
96       */
97      private final transient URN owner;
98  
99      /**
100      * Ctor.
101      * @param tbl Dynamo table
102      * @param urn Owner of them
103      */
104     DyLocks(final Table tbl, final URN urn) {
105         this.table = tbl;
106         this.owner = urn;
107     }
108 
109     @Override
110     public Map<String, String> names() {
111         final ImmutableMap.Builder<String, String> map =
112             new ImmutableMap.Builder<String, String>();
113         Iterables.all(
114             this.table.frame().through(
115                 new QueryValve().withAttributesToGet(
116                     DyLocks.ATTR_LABEL
117                 )
118             ).where(DyLocks.HASH, Conditions.equalTo(this.owner)),
119             new Predicate<Item>() {
120                 @Override
121                 public boolean apply(final Item item) {
122                     try {
123                         map.put(
124                             item.get(DyLocks.RANGE).getS(),
125                             item.get(DyLocks.ATTR_LABEL).getS()
126                         );
127                     } catch (final IOException ex) {
128                         throw new IllegalStateException(ex);
129                     }
130                     return true;
131                 }
132             }
133         );
134         return map.build();
135     }
136 
137     @Override
138     public String lock(final String name, final String label)
139         throws IOException {
140         final AmazonDynamoDB aws = this.table.region().aws();
141         String msg = "";
142         try {
143             final PutItemRequest request = new PutItemRequest();
144             request.setTableName(this.table.name());
145             request.setItem(
146                 new Attributes()
147                     .with(DyLocks.HASH, this.owner)
148                     .with(DyLocks.RANGE, name)
149                     .with(DyLocks.ATTR_LABEL, label)
150             );
151             request.setExpected(
152                 new ImmutableMap.Builder<String, ExpectedAttributeValue>().put(
153                     DyLocks.ATTR_LABEL,
154                     new ExpectedAttributeValue().withExists(false)
155                 ).build()
156             );
157             aws.putItem(request);
158         } catch (final ConditionalCheckFailedException ex) {
159             msg = ex.getLocalizedMessage();
160         } catch (final AmazonClientException ex) {
161             throw new IOException(ex);
162         } finally {
163             aws.shutdown();
164         }
165         return msg;
166     }
167 
168     @Override
169     public void unlock(final String name) {
170         Iterables.removeIf(
171             this.table.frame()
172                 .through(new QueryValve())
173                 .where(DyLocks.HASH, this.owner.toString())
174                 .where(DyLocks.RANGE, name),
175             Predicates.alwaysTrue()
176         );
177     }
178 
179     @Override
180     public String unlock(final String name, final String label)
181         throws IOException {
182         final Iterator<Item> items = this.table.frame()
183             .through(new QueryValve())
184             .where(DyLocks.HASH, this.owner.toString())
185             .where(DyLocks.RANGE, name)
186             .iterator();
187         String msg = "";
188         if (items.hasNext()) {
189             final String required = items.next().get(DyLocks.ATTR_LABEL).getS();
190             if (required.equals(label)) {
191                 this.unlock(name);
192             } else {
193                 msg = required;
194             }
195         }
196         return msg;
197     }
198 }