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.Counters;
33  import co.stateful.spi.Locks;
34  import co.stateful.spi.User;
35  import com.google.common.base.Joiner;
36  import com.google.common.base.Splitter;
37  import com.google.common.collect.Iterables;
38  import com.jcabi.aspects.Cacheable;
39  import com.jcabi.aspects.Immutable;
40  import com.jcabi.aspects.Loggable;
41  import com.jcabi.aspects.Tv;
42  import com.jcabi.dynamo.Attributes;
43  import com.jcabi.dynamo.Conditions;
44  import com.jcabi.dynamo.Credentials;
45  import com.jcabi.dynamo.Item;
46  import com.jcabi.dynamo.QueryValve;
47  import com.jcabi.dynamo.Region;
48  import com.jcabi.manifests.Manifests;
49  import com.jcabi.urn.URN;
50  import java.io.IOException;
51  import java.util.Iterator;
52  import java.util.Locale;
53  import java.util.concurrent.TimeUnit;
54  import lombok.EqualsAndHashCode;
55  import lombok.ToString;
56  import org.apache.commons.codec.digest.DigestUtils;
57  import org.apache.commons.lang3.RandomStringUtils;
58  
59  /**
60   * Default user.
61   *
62   * @author Yegor Bugayenko (yegor@tpc2.com)
63   * @version $Id$
64   * @checkstyle ClassDataAbstractionCouplingCheck (500 lines)
65   */
66  @Immutable
67  @ToString
68  @EqualsAndHashCode(of = "name")
69  @Loggable(Loggable.DEBUG)
70  final class DefaultUser implements User {
71  
72      /**
73       * Table name.
74       */
75      public static final String TOKENS = "tokens";
76  
77      /**
78       * Hash.
79       */
80      public static final String HASH = "urn";
81  
82      /**
83       * Token attribute.
84       */
85      public static final String ATTR_TOKEN = "token";
86  
87      /**
88       * Name of the user.
89       */
90      private final transient URN name;
91  
92      /**
93       * Region.
94       */
95      private final transient Region region;
96  
97      /**
98       * Ctor.
99       * @param urn Name of it
100      */
101     DefaultUser(final URN urn) {
102         this.name = urn;
103         final String key = Manifests.read("Stateful-DynamoKey");
104         Credentials creds = new Credentials.Simple(
105             key,
106             Manifests.read("Stateful-DynamoSecret")
107         );
108         if ("AAAAABBBBBAAAAABBBBB".equals(key)) {
109             creds = new Credentials.Direct(
110                 creds, Integer.parseInt(System.getProperty("dynamo.port"))
111             );
112         }
113         this.region = new Region.Prefixed(
114             new Region.Simple(creds),
115             Manifests.read("Stateful-DynamoPrefix")
116         );
117     }
118 
119     @Override
120     @Cacheable(forever = true)
121     public boolean exists() {
122         return this.region.table(DefaultUser.TOKENS)
123             .frame().through(new QueryValve())
124             .where(DefaultUser.HASH, Conditions.equalTo(this.name))
125             .iterator().hasNext();
126     }
127 
128     @Override
129     @Cacheable(lifetime = 1, unit = TimeUnit.HOURS)
130     public String token() throws IOException {
131         final Iterator<Item> items = this.region.table(DefaultUser.TOKENS)
132             .frame().through(new QueryValve())
133             .where(DefaultUser.HASH, Conditions.equalTo(this.name))
134             .iterator();
135         final String token;
136         if (items.hasNext()) {
137             token = items.next().get(DefaultUser.ATTR_TOKEN).getS();
138         } else {
139             this.refresh();
140             token = this.token();
141         }
142         return token;
143     }
144 
145     @Override
146     @Cacheable.FlushAfter
147     public void refresh() throws IOException {
148         this.region.table(DefaultUser.TOKENS).put(
149             new Attributes()
150                 .with(DefaultUser.HASH, this.name)
151                 .with(
152                     DefaultUser.ATTR_TOKEN,
153                     Joiner.on('-').join(
154                         Iterables.limit(
155                             Splitter.fixedLength(Tv.FOUR).split(
156                                 DigestUtils.md5Hex(
157                                     RandomStringUtils.random(Tv.TEN)
158                                 ).toUpperCase(Locale.ENGLISH)
159                             ),
160                             Tv.FOUR
161                         )
162                     )
163                 )
164         );
165     }
166 
167     @Override
168     @Cacheable(lifetime = 1, unit = TimeUnit.HOURS)
169     public Counters counters() {
170         return new DyCounters(this.region.table(DyCounters.TBL), this.name);
171     }
172 
173     @Override
174     public Locks locks() {
175         return new DyLocks(this.region.table(DyLocks.TBL), this.name);
176     }
177 }