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    return processUpdate(model, true, uriInfo);
139  }
140
141  /**
142   * Build a response for POST create namespace with properties specified.
143   * @param model properties used for create.
144   * @param uriInfo (JAX-RS context variable) request URL
145   * @return response code.
146   */
147  @POST
148  @Consumes({MIMETYPE_XML, MIMETYPE_JSON, MIMETYPE_PROTOBUF,
149    MIMETYPE_PROTOBUF_IETF})
150  public Response post(final NamespacesInstanceModel model,
151      final @Context UriInfo uriInfo) {
152    return processUpdate(model, false, uriInfo);
153  }
154
155
156  // Check that POST or PUT is valid and then update namespace.
157  private Response processUpdate(NamespacesInstanceModel model, final boolean updateExisting,
158      final UriInfo uriInfo) {
159    if (LOG.isTraceEnabled()) {
160      LOG.trace((updateExisting ? "PUT " : "POST ") + uriInfo.getAbsolutePath());
161    }
162    if (model == null) {
163      try {
164        model = new NamespacesInstanceModel(namespace);
165      } catch(IOException ioe) {
166        servlet.getMetrics().incrementFailedPutRequests(1);
167        throw new RuntimeException("Cannot retrieve info for '" + namespace + "'.");
168      }
169    }
170    servlet.getMetrics().incrementRequests(1);
171
172    if (servlet.isReadOnly()) {
173      servlet.getMetrics().incrementFailedPutRequests(1);
174      return Response.status(Response.Status.FORBIDDEN).type(MIMETYPE_TEXT)
175          .entity("Forbidden" + CRLF).build();
176    }
177
178    Admin admin = null;
179    boolean namespaceExists = false;
180    try {
181      admin = servlet.getAdmin();
182      namespaceExists = doesNamespaceExist(admin, namespace);
183    }catch (IOException e) {
184      servlet.getMetrics().incrementFailedPutRequests(1);
185      return processException(e);
186    }
187
188    // Do not allow creation if namespace already exists.
189    if(!updateExisting && namespaceExists){
190      servlet.getMetrics().incrementFailedPutRequests(1);
191      return Response.status(Response.Status.FORBIDDEN).type(MIMETYPE_TEXT).
192          entity("Namespace '" + namespace + "' already exists.  Use REST PUT " +
193          "to alter the existing namespace.").build();
194    }
195
196    // Do not allow altering if namespace does not exist.
197    if (updateExisting && !namespaceExists){
198      servlet.getMetrics().incrementFailedPutRequests(1);
199      return Response.status(Response.Status.FORBIDDEN).type(MIMETYPE_TEXT).
200          entity("Namespace '" + namespace + "' does not exist. Use " +
201          "REST POST to create the namespace.").build();
202    }
203
204    return createOrUpdate(model, uriInfo, admin, updateExisting);
205  }
206
207  // Do the actual namespace create or alter.
208  private Response createOrUpdate(final NamespacesInstanceModel model, final UriInfo uriInfo,
209      final Admin admin, final boolean updateExisting) {
210    NamespaceDescriptor.Builder builder = NamespaceDescriptor.create(namespace);
211    builder.addConfiguration(model.getProperties());
212    if(model.getProperties().size() > 0){
213      builder.addConfiguration(model.getProperties());
214    }
215    NamespaceDescriptor nsd = builder.build();
216
217    try{
218      if(updateExisting){
219        admin.modifyNamespace(nsd);
220      }else{
221        admin.createNamespace(nsd);
222      }
223    }catch (IOException e) {
224      servlet.getMetrics().incrementFailedPutRequests(1);
225      return processException(e);
226    }
227
228    servlet.getMetrics().incrementSucessfulPutRequests(1);
229
230    return updateExisting ? Response.ok(uriInfo.getAbsolutePath()).build() :
231      Response.created(uriInfo.getAbsolutePath()).build();
232  }
233
234  private boolean doesNamespaceExist(Admin admin, String namespaceName) throws IOException{
235    NamespaceDescriptor[] nd = admin.listNamespaceDescriptors();
236    for(int i = 0; i < nd.length; i++){
237      if(nd[i].getName().equals(namespaceName)){
238        return true;
239      }
240    }
241    return false;
242  }
243
244  /**
245   * Build a response for DELETE delete namespace.
246   * @param message value not used.
247   * @param headers value not used.
248   * @return response code.
249   */
250  @DELETE
251  public Response deleteNoBody(final byte[] message,
252      final @Context UriInfo uriInfo, final @Context HttpHeaders headers) {
253    if (LOG.isTraceEnabled()) {
254      LOG.trace("DELETE " + uriInfo.getAbsolutePath());
255    }
256    if (servlet.isReadOnly()) {
257      servlet.getMetrics().incrementFailedDeleteRequests(1);
258      return Response.status(Response.Status.FORBIDDEN).type(MIMETYPE_TEXT)
259          .entity("Forbidden" + CRLF).build();
260    }
261
262    try{
263      Admin admin = servlet.getAdmin();
264      if (!doesNamespaceExist(admin, namespace)){
265        return Response.status(Response.Status.NOT_FOUND).type(MIMETYPE_TEXT).
266            entity("Namespace '" + namespace + "' does not exists.  Cannot " +
267            "drop namespace.").build();
268      }
269
270      admin.deleteNamespace(namespace);
271      servlet.getMetrics().incrementSucessfulDeleteRequests(1);
272      return Response.ok().build();
273
274    } catch (IOException e) {
275      servlet.getMetrics().incrementFailedDeleteRequests(1);
276      return processException(e);
277    }
278  }
279
280  /**
281   * Dispatch to NamespaceInstanceResource for getting list of tables.
282   */
283  @Path("tables")
284  public NamespacesInstanceResource getNamespaceInstanceResource(
285      final @PathParam("tables") String namespace) throws IOException {
286    return new NamespacesInstanceResource(this.namespace, true);
287  }
288}