/* * * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.hadoop.hbase.rest; import java.io.IOException; import javax.servlet.ServletContext; import javax.ws.rs.Consumes; import javax.ws.rs.DELETE; import javax.ws.rs.GET; import javax.ws.rs.POST; import javax.ws.rs.PUT; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; import javax.ws.rs.core.Context; import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.Response; import javax.ws.rs.core.UriInfo; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.hadoop.hbase.HTableDescriptor; import org.apache.hadoop.hbase.NamespaceDescriptor; import org.apache.hadoop.hbase.classification.InterfaceAudience; import org.apache.hadoop.hbase.client.Admin; import org.apache.hadoop.hbase.rest.model.NamespacesInstanceModel; import org.apache.hadoop.hbase.rest.model.TableListModel; import org.apache.hadoop.hbase.rest.model.TableModel; /** * Implements the following REST end points: * <p> * <tt>/namespaces/{namespace} GET: get namespace properties.</tt> * <tt>/namespaces/{namespace} POST: create namespace.</tt> * <tt>/namespaces/{namespace} PUT: alter namespace.</tt> * <tt>/namespaces/{namespace} DELETE: drop namespace.</tt> * <tt>/namespaces/{namespace}/tables GET: list namespace's tables.</tt> * <p> */ @InterfaceAudience.Private public class NamespacesInstanceResource extends ResourceBase { private static final Log LOG = LogFactory.getLog(NamespacesInstanceResource.class); String namespace; boolean queryTables = false; /** * Constructor for standard NamespaceInstanceResource. * @throws IOException */ public NamespacesInstanceResource(String namespace) throws IOException { this(namespace, false); } /** * Constructor for querying namespace table list via NamespaceInstanceResource. * @throws IOException */ public NamespacesInstanceResource(String namespace, boolean queryTables) throws IOException { super(); this.namespace = namespace; this.queryTables = queryTables; } /** * Build a response for GET namespace description or GET list of namespace tables. * @param context servlet context * @param uriInfo (JAX-RS context variable) request URL * @return A response containing NamespacesInstanceModel for a namespace descriptions and * TableListModel for a list of namespace tables. */ @GET @Produces({MIMETYPE_TEXT, MIMETYPE_XML, MIMETYPE_JSON, MIMETYPE_PROTOBUF, MIMETYPE_PROTOBUF_IETF}) public Response get(final @Context ServletContext context, final @Context UriInfo uriInfo) { if (LOG.isTraceEnabled()) { LOG.trace("GET " + uriInfo.getAbsolutePath()); } servlet.getMetrics().incrementRequests(1); // Respond to list of namespace tables requests. if(queryTables){ TableListModel tableModel = new TableListModel(); try{ HTableDescriptor[] tables = servlet.getAdmin().listTableDescriptorsByNamespace(namespace); for(int i = 0; i < tables.length; i++){ tableModel.add(new TableModel(tables[i].getTableName().getQualifierAsString())); } servlet.getMetrics().incrementSucessfulGetRequests(1); return Response.ok(tableModel).build(); }catch(IOException e) { servlet.getMetrics().incrementFailedGetRequests(1); throw new RuntimeException("Cannot retrieve table list for '" + namespace + "'."); } } // Respond to namespace description requests. try { NamespacesInstanceModel rowModel = new NamespacesInstanceModel(servlet.getAdmin(), namespace); servlet.getMetrics().incrementSucessfulGetRequests(1); return Response.ok(rowModel).build(); } catch (IOException e) { servlet.getMetrics().incrementFailedGetRequests(1); throw new RuntimeException("Cannot retrieve info for '" + namespace + "'."); } } /** * Build a response for PUT alter namespace with properties specified. * @param model properties used for alter. * @param uriInfo (JAX-RS context variable) request URL * @return response code. */ @PUT @Consumes({MIMETYPE_XML, MIMETYPE_JSON, MIMETYPE_PROTOBUF, MIMETYPE_PROTOBUF_IETF}) public Response put(final NamespacesInstanceModel model, final @Context UriInfo uriInfo) { if (LOG.isTraceEnabled()) { LOG.trace("PUT " + uriInfo.getAbsolutePath()); } servlet.getMetrics().incrementRequests(1); return processUpdate(model, true, uriInfo); } /** * Build a response for PUT alter namespace with no properties specified. * @param message value not used. * @param headers value not used. * @return response code. */ @PUT public Response putNoBody(final byte[] message, final @Context UriInfo uriInfo, final @Context HttpHeaders headers) { if (LOG.isTraceEnabled()) { LOG.trace("PUT " + uriInfo.getAbsolutePath()); } servlet.getMetrics().incrementRequests(1); try{ NamespacesInstanceModel model = new NamespacesInstanceModel(namespace); return processUpdate(model, true, uriInfo); }catch(IOException ioe){ servlet.getMetrics().incrementFailedPutRequests(1); throw new RuntimeException("Cannot retrieve info for '" + namespace + "'."); } } /** * Build a response for POST create namespace with properties specified. * @param model properties used for create. * @param uriInfo (JAX-RS context variable) request URL * @return response code. */ @POST @Consumes({MIMETYPE_XML, MIMETYPE_JSON, MIMETYPE_PROTOBUF, MIMETYPE_PROTOBUF_IETF}) public Response post(final NamespacesInstanceModel model, final @Context UriInfo uriInfo) { if (LOG.isTraceEnabled()) { LOG.trace("POST " + uriInfo.getAbsolutePath()); } servlet.getMetrics().incrementRequests(1); return processUpdate(model, false, uriInfo); } /** * Build a response for POST create namespace with no properties specified. * @param message value not used. * @param headers value not used. * @return response code. */ @POST public Response postNoBody(final byte[] message, final @Context UriInfo uriInfo, final @Context HttpHeaders headers) { if (LOG.isTraceEnabled()) { LOG.trace("POST " + uriInfo.getAbsolutePath()); } servlet.getMetrics().incrementRequests(1); try{ NamespacesInstanceModel model = new NamespacesInstanceModel(namespace); return processUpdate(model, false, uriInfo); }catch(IOException ioe){ servlet.getMetrics().incrementFailedPutRequests(1); throw new RuntimeException("Cannot retrieve info for '" + namespace + "'."); } } // Check that POST or PUT is valid and then update namespace. private Response processUpdate(final NamespacesInstanceModel model, final boolean updateExisting, final UriInfo uriInfo) { if (servlet.isReadOnly()) { servlet.getMetrics().incrementFailedPutRequests(1); return Response.status(Response.Status.FORBIDDEN).type(MIMETYPE_TEXT) .entity("Forbidden" + CRLF).build(); } Admin admin = null; boolean namespaceExists = false; try { admin = servlet.getAdmin(); namespaceExists = doesNamespaceExist(admin, namespace); }catch (IOException e) { servlet.getMetrics().incrementFailedPutRequests(1); return processException(e); } // Do not allow creation if namespace already exists. if(!updateExisting && namespaceExists){ servlet.getMetrics().incrementFailedPutRequests(1); return Response.status(Response.Status.FORBIDDEN).type(MIMETYPE_TEXT). entity("Namespace '" + namespace + "' already exists. Use REST PUT " + "to alter the existing namespace.").build(); } // Do not allow altering if namespace does not exist. if (updateExisting && !namespaceExists){ servlet.getMetrics().incrementFailedPutRequests(1); return Response.status(Response.Status.FORBIDDEN).type(MIMETYPE_TEXT). entity("Namespace '" + namespace + "' does not exist. Use " + "REST POST to create the namespace.").build(); } return createOrUpdate(model, uriInfo, admin, updateExisting); } // Do the actual namespace create or alter. private Response createOrUpdate(final NamespacesInstanceModel model, final UriInfo uriInfo, final Admin admin, final boolean updateExisting) { NamespaceDescriptor.Builder builder = NamespaceDescriptor.create(namespace); builder.addConfiguration(model.getProperties()); if(model.getProperties().size() > 0){ builder.addConfiguration(model.getProperties()); } NamespaceDescriptor nsd = builder.build(); try{ if(updateExisting){ admin.modifyNamespace(nsd); }else{ admin.createNamespace(nsd); } }catch (IOException e) { servlet.getMetrics().incrementFailedPutRequests(1); return processException(e); } servlet.getMetrics().incrementSucessfulPutRequests(1); return Response.created(uriInfo.getAbsolutePath()).build(); } private boolean doesNamespaceExist(Admin admin, String namespaceName) throws IOException{ NamespaceDescriptor[] nd = admin.listNamespaceDescriptors(); for(int i = 0; i < nd.length; i++){ if(nd[i].getName().equals(namespaceName)){ return true; } } return false; } /** * Build a response for DELETE delete namespace. * @param message value not used. * @param headers value not used. * @return response code. */ @DELETE public Response deleteNoBody(final byte[] message, final @Context UriInfo uriInfo, final @Context HttpHeaders headers) { if (LOG.isTraceEnabled()) { LOG.trace("DELETE " + uriInfo.getAbsolutePath()); } if (servlet.isReadOnly()) { servlet.getMetrics().incrementFailedDeleteRequests(1); return Response.status(Response.Status.FORBIDDEN).type(MIMETYPE_TEXT) .entity("Forbidden" + CRLF).build(); } try{ Admin admin = servlet.getAdmin(); if (!doesNamespaceExist(admin, namespace)){ return Response.status(Response.Status.NOT_FOUND).type(MIMETYPE_TEXT). entity("Namespace '" + namespace + "' does not exists. Cannot " + "drop namespace.").build(); } admin.deleteNamespace(namespace); servlet.getMetrics().incrementSucessfulDeleteRequests(1); return Response.ok().build(); } catch (IOException e) { servlet.getMetrics().incrementFailedDeleteRequests(1); return processException(e); } } /** * Dispatch to NamespaceInstanceResource for getting list of tables. */ @Path("tables") public NamespacesInstanceResource getNamespaceInstanceResource( final @PathParam("tables") String namespace) throws IOException { return new NamespacesInstanceResource(this.namespace, true); } }