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