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