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 */
019
020package org.apache.hadoop.hbase.rest;
021
022import java.io.IOException;
023import java.util.Map;
024
025import javax.ws.rs.Consumes;
026import javax.ws.rs.DELETE;
027import javax.ws.rs.GET;
028import javax.ws.rs.POST;
029import javax.ws.rs.PUT;
030import javax.ws.rs.Produces;
031import javax.ws.rs.core.CacheControl;
032import javax.ws.rs.core.Context;
033import javax.ws.rs.core.Response;
034import javax.ws.rs.core.Response.ResponseBuilder;
035import javax.ws.rs.core.UriInfo;
036import javax.xml.namespace.QName;
037
038import org.apache.hadoop.hbase.HColumnDescriptor;
039import org.apache.hadoop.hbase.HTableDescriptor;
040import org.apache.hadoop.hbase.TableExistsException;
041import org.apache.hadoop.hbase.TableName;
042import org.apache.hadoop.hbase.TableNotEnabledException;
043import org.apache.hadoop.hbase.TableNotFoundException;
044import org.apache.yetus.audience.InterfaceAudience;
045import org.slf4j.Logger;
046import org.slf4j.LoggerFactory;
047import org.apache.hadoop.hbase.client.Admin;
048import org.apache.hadoop.hbase.client.Table;
049import org.apache.hadoop.hbase.rest.model.ColumnSchemaModel;
050import org.apache.hadoop.hbase.rest.model.TableSchemaModel;
051
052@InterfaceAudience.Private
053public class SchemaResource extends ResourceBase {
054  private static final Logger LOG = LoggerFactory.getLogger(SchemaResource.class);
055
056  static CacheControl cacheControl;
057  static {
058    cacheControl = new CacheControl();
059    cacheControl.setNoCache(true);
060    cacheControl.setNoTransform(false);
061  }
062
063  TableResource tableResource;
064
065  /**
066   * Constructor
067   * @param tableResource
068   * @throws IOException
069   */
070  public SchemaResource(TableResource tableResource) throws IOException {
071    super();
072    this.tableResource = tableResource;
073  }
074
075  private HTableDescriptor getTableSchema() throws IOException,
076      TableNotFoundException {
077    Table table = servlet.getTable(tableResource.getName());
078    try {
079      return table.getTableDescriptor();
080    } finally {
081      table.close();
082    }
083  }
084
085  @GET
086  @Produces({MIMETYPE_TEXT, MIMETYPE_XML, MIMETYPE_JSON, MIMETYPE_PROTOBUF,
087    MIMETYPE_PROTOBUF_IETF})
088  public Response get(final @Context UriInfo uriInfo) {
089    if (LOG.isTraceEnabled()) {
090      LOG.trace("GET " + uriInfo.getAbsolutePath());
091    }
092    servlet.getMetrics().incrementRequests(1);
093    try {
094      ResponseBuilder response =
095        Response.ok(new TableSchemaModel(getTableSchema()));
096      response.cacheControl(cacheControl);
097      servlet.getMetrics().incrementSucessfulGetRequests(1);
098      return response.build();
099    } catch (Exception e) {
100      servlet.getMetrics().incrementFailedGetRequests(1);
101      return processException(e);
102    }
103  }
104
105  private Response replace(final TableName name, final TableSchemaModel model,
106      final UriInfo uriInfo, final Admin admin) {
107    if (servlet.isReadOnly()) {
108      return Response.status(Response.Status.FORBIDDEN)
109        .type(MIMETYPE_TEXT).entity("Forbidden" + CRLF)
110        .build();
111    }
112    try {
113      HTableDescriptor htd = new HTableDescriptor(name);
114      for (Map.Entry<QName,Object> e: model.getAny().entrySet()) {
115        htd.setValue(e.getKey().getLocalPart(), e.getValue().toString());
116      }
117      for (ColumnSchemaModel family: model.getColumns()) {
118        HColumnDescriptor hcd = new HColumnDescriptor(family.getName());
119        for (Map.Entry<QName,Object> e: family.getAny().entrySet()) {
120          hcd.setValue(e.getKey().getLocalPart(), e.getValue().toString());
121        }
122        htd.addFamily(hcd);
123      }
124      if (admin.tableExists(name)) {
125        admin.disableTable(name);
126        admin.modifyTable(name, htd);
127        admin.enableTable(name);
128        servlet.getMetrics().incrementSucessfulPutRequests(1);
129      } else try {
130        admin.createTable(htd);
131        servlet.getMetrics().incrementSucessfulPutRequests(1);
132      } catch (TableExistsException e) {
133        // race, someone else created a table with the same name
134        return Response.status(Response.Status.NOT_MODIFIED)
135          .type(MIMETYPE_TEXT).entity("Not modified" + CRLF)
136          .build();
137      }
138      return Response.created(uriInfo.getAbsolutePath()).build();
139    } catch (Exception e) {
140      servlet.getMetrics().incrementFailedPutRequests(1);
141      return processException(e);
142    }
143  }
144
145  private Response update(final TableName name, final TableSchemaModel model,
146      final UriInfo uriInfo, final Admin admin) {
147    if (servlet.isReadOnly()) {
148      return Response.status(Response.Status.FORBIDDEN)
149        .type(MIMETYPE_TEXT).entity("Forbidden" + CRLF)
150        .build();
151    }
152    try {
153      HTableDescriptor htd = admin.getTableDescriptor(name);
154      admin.disableTable(name);
155      try {
156        for (ColumnSchemaModel family: model.getColumns()) {
157          HColumnDescriptor hcd = new HColumnDescriptor(family.getName());
158          for (Map.Entry<QName,Object> e: family.getAny().entrySet()) {
159            hcd.setValue(e.getKey().getLocalPart(), e.getValue().toString());
160          }
161          if (htd.hasFamily(hcd.getName())) {
162            admin.modifyColumnFamily(name, hcd);
163          } else {
164            admin.addColumnFamily(name, hcd);
165          }
166        }
167      } catch (IOException e) {
168        return Response.status(Response.Status.SERVICE_UNAVAILABLE)
169          .type(MIMETYPE_TEXT).entity("Unavailable" + CRLF)
170          .build();
171      } finally {
172        admin.enableTable(TableName.valueOf(tableResource.getName()));
173      }
174      servlet.getMetrics().incrementSucessfulPutRequests(1);
175      return Response.ok().build();
176    } catch (Exception e) {
177      servlet.getMetrics().incrementFailedPutRequests(1);
178      return processException(e);
179    }
180  }
181
182  private Response update(final TableSchemaModel model, final boolean replace,
183      final UriInfo uriInfo) {
184    try {
185      TableName name = TableName.valueOf(tableResource.getName());
186      Admin admin = servlet.getAdmin();
187      if (replace || !admin.tableExists(name)) {
188        return replace(name, model, uriInfo, admin);
189      } else {
190        return update(name, model, uriInfo, admin);
191      }
192    } catch (Exception e) {
193      servlet.getMetrics().incrementFailedPutRequests(1);
194      return processException(e);
195    }
196  }
197
198  @PUT
199  @Consumes({MIMETYPE_XML, MIMETYPE_JSON, MIMETYPE_PROTOBUF,
200    MIMETYPE_PROTOBUF_IETF})
201  public Response put(final TableSchemaModel model,
202      final @Context UriInfo uriInfo) {
203    if (LOG.isTraceEnabled()) {
204      LOG.trace("PUT " + uriInfo.getAbsolutePath());
205    }
206    servlet.getMetrics().incrementRequests(1);
207    return update(model, true, uriInfo);
208  }
209
210  @POST
211  @Consumes({MIMETYPE_XML, MIMETYPE_JSON, MIMETYPE_PROTOBUF,
212    MIMETYPE_PROTOBUF_IETF})
213  public Response post(final TableSchemaModel model,
214      final @Context UriInfo uriInfo) {
215    if (LOG.isTraceEnabled()) {
216      LOG.trace("PUT " + uriInfo.getAbsolutePath());
217    }
218    servlet.getMetrics().incrementRequests(1);
219    return update(model, false, uriInfo);
220  }
221
222  @edu.umd.cs.findbugs.annotations.SuppressWarnings(value="DE_MIGHT_IGNORE",
223      justification="Expected")
224  @DELETE
225  public Response delete(final @Context UriInfo uriInfo) {
226    if (LOG.isTraceEnabled()) {
227      LOG.trace("DELETE " + uriInfo.getAbsolutePath());
228    }
229    servlet.getMetrics().incrementRequests(1);
230    if (servlet.isReadOnly()) {
231      return Response.status(Response.Status.FORBIDDEN).type(MIMETYPE_TEXT)
232          .entity("Forbidden" + CRLF).build();
233    }
234    try {
235      Admin admin = servlet.getAdmin();
236      try {
237        admin.disableTable(TableName.valueOf(tableResource.getName()));
238      } catch (TableNotEnabledException e) { /* this is what we want anyway */ }
239      admin.deleteTable(TableName.valueOf(tableResource.getName()));
240      servlet.getMetrics().incrementSucessfulDeleteRequests(1);
241      return Response.ok().build();
242    } catch (Exception e) {
243      servlet.getMetrics().incrementFailedDeleteRequests(1);
244      return processException(e);
245    }
246  }
247}