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.rest;
31  
32  import co.stateful.spi.Locks;
33  import com.rexsl.page.JaxbBundle;
34  import com.rexsl.page.Link;
35  import com.rexsl.page.PageBuilder;
36  import java.io.IOException;
37  import java.net.HttpURLConnection;
38  import java.util.Map;
39  import java.util.logging.Level;
40  import javax.ws.rs.Consumes;
41  import javax.ws.rs.DefaultValue;
42  import javax.ws.rs.FormParam;
43  import javax.ws.rs.GET;
44  import javax.ws.rs.POST;
45  import javax.ws.rs.Path;
46  import javax.ws.rs.QueryParam;
47  import javax.ws.rs.WebApplicationException;
48  import javax.ws.rs.core.MediaType;
49  import javax.ws.rs.core.Response;
50  import org.apache.commons.io.IOUtils;
51  import org.apache.commons.lang3.StringUtils;
52  
53  /**
54   * Locks of a user.
55   *
56   * @author Yegor Bugayenko (yegor@tpc2.com)
57   * @version $Id$
58   * @checkstyle MultipleStringLiteralsCheck (500 lines)
59   * @since 1.1
60   */
61  @Path("/k")
62  public final class LocksRs extends BaseRs {
63  
64      /**
65       * Query param.
66       * @checkstyle ConstantUsageCheck (3 lines)
67       */
68      private static final String PARAM = "name";
69  
70      /**
71       * Get entrance page JAX-RS response.
72       * @return The JAX-RS response
73       * @throws Exception If some problem inside
74       */
75      @GET
76      @Path("/")
77      public Response index() throws Exception {
78          return new PageBuilder()
79              .stylesheet("/xsl/locks.xsl")
80              .build(StPage.class)
81              .init(this)
82              .append(
83                  new JaxbBundle(
84                      "documentation",
85                      IOUtils.toString(
86                          this.getClass().getResourceAsStream(
87                              "doc-locks.html"
88                          )
89                      )
90                  )
91              )
92              .append(this.list())
93              .append(new JaxbBundle("menu", "locks"))
94              .link(new Link("lock", "./lock"))
95              .link(new Link("unlock", "./unlock"))
96              .render()
97              .build();
98      }
99  
100     /**
101      * Lock.
102      * @param name Name of the lock
103      * @param label Label
104      * @return Response
105      * @throws IOException If fails due to IO problem
106      */
107     @POST
108     @Path("/lock")
109     @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
110     public Response lock(@FormParam(LocksRs.PARAM) final String name,
111         @FormParam("label") @DefaultValue("none") final String label)
112         throws IOException {
113         if (!name.matches("[0-9a-zA-Z\\-\\._\\$]{1,256}")) {
114             throw this.flash().redirect(
115                 this.uriInfo().getBaseUriBuilder()
116                     .clone()
117                     .path(LocksRs.class)
118                     .build(),
119                 "1-256 letters, numbers or dashes",
120                 Level.WARNING
121             );
122         }
123         if (label.isEmpty()) {
124             throw this.flash().redirect(
125                 this.uriInfo().getBaseUriBuilder()
126                     .clone()
127                     .path(LocksRs.class)
128                     .build(),
129                 "label can't be empty",
130                 Level.WARNING
131             );
132         }
133         if (this.user().locks().names().size() > Locks.MAX) {
134             throw this.flash().redirect(
135                 this.uriInfo().getBaseUriBuilder()
136                     .clone()
137                     .path(LocksRs.class)
138                     .build(),
139                 "too many locks in your account",
140                 Level.SEVERE
141             );
142         }
143         final String msg = this.user().locks().lock(name, label);
144         if (msg.isEmpty()) {
145             throw this.flash().redirect(
146                 this.uriInfo().getBaseUriBuilder()
147                     .clone()
148                     .path(LocksRs.class)
149                     .build(),
150                 String.format("lock %s added successfully", name),
151                 Level.INFO
152             );
153         }
154         throw new WebApplicationException(
155             Response.status(HttpURLConnection.HTTP_CONFLICT)
156                 .entity(msg)
157                 .build()
158         );
159     }
160 
161     /**
162      * Unlock.
163      * @param name Name of the lock
164      * @param label Optional label
165      * @throws IOException If fails
166      */
167     @GET
168     @Path("/unlock")
169     public void unlock(@QueryParam(LocksRs.PARAM) final String name,
170         @QueryParam("label") final String label)
171         throws IOException {
172         if (StringUtils.isEmpty(label)) {
173             this.user().locks().unlock(name);
174         } else {
175             final String match = this.user().locks().unlock(name, label);
176             if (!match.isEmpty()) {
177                 throw new WebApplicationException(
178                     Response.status(HttpURLConnection.HTTP_CONFLICT)
179                         .entity(String.format("label doesn't match: %s", match))
180                         .build()
181                 );
182             }
183         }
184         throw this.flash().redirect(
185             this.uriInfo().getBaseUriBuilder()
186                 .clone()
187                 .path(LocksRs.class)
188                 .build(),
189             String.format("%s lock removed", name),
190             Level.INFO
191         );
192     }
193 
194     /**
195      * Get all locks of a user.
196      * @return Locks
197      * @throws IOException If fails
198      */
199     private JaxbBundle list() throws IOException {
200         return new JaxbBundle("locks").add(
201             new JaxbBundle.Group<Map.Entry<String, String>>(
202                 this.user().locks().names().entrySet()
203             ) {
204                 @Override
205                 public JaxbBundle bundle(final Map.Entry<String, String> ent) {
206                     return new JaxbBundle("lock")
207                         .add("name", ent.getKey()).up()
208                         .add("label", ent.getValue()).up();
209                 }
210             }
211         );
212     }
213 
214 }