001/*
002 *
003 * Licensed to the Apache Software Foundation (ASF) under one
004 * or more contributor license agreements.  See the NOTICE file
005 * distributed with this work for additional information
006 * regarding copyright ownership.  The ASF licenses this file
007 * to you under the Apache License, Version 2.0 (the
008 * "License"); you may not use this file except in compliance
009 * with the License.  You may obtain a copy of the License at
010 *
011 *     http://www.apache.org/licenses/LICENSE-2.0
012 *
013 * Unless required by applicable law or agreed to in writing, software
014 * distributed under the License is distributed on an "AS IS" BASIS,
015 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
016 * See the License for the specific language governing permissions and
017 * limitations under the License.
018 */
019package org.apache.hadoop.hbase.rest;
020
021import java.io.IOException;
022
023import javax.servlet.ServletContext;
024import javax.ws.rs.Consumes;
025import javax.ws.rs.DELETE;
026import javax.ws.rs.GET;
027import javax.ws.rs.POST;
028import javax.ws.rs.PUT;
029import javax.ws.rs.Path;
030import javax.ws.rs.PathParam;
031import javax.ws.rs.Produces;
032import javax.ws.rs.core.Context;
033import javax.ws.rs.core.HttpHeaders;
034import javax.ws.rs.core.Response;
035import javax.ws.rs.core.UriInfo;
036
037import org.apache.hadoop.hbase.HTableDescriptor;
038import org.apache.hadoop.hbase.NamespaceDescriptor;
039import org.apache.yetus.audience.InterfaceAudience;
040import org.slf4j.Logger;
041import org.slf4j.LoggerFactory;
042import org.apache.hadoop.hbase.client.Admin;
043import org.apache.hadoop.hbase.rest.model.NamespacesInstanceModel;
044import org.apache.hadoop.hbase.rest.model.TableListModel;
045import org.apache.hadoop.hbase.rest.model.TableModel;
046
047/**
048 * Implements the following REST end points:
049 * <p>
050 * <tt>/namespaces/{namespace} GET: get namespace properties.</tt>
051 * <tt>/namespaces/{namespace} POST: create namespace.</tt>
052 * <tt>/namespaces/{namespace} PUT: alter namespace.</tt>
053 * <tt>/namespaces/{namespace} DELETE: drop namespace.</tt>
054 * <tt>/namespaces/{namespace}/tables GET: list namespace's tables.</tt>
055 * <p>
056 */
057@InterfaceAudience.Private
058public class NamespacesInstanceResource extends ResourceBase {
059
060  private static final Logger LOG = LoggerFactory.getLogger(NamespacesInstanceResource.class);
061  String namespace;
062  boolean queryTables = false;
063
064  /**
065   * Constructor for standard NamespaceInstanceResource.
066   * @throws IOException
067   */
068  public NamespacesInstanceResource(String namespace) throws IOException {
069    this(namespace, false);
070  }
071
072  /**
073   * Constructor for querying namespace table list via NamespaceInstanceResource.
074   * @throws IOException
075   */
076  public NamespacesInstanceResource(String namespace, boolean queryTables) throws IOException {
077    super();
078    this.namespace = namespace;
079    this.queryTables = queryTables;
080  }
081
082  /**
083   * Build a response for GET namespace description or GET list of namespace tables.
084   * @param context servlet context
085   * @param uriInfo (JAX-RS context variable) request URL
086   * @return A response containing NamespacesInstanceModel for a namespace descriptions and
087   * TableListModel for a list of namespace tables.
088   */
089  @GET
090  @Produces({MIMETYPE_TEXT, MIMETYPE_XML, MIMETYPE_JSON, MIMETYPE_PROTOBUF,
091    MIMETYPE_PROTOBUF_IETF})
092  public Response get(final @Context ServletContext context,
093      final @Context UriInfo uriInfo) {
094    if (LOG.isTraceEnabled()) {
095      LOG.trace("GET " + uriInfo.getAbsolutePath());
096    }
097    servlet.getMetrics().incrementRequests(1);
098
099    // Respond to list of namespace tables requests.
100    if(queryTables){
101      TableListModel tableModel = new TableListModel();
102      try{
103        HTableDescriptor[] tables = servlet.getAdmin().listTableDescriptorsByNamespace(namespace);
104        for(int i = 0; i < tables.length; i++){
105          tableModel.add(new TableModel(tables[i].getTableName().getQualifierAsString()));
106        }
107
108        servlet.getMetrics().incrementSucessfulGetRequests(1);
109        return Response.ok(tableModel).build();
110      }catch(IOException e) {
111        servlet.getMetrics().incrementFailedGetRequests(1);
112        throw new RuntimeException("Cannot retrieve table list for '" + namespace + "'.");
113      }
114    }
115
116    // Respond to namespace description requests.
117    try {
118      NamespacesInstanceModel rowModel =
119          new NamespacesInstanceModel(servlet.getAdmin(), namespace);
120      servlet.getMetrics().incrementSucessfulGetRequests(1);
121      return Response.ok(rowModel).build();
122    } catch (IOException e) {
123      servlet.getMetrics().incrementFailedGetRequests(1);
124      throw new RuntimeException("Cannot retrieve info for '" + namespace + "'.");
125    }
126  }
127
128  /**
129   * Build a response for PUT alter namespace with properties specified.
130   * @param model properties used for alter.
131   * @param uriInfo (JAX-RS context variable) request URL
132   * @return response code.
133   */
134  @PUT
135  @Consumes({MIMETYPE_XML, MIMETYPE_JSON, MIMETYPE_PROTOBUF,
136    MIMETYPE_PROTOBUF_IETF})
137  public Response put(final NamespacesInstanceModel model, final @Context UriInfo uriInfo) {
138    if (LOG.isTraceEnabled()) {
139      LOG.trace("PUT " + uriInfo.getAbsolutePath());
140    }
141    servlet.getMetrics().incrementRequests(1);
142    return processUpdate(model, true, uriInfo);
143  }
144
145  /**
146   * Build a response for PUT alter namespace with no properties specified.
147   * @param message value not used.
148   * @param headers value not used.
149   * @return response code.
150   */
151  @PUT
152  public Response putNoBody(final byte[] message,
153      final @Context UriInfo uriInfo, final @Context HttpHeaders headers) {
154    if (LOG.isTraceEnabled()) {
155      LOG.trace("PUT " + uriInfo.getAbsolutePath());
156    }
157    servlet.getMetrics().incrementRequests(1);
158    try{
159      NamespacesInstanceModel model = new NamespacesInstanceModel(namespace);
160      return processUpdate(model, true, uriInfo);
161    }catch(IOException ioe){
162      servlet.getMetrics().incrementFailedPutRequests(1);
163      throw new RuntimeException("Cannot retrieve info for '" + namespace + "'.");
164    }
165  }
166
167  /**
168   * Build a response for POST create namespace with properties specified.
169   * @param model properties used for create.
170   * @param uriInfo (JAX-RS context variable) request URL
171   * @return response code.
172   */
173  @POST
174  @Consumes({MIMETYPE_XML, MIMETYPE_JSON, MIMETYPE_PROTOBUF,
175    MIMETYPE_PROTOBUF_IETF})
176  public Response post(final NamespacesInstanceModel model,
177      final @Context UriInfo uriInfo) {
178
179    if (LOG.isTraceEnabled()) {
180      LOG.trace("POST " + uriInfo.getAbsolutePath());
181    }
182    servlet.getMetrics().incrementRequests(1);
183    return processUpdate(model, false, uriInfo);
184  }
185
186  /**
187   * Build a response for POST create namespace with no properties specified.
188   * @param message value not used.
189   * @param headers value not used.
190   * @return response code.
191   */
192  @POST
193  public Response postNoBody(final byte[] message,
194      final @Context UriInfo uriInfo, final @Context HttpHeaders headers) {
195    if (LOG.isTraceEnabled()) {
196      LOG.trace("POST " + uriInfo.getAbsolutePath());
197    }
198    servlet.getMetrics().incrementRequests(1);
199    try{
200      NamespacesInstanceModel model = new NamespacesInstanceModel(namespace);
201      return processUpdate(model, false, uriInfo);
202    }catch(IOException ioe){
203      servlet.getMetrics().incrementFailedPutRequests(1);
204      throw new RuntimeException("Cannot retrieve info for '" + namespace + "'.");
205    }
206  }
207
208  // Check that POST or PUT is valid and then update namespace.
209  private Response processUpdate(final NamespacesInstanceModel model, final boolean updateExisting,
210      final UriInfo uriInfo) {
211    if (servlet.isReadOnly()) {
212      servlet.getMetrics().incrementFailedPutRequests(1);
213      return Response.status(Response.Status.FORBIDDEN).type(MIMETYPE_TEXT)
214          .entity("Forbidden" + CRLF).build();
215    }
216
217    Admin admin = null;
218    boolean namespaceExists = false;
219    try {
220      admin = servlet.getAdmin();
221      namespaceExists = doesNamespaceExist(admin, namespace);
222    }catch (IOException e) {
223      servlet.getMetrics().incrementFailedPutRequests(1);
224      return processException(e);
225    }
226
227    // Do not allow creation if namespace already exists.
228    if(!updateExisting && namespaceExists){
229      servlet.getMetrics().incrementFailedPutRequests(1);
230      return Response.status(Response.Status.FORBIDDEN).type(MIMETYPE_TEXT).
231          entity("Namespace '" + namespace + "' already exists.  Use REST PUT " +
232          "to alter the existing namespace.").build();
233    }
234
235    // Do not allow altering if namespace does not exist.
236    if (updateExisting && !namespaceExists){
237      servlet.getMetrics().incrementFailedPutRequests(1);
238      return Response.status(Response.Status.FORBIDDEN).type(MIMETYPE_TEXT).
239          entity("Namespace '" + namespace + "' does not exist. Use " +
240          "REST POST to create the namespace.").build();
241    }
242
243    return createOrUpdate(model, uriInfo, admin, updateExisting);
244  }
245
246  // Do the actual namespace create or alter.
247  private Response createOrUpdate(final NamespacesInstanceModel model, final UriInfo uriInfo,
248      final Admin admin, final boolean updateExisting) {
249    NamespaceDescriptor.Builder builder = NamespaceDescriptor.create(namespace);
250    builder.addConfiguration(model.getProperties());
251    if(model.getProperties().size() > 0){
252      builder.addConfiguration(model.getProperties());
253    }
254    NamespaceDescriptor nsd = builder.build();
255
256    try{
257      if(updateExisting){
258        admin.modifyNamespace(nsd);
259      }else{
260        admin.createNamespace(nsd);
261      }
262    }catch (IOException e) {
263      servlet.getMetrics().incrementFailedPutRequests(1);
264      return processException(e);
265    }
266
267    servlet.getMetrics().incrementSucessfulPutRequests(1);
268    return Response.created(uriInfo.getAbsolutePath()).build();
269  }
270
271  private boolean doesNamespaceExist(Admin admin, String namespaceName) throws IOException{
272    NamespaceDescriptor[] nd = admin.listNamespaceDescriptors();
273    for(int i = 0; i < nd.length; i++){
274      if(nd[i].getName().equals(namespaceName)){
275        return true;
276      }
277    }
278    return false;
279  }
280
281  /**
282   * Build a response for DELETE delete namespace.
283   * @param message value not used.
284   * @param headers value not used.
285   * @return response code.
286   */
287  @DELETE
288  public Response deleteNoBody(final byte[] message,
289      final @Context UriInfo uriInfo, final @Context HttpHeaders headers) {
290    if (LOG.isTraceEnabled()) {
291      LOG.trace("DELETE " + uriInfo.getAbsolutePath());
292    }
293    if (servlet.isReadOnly()) {
294      servlet.getMetrics().incrementFailedDeleteRequests(1);
295      return Response.status(Response.Status.FORBIDDEN).type(MIMETYPE_TEXT)
296          .entity("Forbidden" + CRLF).build();
297    }
298
299    try{
300      Admin admin = servlet.getAdmin();
301      if (!doesNamespaceExist(admin, namespace)){
302        return Response.status(Response.Status.NOT_FOUND).type(MIMETYPE_TEXT).
303            entity("Namespace '" + namespace + "' does not exists.  Cannot " +
304            "drop namespace.").build();
305      }
306
307      admin.deleteNamespace(namespace);
308      servlet.getMetrics().incrementSucessfulDeleteRequests(1);
309      return Response.ok().build();
310
311    } catch (IOException e) {
312      servlet.getMetrics().incrementFailedDeleteRequests(1);
313      return processException(e);
314    }
315  }
316
317  /**
318   * Dispatch to NamespaceInstanceResource for getting list of tables.
319   */
320  @Path("tables")
321  public NamespacesInstanceResource getNamespaceInstanceResource(
322      final @PathParam("tables") String namespace) throws IOException {
323    return new NamespacesInstanceResource(this.namespace, true);
324  }
325}