hadoop-common-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From st...@apache.org
Subject svn commit: r599945 - in /lucene/hadoop/trunk/src/contrib/hbase: ./ src/java/org/apache/hadoop/hbase/rest/ src/java/org/apache/hadoop/hbase/util/ src/webapps/rest/ src/webapps/rest/META-INF/ src/webapps/rest/WEB-INF/
Date Fri, 30 Nov 2007 20:15:49 GMT
Author: stack
Date: Fri Nov 30 12:15:48 2007
New Revision: 599945

URL: http://svn.apache.org/viewvc?rev=599945&view=rev
Log:
HADOOP-2068 RESTful interface

Added:
    lucene/hadoop/trunk/src/contrib/hbase/src/java/org/apache/hadoop/hbase/rest/
    lucene/hadoop/trunk/src/contrib/hbase/src/java/org/apache/hadoop/hbase/rest/Dispatcher.java
    lucene/hadoop/trunk/src/contrib/hbase/src/java/org/apache/hadoop/hbase/rest/GenericHandler.java
    lucene/hadoop/trunk/src/contrib/hbase/src/java/org/apache/hadoop/hbase/rest/MetaHandler.java
    lucene/hadoop/trunk/src/contrib/hbase/src/java/org/apache/hadoop/hbase/rest/ScannerHandler.java
    lucene/hadoop/trunk/src/contrib/hbase/src/java/org/apache/hadoop/hbase/rest/TableHandler.java
    lucene/hadoop/trunk/src/contrib/hbase/src/webapps/rest/
    lucene/hadoop/trunk/src/contrib/hbase/src/webapps/rest/META-INF/
    lucene/hadoop/trunk/src/contrib/hbase/src/webapps/rest/META-INF/MANIFEST.MF
    lucene/hadoop/trunk/src/contrib/hbase/src/webapps/rest/WEB-INF/
    lucene/hadoop/trunk/src/contrib/hbase/src/webapps/rest/WEB-INF/web.xml
Modified:
    lucene/hadoop/trunk/src/contrib/hbase/CHANGES.txt
    lucene/hadoop/trunk/src/contrib/hbase/src/java/org/apache/hadoop/hbase/util/InfoServer.java

Modified: lucene/hadoop/trunk/src/contrib/hbase/CHANGES.txt
URL: http://svn.apache.org/viewvc/lucene/hadoop/trunk/src/contrib/hbase/CHANGES.txt?rev=599945&r1=599944&r2=599945&view=diff
==============================================================================
--- lucene/hadoop/trunk/src/contrib/hbase/CHANGES.txt (original)
+++ lucene/hadoop/trunk/src/contrib/hbase/CHANGES.txt Fri Nov 30 12:15:48 2007
@@ -7,6 +7,7 @@
   NEW FEATURES
     HADOOP-2061 Add new Base64 dialects
     HADOOP-2084 Add a LocalHBaseCluster
+    HADOOP-2068 RESTful interface
 
   OPTIMIZATIONS
 

Added: lucene/hadoop/trunk/src/contrib/hbase/src/java/org/apache/hadoop/hbase/rest/Dispatcher.java
URL: http://svn.apache.org/viewvc/lucene/hadoop/trunk/src/contrib/hbase/src/java/org/apache/hadoop/hbase/rest/Dispatcher.java?rev=599945&view=auto
==============================================================================
--- lucene/hadoop/trunk/src/contrib/hbase/src/java/org/apache/hadoop/hbase/rest/Dispatcher.java (added)
+++ lucene/hadoop/trunk/src/contrib/hbase/src/java/org/apache/hadoop/hbase/rest/Dispatcher.java Fri Nov 30 12:15:48 2007
@@ -0,0 +1,162 @@
+/**
+ * Copyright 2007 The Apache Software Foundation
+ *
+ * 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.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.hadoop.hbase.HBaseAdmin;
+import org.apache.hadoop.hbase.HBaseConfiguration;
+
+/**
+ * Servlet implementation class for hbase REST interface.
+ * Presumes container ensures single thread through here at any one time
+ * (Usually the default configuration).  In other words, code is not
+ * written thread-safe.
+ * <p>This servlet has explicit dependency on Jetty server; it uses the
+ * jetty implementation of MultipartResponse.
+ * 
+ * <p>TODO:
+ * <ul>
+ * <li>multipart/related response is not correct; the servlet setContentType
+ * is broken.  I am unable to add parameters such as boundary or start to
+ * multipart/related.  They get stripped.</li>
+ * <li>Currently creating a scanner, need to specify a column.  Need to make
+ * it so the HTable instance has current table's metadata to-hand so easy to
+ * find the list of all column families so can make up list of columns if none
+ * specified.</li>
+ * <li>Minor items are we are decoding URLs in places where probably already
+ * done and how to timeout scanners that are in the scanner list.</li>
+ * </ul>
+ * @see <a href="http://wiki.apache.org/lucene-hadoop/Hbase/HbaseRest">Hbase REST Specification</a>
+ */
+public class Dispatcher extends javax.servlet.http.HttpServlet
+implements javax.servlet.Servlet {
+  
+  private static final long serialVersionUID = 1045003206345359301L;
+  
+  private MetaHandler metaHandler;
+  private TableHandler tableHandler;
+  private ScannerHandler scannerHandler;
+
+  private static final String SCANNER = "scanner";
+  private static final String ROW = "row";
+      
+  /**
+   * Default constructor
+   */
+  public Dispatcher() {
+    super();
+  }
+
+  public void init() throws ServletException {
+    super.init();
+    
+    HBaseConfiguration conf = new HBaseConfiguration();
+    HBaseAdmin admin = null;
+    
+    try{
+      admin = new HBaseAdmin(conf);
+      metaHandler = new MetaHandler(conf, admin);
+      tableHandler = new TableHandler(conf, admin);
+      scannerHandler = new ScannerHandler(conf, admin);
+    } catch(Exception e){
+      throw new ServletException(e);
+    }
+  }
+
+  protected void doGet(HttpServletRequest request, HttpServletResponse response)
+  throws IOException, ServletException {
+    String [] pathSegments = getPathSegments(request);
+    
+    if (pathSegments.length == 0 || pathSegments[0].length() <= 0) {
+      // if it was a root request, then get some metadata about 
+      // the entire instance.
+      metaHandler.doGet(request, response, pathSegments);
+    } else {
+      // otherwise, it must be a GET request suitable for the
+      // table handler.
+      tableHandler.doGet(request, response, pathSegments);
+    }
+  }
+
+  protected void doPost(HttpServletRequest request, HttpServletResponse response)
+  throws IOException, ServletException {
+    String [] pathSegments = getPathSegments(request);
+    
+    // there should be at least two path segments (table name and row or scanner)
+    if (pathSegments.length >= 2 && pathSegments[0].length() > 0) {
+      if (pathSegments[1].toLowerCase().equals(SCANNER) &&
+          pathSegments.length >= 2) {
+        scannerHandler.doPost(request, response, pathSegments);
+        return;
+      } else if (pathSegments[1].toLowerCase().equals(ROW) && pathSegments.length >= 3) {
+        tableHandler.doPost(request, response, pathSegments);
+        return;
+      }
+    }
+
+    // if we get to this point, then no handler was matched this request.
+    GenericHandler.doNotFound(response, "No handler for " + request.getPathInfo());
+  }
+  
+
+  protected void doPut(HttpServletRequest request, HttpServletResponse response)
+  throws ServletException, IOException {
+    // Equate PUT with a POST.
+    doPost(request, response);
+  }
+
+  protected void doDelete(HttpServletRequest request,
+      HttpServletResponse response)
+  throws IOException, ServletException {
+    String [] pathSegments = getPathSegments(request);
+    
+    // must be at least two path segments (table name and row or scanner)
+    if (pathSegments.length >= 2 && pathSegments[0].length() > 0) {
+      // DELETE to a scanner requires at least three path segments
+      if (pathSegments[1].toLowerCase().equals(SCANNER) &&
+          pathSegments.length == 3 && pathSegments[2].length() > 0) {
+        scannerHandler.doDelete(request, response, pathSegments);
+        return;
+      } else if (pathSegments[1].toLowerCase().equals(ROW) &&
+          pathSegments.length >= 3) {
+        tableHandler.doDelete(request, response, pathSegments);
+        return;
+      } 
+    }
+    
+    // if we reach this point, then no handler exists for this request.
+    GenericHandler.doNotFound(response, "No handler");
+  }
+  
+  /*
+   * @param request
+   * @return request pathinfo split on the '/' ignoring the first '/' so first
+   * element in pathSegment is not the empty string.
+   */
+  private String [] getPathSegments(final HttpServletRequest request) {
+    return request.getPathInfo().substring(1).split("/");
+  }
+}
\ No newline at end of file

Added: lucene/hadoop/trunk/src/contrib/hbase/src/java/org/apache/hadoop/hbase/rest/GenericHandler.java
URL: http://svn.apache.org/viewvc/lucene/hadoop/trunk/src/contrib/hbase/src/java/org/apache/hadoop/hbase/rest/GenericHandler.java?rev=599945&view=auto
==============================================================================
--- lucene/hadoop/trunk/src/contrib/hbase/src/java/org/apache/hadoop/hbase/rest/GenericHandler.java (added)
+++ lucene/hadoop/trunk/src/contrib/hbase/src/java/org/apache/hadoop/hbase/rest/GenericHandler.java Fri Nov 30 12:15:48 2007
@@ -0,0 +1,270 @@
+/**
+ * Copyright 2007 The Apache Software Foundation
+ *
+ * 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 java.io.PrintWriter;
+import java.io.UnsupportedEncodingException;
+import java.net.URLDecoder;
+import java.util.Map;
+
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.hadoop.hbase.HBaseAdmin;
+import org.apache.hadoop.hbase.HBaseConfiguration;
+import org.apache.hadoop.hbase.HConstants;
+import org.apache.hadoop.hbase.HTable;
+import org.apache.hadoop.io.Text;
+import org.mortbay.servlet.MultiPartResponse;
+import org.znerd.xmlenc.LineBreak;
+import org.znerd.xmlenc.XMLOutputter;
+
+
+/**
+ * GenericHandler contains some basic common stuff that all the individual
+ * REST handler types take advantage of.
+ */
+public abstract class GenericHandler {
+
+  protected static final long serialVersionUID = 6939910503474376143L;
+  
+  protected HBaseConfiguration conf;
+  protected HBaseAdmin admin;
+  protected HTable table = null;
+
+  protected static final String ACCEPT = "accept";
+  protected static final String COLUMN = "column";
+  protected static final String TIMESTAMP = "timestamp";
+  protected static final String START_ROW = "start_row";
+  protected static final String END_ROW = "end_row";
+  protected static final String CONTENT_TYPE = "content-type";
+  protected static final String ROW = "row";
+  protected static final String REGIONS = "regions";
+  
+  protected final Log LOG = LogFactory.getLog(this.getClass());
+
+  public GenericHandler(HBaseConfiguration conf, HBaseAdmin admin)  {
+    this.conf = conf;
+    this.admin = admin;
+  }
+
+  /*
+   * Supported content types as enums
+   */
+  protected enum ContentType {
+    XML("text/xml"),
+    PLAIN("text/plain"),
+    MIME("multipart/related"),
+    NOT_ACCEPTABLE("");
+    
+    private final String type;
+    
+    private ContentType(final String t) {
+      this.type = t;
+    }
+    
+    @Override
+    public String toString() {
+      return this.type;
+    }
+    
+    /**
+     * Utility method used looking at Accept header content.
+     * @param t The content type to examine.
+     * @return The enum that matches the prefix of <code>t</code> or
+     * the default enum if <code>t</code> is empty.  If unsupported type, we
+     * return NOT_ACCEPTABLE.
+     */
+    public static ContentType getContentType(final String t) {
+      // Default to text/plain. Curl sends */*.
+      if (t == null || t.equals("*/*")) { 
+        return ContentType.XML;
+      }
+      String lowerCased = t.toLowerCase();
+      ContentType [] values = ContentType.values();
+      ContentType result = null;
+      for (int i = 0; i < values.length; i++) {
+        if (lowerCased.startsWith(values[i].type)) {
+          result = values[i];
+          break;
+        }
+      }
+      return result == null? NOT_ACCEPTABLE: result;
+    }
+  }
+
+  
+  /*
+   * @param o
+   * @return XMLOutputter wrapped around <code>o</code>.
+   * @throws IllegalStateException
+   * @throws IOException
+   */
+  protected XMLOutputter getXMLOutputter(final PrintWriter o)
+  throws IllegalStateException, IOException {
+    XMLOutputter outputter = new XMLOutputter(o, HConstants.UTF8_ENCODING);
+    outputter.setLineBreak(LineBreak.UNIX);
+    outputter.setIndentation(" ");
+    outputter.declaration();
+    return outputter;
+  }
+  
+  /*
+   * Write an XML element.
+   * @param outputter
+   * @param name
+   * @param value
+   * @throws IllegalStateException
+   * @throws IOException
+   */
+  protected void doElement(final XMLOutputter outputter,
+      final String name, final String value)
+  throws IllegalStateException, IOException {
+    outputter.startTag(name);
+    if (value.length() > 0) {
+      outputter.pcdata(value);
+    }
+    outputter.endTag();
+  }
+  
+  /*
+   * Set content-type, encoding, and status on passed <code>response</code>
+   * @param response
+   * @param status
+   * @param contentType
+   */
+  public static void setResponseHeader(final HttpServletResponse response,
+      final int status, final String contentType) {
+    // Container adds the charset to the HTTP content-type header.
+    response.setContentType(contentType);
+    response.setCharacterEncoding(HConstants.UTF8_ENCODING);
+    response.setStatus(status);
+  }
+
+  /*
+   * If we can't do the specified Accepts header type.
+   * @param response
+   * @throws IOException
+   */
+  public static void doNotAcceptable(final HttpServletResponse response)
+  throws IOException {
+    response.sendError(HttpServletResponse.SC_NOT_ACCEPTABLE);
+  }
+
+  /*
+   * If we can't do the specified Accepts header type.
+   * @param response
+   * @param message
+   * @throws IOException
+   */
+  public static void doNotAcceptable(final HttpServletResponse response,
+      final String message)
+  throws IOException {
+    response.sendError(HttpServletResponse.SC_NOT_ACCEPTABLE, message);
+  }
+  
+  /*
+   * Resource not found.
+   * @param response
+   * @throws IOException
+   */
+  public static void doNotFound(final HttpServletResponse response)
+  throws IOException {
+    response.sendError(HttpServletResponse.SC_NOT_FOUND);
+  }
+  
+  /*
+   * Resource not found.
+   * @param response
+   * @param msg
+   * @throws IOException
+   */
+  public static void doNotFound(final HttpServletResponse response, final String msg)
+  throws IOException {
+    response.sendError(HttpServletResponse.SC_NOT_FOUND, msg);
+  }
+
+  /*
+   * Unimplemented method.
+   * @param response
+   * @param message to send
+   * @throws IOException
+   */
+  public static void doMethodNotAllowed(final HttpServletResponse response,
+      final String message)
+  throws IOException {
+    response.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, message);
+  }
+
+  protected String getTableName(final String [] pathSegments)
+  throws UnsupportedEncodingException {
+    // Get table name?  First part of passed segment.  It can't be empty string
+    // or null because we should have tested for that before coming in here.
+    return URLDecoder.decode(pathSegments[0], HConstants.UTF8_ENCODING);
+  }
+ 
+  /*
+   * Output row columns 
+   * @param outputter
+   * @param m
+   * @throws IllegalStateException
+   * @throws IllegalArgumentException
+   * @throws IOException
+   */
+  protected void outputColumnsXml(final XMLOutputter outputter,
+      final Map<Text, byte[]> m)
+  throws IllegalStateException, IllegalArgumentException, IOException {
+    for (Map.Entry<Text, byte[]> e: m.entrySet()) {
+      outputter.startTag(COLUMN);
+      doElement(outputter, "name", e.getKey().toString());
+      // We don't know String from binary data so we always base64 encode.
+      doElement(outputter, "value",
+        org.apache.hadoop.hbase.util.Base64.encodeBytes(e.getValue()));
+      outputter.endTag();
+    }
+  }
+ 
+  protected void outputColumnsMime(final MultiPartResponse mpr,
+      final Map<Text, byte[]> m)
+  throws IOException {
+    for (Map.Entry<Text, byte[]> e: m.entrySet()) {
+      mpr.startPart("application/octet-stream",
+        new String [] {"Content-Description: " + e.getKey().toString(),
+          "Content-Transfer-Encoding: binary",
+          "Content-Length: " + e.getValue().length});
+      mpr.getOut().write(e.getValue());
+    }  
+  }
+ 
+  protected void focusTable(final String tableName) throws IOException {
+    // Do we have an HTable instance to suit?  TODO, keep a pool of
+    // instances of HTable. For now, allocate a new one each time table
+    // focus changes.
+    if (this.table == null ||
+        !this.table.getTableName().toString().equals(tableName)) {
+      if (this.table != null) {
+        this.table.close();
+      }
+      this.table = new HTable(this.conf, new Text(tableName));
+    }
+  }
+}
\ No newline at end of file

Added: lucene/hadoop/trunk/src/contrib/hbase/src/java/org/apache/hadoop/hbase/rest/MetaHandler.java
URL: http://svn.apache.org/viewvc/lucene/hadoop/trunk/src/contrib/hbase/src/java/org/apache/hadoop/hbase/rest/MetaHandler.java?rev=599945&view=auto
==============================================================================
--- lucene/hadoop/trunk/src/contrib/hbase/src/java/org/apache/hadoop/hbase/rest/MetaHandler.java (added)
+++ lucene/hadoop/trunk/src/contrib/hbase/src/java/org/apache/hadoop/hbase/rest/MetaHandler.java Fri Nov 30 12:15:48 2007
@@ -0,0 +1,106 @@
+/**
+ * Copyright 2007 The Apache Software Foundation
+ *
+ * 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 java.io.PrintWriter;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.hadoop.hbase.HBaseAdmin;
+import org.apache.hadoop.hbase.HBaseConfiguration;
+import org.apache.hadoop.hbase.HTableDescriptor;
+import org.znerd.xmlenc.XMLOutputter;
+
+
+/**
+ * MetaHandler fields all requests for metadata at the instance level. At the
+ * momment this is only GET requests to /.
+ */
+public class MetaHandler extends GenericHandler {
+
+  public MetaHandler(HBaseConfiguration conf, HBaseAdmin admin) 
+  throws ServletException{
+    super(conf, admin);
+  }
+   
+   
+   public void doGet(HttpServletRequest request, HttpServletResponse response, 
+    String[] pathSegments)
+  throws ServletException, IOException {
+    getTables(request, response);
+  }
+  
+  public void doPost(HttpServletRequest request, HttpServletResponse response, 
+    String[] pathSegments)
+  throws ServletException, IOException {
+    doMethodNotAllowed(response, "POST not allowed at /");
+  }
+  
+  public void doPut(HttpServletRequest request, HttpServletResponse response, 
+    String[] pathSegments)
+  throws ServletException, IOException {
+    doMethodNotAllowed(response, "PUT not allowed at /");    
+  }
+  
+  public void doDelete(HttpServletRequest request, HttpServletResponse response, 
+    String[] pathSegments)
+  throws ServletException, IOException {
+    doMethodNotAllowed(response, "DELETE not allowed at /");
+  }
+     
+  /*
+   * Return list of tables. 
+   * @param request
+   * @param response
+   */
+  private void getTables(final HttpServletRequest request,
+    final HttpServletResponse response)
+  throws IOException {
+    HTableDescriptor [] tables = this.admin.listTables();
+    switch (ContentType.getContentType(request.getHeader(ACCEPT))) {
+      case XML:
+        setResponseHeader(response, tables.length > 0? 200: 204,
+            ContentType.XML.toString());
+          XMLOutputter outputter = getXMLOutputter(response.getWriter());
+          outputter.startTag("tables");
+          for (int i = 0; i < tables.length; i++) {
+            doElement(outputter, "table", tables[i].getName().toString());
+          }
+          outputter.endTag();
+          outputter.endDocument();
+          outputter.getWriter().close();
+        break;
+      case PLAIN:
+        setResponseHeader(response, tables.length > 0? 200: 204,
+            ContentType.PLAIN.toString());
+          PrintWriter out = response.getWriter();
+          for (int i = 0; i < tables.length; i++) {
+            out.println(tables[i].getName().toString());
+          }
+          out.close();
+        break;
+      default:
+        doNotAcceptable(response);
+    }
+  }
+}
\ No newline at end of file

Added: lucene/hadoop/trunk/src/contrib/hbase/src/java/org/apache/hadoop/hbase/rest/ScannerHandler.java
URL: http://svn.apache.org/viewvc/lucene/hadoop/trunk/src/contrib/hbase/src/java/org/apache/hadoop/hbase/rest/ScannerHandler.java?rev=599945&view=auto
==============================================================================
--- lucene/hadoop/trunk/src/contrib/hbase/src/java/org/apache/hadoop/hbase/rest/ScannerHandler.java (added)
+++ lucene/hadoop/trunk/src/contrib/hbase/src/java/org/apache/hadoop/hbase/rest/ScannerHandler.java Fri Nov 30 12:15:48 2007
@@ -0,0 +1,325 @@
+/**
+ * Copyright 2007 The Apache Software Foundation
+ *
+ * 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 java.net.URLDecoder;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.SortedMap;
+import java.util.TreeMap;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.hadoop.hbase.HBaseAdmin;
+import org.apache.hadoop.hbase.HBaseConfiguration;
+import org.apache.hadoop.hbase.HConstants;
+import org.apache.hadoop.hbase.HScannerInterface;
+import org.apache.hadoop.hbase.HStoreKey;
+import org.apache.hadoop.hbase.util.JenkinsHash;
+import org.apache.hadoop.io.Text;
+import org.mortbay.servlet.MultiPartResponse;
+import org.znerd.xmlenc.XMLOutputter;
+
+/**
+ * ScannderHandler fields all scanner related requests. 
+ */
+public class ScannerHandler extends GenericHandler {
+
+  public ScannerHandler(HBaseConfiguration conf, HBaseAdmin admin) 
+  throws ServletException{
+    super(conf, admin);
+  }
+    
+  private class ScannerRecord {
+    private final HScannerInterface scanner;
+    private HStoreKey key = null;
+    private SortedMap<Text, byte []> value = null;
+    
+    private boolean isEmpty;
+    
+    ScannerRecord(final HScannerInterface s) {
+      this.isEmpty = false;
+      this.scanner = s;
+    }
+  
+    public HScannerInterface getScanner() {
+      return this.scanner;
+    }
+  
+    public HStoreKey getKey() {
+      return this.key;
+    }
+  
+    public SortedMap<Text, byte[]> getValue() {
+      return this.value;
+    }
+
+    public boolean isEmpty(){
+      return this.isEmpty;
+    }
+    
+    /**
+     * Call next on the scanner.
+     * @return True if more values in scanner.
+     * @throws IOException
+     */
+    public boolean next() throws IOException {
+      this.key = new HStoreKey();
+      this.value = new TreeMap<Text, byte []>();
+      this.isEmpty = !this.scanner.next(this.key, this.value);
+      return !this.isEmpty;
+    }
+  }
+  
+  /*
+   * Map of outstanding scanners keyed by scannerid.
+   */
+  private final Map<String, ScannerRecord> scanners =
+    new HashMap<String, ScannerRecord>();
+  
+  public void doGet(HttpServletRequest request, HttpServletResponse response, 
+    String[] pathSegments)
+  throws ServletException, IOException {
+    doMethodNotAllowed(response, "GET to a scanner not supported.");
+  }
+  
+  public void doPost(HttpServletRequest request, HttpServletResponse response, 
+    String[] pathSegments)
+  throws ServletException, IOException {
+    if (pathSegments.length == 2) {
+      // trying to create a scanner
+      openScanner(request, response, pathSegments);
+    }
+    else if (pathSegments.length == 3) {
+      // advancing a scanner
+      getScanner(request, response, pathSegments[2]);
+    }
+    else{
+      doNotFound(response, "No handler for request");
+    }
+  }
+  
+  public void doPut(HttpServletRequest request, HttpServletResponse response, 
+    String[] pathSegments)
+  throws ServletException, IOException {
+    doPost(request, response, pathSegments);
+  }
+  
+  public void doDelete(HttpServletRequest request, HttpServletResponse response, 
+    String[] pathSegments)
+  throws ServletException, IOException {
+    doMethodNotAllowed(response, "Not hooked back up yet!");    
+  }
+  
+  /*
+   * Advance scanner and return current position.
+   * @param request
+   * @param response
+   * @param scannerid
+   * @throws IOException
+   */
+  private void getScanner(final HttpServletRequest request,
+      final HttpServletResponse response, final String scannerid)
+  throws IOException {
+    ScannerRecord sr = this.scanners.get(scannerid);
+    if (sr == null) {
+      doNotFound(response, "No such scanner.");
+      return;
+    }
+
+    if (sr.next()) {
+      switch (ContentType.getContentType(request.getHeader(ACCEPT))) {
+        case XML:
+          outputScannerEntryXML(response, sr);
+          break;
+        case MIME:
+          outputScannerEntryMime(response, sr);
+          break;
+        default:
+          doNotAcceptable(response);
+      }
+    }
+    else{
+      this.scanners.remove(scannerid);
+      doNotFound(response, "Scanner is expended");
+    }
+  }
+
+  private void outputScannerEntryXML(final HttpServletResponse response,
+    final ScannerRecord sr)
+  throws IOException {
+    HStoreKey key = sr.getKey();
+    
+    // respond with a 200 and Content-type: text/xml
+    setResponseHeader(response, 200, ContentType.XML.toString());
+    
+    // setup an xml outputter
+    XMLOutputter outputter = getXMLOutputter(response.getWriter());
+    
+    outputter.startTag(ROW);
+    
+    // write the row key
+    doElement(outputter, "name", key.getRow().toString());
+    
+    // Normally no column is supplied when scanning.
+    if (key.getColumn() != null &&
+        key.getColumn().getLength() > 0) {
+      doElement(outputter, "key-column", key.getColumn().toString());
+    }
+    
+    doElement(outputter, "timestamp", Long.toString(key.getTimestamp()));
+    
+    outputColumnsXml(outputter, sr.getValue());
+    outputter.endTag();
+    outputter.endDocument();
+    outputter.getWriter().close();
+  }
+
+  private void outputScannerEntryMime(final HttpServletResponse response,
+    final ScannerRecord sr)
+  throws IOException {
+    response.setStatus(200);
+    // This code ties me to the jetty server.
+    MultiPartResponse mpr = new MultiPartResponse(response);
+    // Content type should look like this for multipart:
+    // Content-type: multipart/related;start="<rootpart*94ebf1e6-7eb5-43f1-85f4-2615fc40c5d6@example.jaxws.sun.com>";type="application/xop+xml";boundary="uuid:94ebf1e6-7eb5-43f1-85f4-2615fc40c5d6";start-info="text/xml"
+    String ct = ContentType.MIME.toString() + ";charset=\"UTF-8\";boundary=\"" +
+      mpr.getBoundary() + "\"";
+    // Setting content type is broken.  I'm unable to set parameters on the
+    // content-type; They get stripped.  Can't set boundary, etc.
+    // response.addHeader("Content-Type", ct);
+    response.setContentType(ct);
+    // Write row, key-column and timestamp each in its own part.
+    mpr.startPart("application/octet-stream",
+        new String [] {"Content-Description: row",
+          "Content-Transfer-Encoding: binary",
+          "Content-Length: " + sr.getKey().getRow().getBytes().length});
+    mpr.getOut().write(sr.getKey().getRow().getBytes());
+
+    // Usually key-column is empty when scanning.
+    if (sr.getKey().getColumn() != null &&
+        sr.getKey().getColumn().getLength() > 0) {
+      mpr.startPart("application/octet-stream",
+        new String [] {"Content-Description: key-column",
+          "Content-Transfer-Encoding: binary",
+          "Content-Length: " + sr.getKey().getColumn().getBytes().length});
+    }
+    mpr.getOut().write(sr.getKey().getColumn().getBytes());
+    // TODO: Fix. Need to write out the timestamp in the ordained timestamp
+    // format.
+    byte [] timestampBytes = Long.toString(sr.getKey().getTimestamp()).getBytes();
+    mpr.startPart("application/octet-stream",
+        new String [] {"Content-Description: timestamp",
+          "Content-Transfer-Encoding: binary",
+          "Content-Length: " + timestampBytes.length});
+    mpr.getOut().write(timestampBytes);
+    // Write out columns
+    outputColumnsMime(mpr, sr.getValue());
+    mpr.close();
+  }
+  
+  /*
+   * Create scanner
+   * @param request
+   * @param response
+   * @param pathSegments
+   * @throws IOException
+   */
+  private void openScanner(final HttpServletRequest request,
+      final HttpServletResponse response, final String [] pathSegments)
+  throws IOException, ServletException {
+    // focus on the table
+    focusTable(getTableName(pathSegments));
+    
+    // get the list of columns we're supposed to interact with
+    String[] raw_columns = request.getParameterValues(COLUMN);
+    Text [] columns = null;
+    
+    if (raw_columns != null) {
+      columns = new Text [raw_columns.length];
+      for (int i = 0; i < raw_columns.length; i++) {
+        // I think this decoding is redundant.
+        columns[i] =
+          new Text(URLDecoder.decode(raw_columns[i], HConstants.UTF8_ENCODING));
+      }
+    } else {
+      // TODO: Need to put into the scanner all of the table's column
+      // families.  TODO: Verify this returns all rows.  For now just fail.
+      doMethodNotAllowed(response, "Unspecified columns parameter currently not supported!");
+      return;
+    }
+
+    // TODO: Parse according to the timestamp format we agree on.    
+    String raw_ts = request.getParameter(TIMESTAMP);
+
+    // TODO: Are these decodings redundant?
+    Text startRow = request.getParameter(START_ROW) == null?
+      HConstants.EMPTY_START_ROW:
+      new Text(URLDecoder.decode(request.getParameter(START_ROW),
+        HConstants.UTF8_ENCODING));
+    // Empty start row is same value as empty end row.
+    Text endRow = request.getParameter(END_ROW) == null?
+      HConstants.EMPTY_START_ROW:
+      new Text(URLDecoder.decode(request.getParameter(END_ROW),
+        HConstants.UTF8_ENCODING));
+
+    HScannerInterface scanner = (request.getParameter(END_ROW) == null)?
+       this.table.obtainScanner(columns, startRow):
+       this.table.obtainScanner(columns, startRow, endRow);
+    
+    // Make a scanner id by hashing the object toString value (object name +
+    // an id).  Will make identifier less burdensome and more url friendly.
+    String scannerid =
+      Integer.toHexString(JenkinsHash.hash(scanner.toString().getBytes(), -1));
+    ScannerRecord sr = new ScannerRecord(scanner);
+    
+    // store the scanner for subsequent requests
+    this.scanners.put(scannerid, sr);
+    
+    // set a 201 (Created) header and a Location pointing to the new
+    // scanner
+    response.setStatus(201);
+    response.addHeader("Location", request.getContextPath() + "/" +
+      pathSegments[0] + "/" + pathSegments[1] + "/" + scannerid);
+    response.getOutputStream().close();
+  }
+
+  /*
+   * Delete scanner
+   * @param response
+   * @param scannerid
+   * @throws IOException
+   */
+  private void deleteScanner(final HttpServletResponse response,
+      final String scannerid)
+  throws IOException, ServletException {
+    ScannerRecord sr = this.scanners.remove(scannerid);
+    if (sr == null) {
+      doNotFound(response, "No such scanner");
+    } else {
+      sr.getScanner().close();
+      response.setStatus(200);
+      response.getOutputStream().close();
+    }
+  }
+}
\ No newline at end of file

Added: lucene/hadoop/trunk/src/contrib/hbase/src/java/org/apache/hadoop/hbase/rest/TableHandler.java
URL: http://svn.apache.org/viewvc/lucene/hadoop/trunk/src/contrib/hbase/src/java/org/apache/hadoop/hbase/rest/TableHandler.java?rev=599945&view=auto
==============================================================================
--- lucene/hadoop/trunk/src/contrib/hbase/src/java/org/apache/hadoop/hbase/rest/TableHandler.java (added)
+++ lucene/hadoop/trunk/src/contrib/hbase/src/java/org/apache/hadoop/hbase/rest/TableHandler.java Fri Nov 30 12:15:48 2007
@@ -0,0 +1,474 @@
+/**
+ * Copyright 2007 The Apache Software Foundation
+ *
+ * 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 java.io.PrintWriter;
+import java.net.URLDecoder;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+
+import org.apache.hadoop.hbase.HBaseAdmin;
+import org.apache.hadoop.hbase.HBaseConfiguration;
+import org.apache.hadoop.hbase.HColumnDescriptor;
+import org.apache.hadoop.hbase.HConstants;
+import org.apache.hadoop.hbase.HTableDescriptor;
+import org.apache.hadoop.io.Text;
+import org.mortbay.servlet.MultiPartResponse;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+import org.znerd.xmlenc.XMLOutputter;
+
+
+/**
+ * TableHandler fields all requests that deal with an individual table.
+ * That means all requests that start with /api/[table_name]/... go to 
+ * this handler.
+ */
+public class TableHandler extends GenericHandler {
+     
+  public TableHandler(HBaseConfiguration conf, HBaseAdmin admin) 
+  throws ServletException{
+    super(conf, admin);
+  }
+  
+  public void doGet(HttpServletRequest request, HttpServletResponse response, 
+    String[] pathSegments)
+  throws ServletException, IOException {
+    // if it's just table name, return the metadata
+    if (pathSegments.length == 1) {
+      getTableMetadata(request, response, pathSegments[0]);
+    }
+    else{
+      focusTable(pathSegments[0]);
+      if (pathSegments[1].toLowerCase().equals(REGIONS)) {
+        // get a region list
+        getTableRegions(request, response);
+      }
+      else if (pathSegments[1].toLowerCase().equals(ROW)) {
+        // get a row
+        getRow(request, response, pathSegments);
+      }
+      else{
+        doNotFound(response, "Not handled in TableHandler");
+      }
+    }
+  }
+  
+  public void doPost(HttpServletRequest request, HttpServletResponse response, 
+    String[] pathSegments)
+  throws ServletException, IOException {
+    putRow(request, response, pathSegments);
+  }
+  
+  public void doPut(HttpServletRequest request, HttpServletResponse response, 
+    String[] pathSegments)
+  throws ServletException, IOException {
+    doPost(request, response, pathSegments);
+  }
+  
+  public void doDelete(HttpServletRequest request, HttpServletResponse response, 
+    String[] pathSegments)
+  throws ServletException, IOException {
+    deleteRow(request, response, pathSegments);
+  }
+  
+  /*
+   * @param request
+   * @param response
+   * @param pathSegments info path split on the '/' character.  First segment
+   * is the tablename, second is 'row', and third is the row id.
+   * @throws IOException
+   * Retrieve a row in one of several output formats.
+   */
+  private void getRow(final HttpServletRequest request,
+    final HttpServletResponse response, final String [] pathSegments)
+  throws IOException {
+    // pull the row key out of the path
+    String row = URLDecoder.decode(pathSegments[2], HConstants.UTF8_ENCODING);
+
+    String timestampStr = null;
+    if (pathSegments.length == 4) {
+      // A timestamp has been supplied.
+      timestampStr = pathSegments[3];
+      if (timestampStr.equals("timestamps")) {
+        // Not supported in hbase just yet. TODO
+        doMethodNotAllowed(response, "Not yet supported by hbase");
+        return;
+      }
+    }
+    
+    String[] columns = request.getParameterValues(COLUMN);
+        
+    if (columns == null || columns.length == 0) {
+      // They want full row returned. 
+
+      // Presumption is that this.table has already been focused on target table.
+      Map<Text, byte[]> result = timestampStr == null ? 
+        this.table.getRow(new Text(row)) 
+        : this.table.getRow(new Text(row), Long.parseLong(timestampStr));
+        
+      if (result == null || result.size() == 0) {
+        doNotFound(response, "Row not found!");
+      } else {
+        switch (ContentType.getContentType(request.getHeader(ACCEPT))) {
+        case XML:
+          outputRowXml(response, result);
+          break;
+        case MIME:
+          outputRowMime(response, result);
+          break;
+        default:
+          doNotAcceptable(response, "Unsupported Accept Header Content: " +
+            request.getHeader(CONTENT_TYPE));
+        }
+      }
+    } else {
+       Map<Text, byte[]> prefiltered_result = this.table.getRow(new Text(row));
+    
+      if (prefiltered_result == null || prefiltered_result.size() == 0) {
+        doNotFound(response, "Row not found!");
+      } else {
+        // create a Set from the columns requested so we can
+        // efficiently filter the actual found columns
+        Set<String> requested_columns_set = new HashSet<String>();
+        for(int i = 0; i < columns.length; i++){
+          requested_columns_set.add(columns[i]);
+        }
+  
+        // output map that will contain the filtered results
+        Map<Text, byte[]> m = new HashMap<Text, byte[]>();
+
+        // get an array of all the columns retrieved
+        Object[] columns_retrieved = prefiltered_result.keySet().toArray();
+
+        // copy over those cells with requested column names
+        for(int i = 0; i < columns_retrieved.length; i++){
+          Text current_column = (Text)columns_retrieved[i];
+          if(requested_columns_set.contains(current_column.toString())){
+            m.put(current_column, prefiltered_result.get(current_column));            
+          }
+        }
+        
+        switch (ContentType.getContentType(request.getHeader(ACCEPT))) {
+          case XML:
+            outputRowXml(response, m);
+            break;
+          case MIME:
+            outputRowMime(response, m);
+            break;
+          default:
+            doNotAcceptable(response, "Unsupported Accept Header Content: " +
+              request.getHeader(CONTENT_TYPE));
+        }
+      }
+    }
+  }
+  
+  /*
+   * Output a row encoded as XML.
+   * @param response
+   * @param result
+   * @throws IOException
+   */
+  private void outputRowXml(final HttpServletResponse response,
+      final Map<Text, byte[]> result)
+  throws IOException {
+    setResponseHeader(response, result.size() > 0? 200: 204,
+        ContentType.XML.toString());
+    XMLOutputter outputter = getXMLOutputter(response.getWriter());
+    outputter.startTag(ROW);
+    outputColumnsXml(outputter, result);
+    outputter.endTag();
+    outputter.endDocument();
+    outputter.getWriter().close();
+  }
+
+  /*
+   * @param response
+   * @param result
+   * Output the results contained in result as a multipart/related response.
+   */
+  private void outputRowMime(final HttpServletResponse response,
+      final Map<Text, byte[]> result)
+  throws IOException {
+    response.setStatus(result.size() > 0? 200: 204);
+    // This code ties me to the jetty server.
+    MultiPartResponse mpr = new MultiPartResponse(response);
+    // Content type should look like this for multipart:
+    // Content-type: multipart/related;start="<rootpart*94ebf1e6-7eb5-43f1-85f4-2615fc40c5d6@example.jaxws.sun.com>";type="application/xop+xml";boundary="uuid:94ebf1e6-7eb5-43f1-85f4-2615fc40c5d6";start-info="text/xml"
+    String ct = ContentType.MIME.toString() + ";charset=\"UTF-8\";boundary=\"" +
+      mpr.getBoundary() + "\"";
+    // Setting content type is broken.  I'm unable to set parameters on the
+    // content-type; They get stripped.  Can't set boundary, etc.
+    // response.addHeader("Content-Type", ct);
+    response.setContentType(ct);
+    outputColumnsMime(mpr, result);
+    mpr.close();
+  }
+  
+  /*
+   * @param request
+   * @param response
+   * @param pathSegments
+   * Do a put based on the client request.
+   */
+  private void putRow(final HttpServletRequest request,
+    final HttpServletResponse response, final String [] pathSegments)
+  throws IOException, ServletException {
+    focusTable(pathSegments[0]);
+    switch(ContentType.getContentType(request.getHeader(CONTENT_TYPE))) {
+      case XML:
+        putRowXml(request, response, pathSegments);
+        break;
+      case MIME:
+        doNotAcceptable(response);
+        break;
+      default:
+        doNotAcceptable(response, "Unsupported Accept Header Content: " +
+          request.getHeader(CONTENT_TYPE));
+    }
+  }
+
+  /*
+   * @param request
+   * @param response
+   * @param pathSegments
+   * Decode supplied XML and do a put to Hbase.
+   */
+  private void putRowXml(final HttpServletRequest request,
+    final HttpServletResponse response, final String [] pathSegments)
+  throws IOException, ServletException{
+
+    DocumentBuilderFactory docBuilderFactory 
+      = DocumentBuilderFactory.newInstance();  
+    //ignore all comments inside the xml file
+    docBuilderFactory.setIgnoringComments(true);
+
+    DocumentBuilder builder = null;
+    Document doc = null;
+    
+    String timestamp = pathSegments.length >= 4 ? pathSegments[3] : null;
+    
+    try{
+      builder = docBuilderFactory.newDocumentBuilder();
+      doc = builder.parse(request.getInputStream());
+    } catch (javax.xml.parsers.ParserConfigurationException e) {
+      throw new ServletException(e);
+    } catch (org.xml.sax.SAXException e){
+      throw new ServletException(e);
+    }
+
+    long lock_id = -1;
+    
+    try{
+      // start an update
+      Text key = new Text(pathSegments[2]);
+      lock_id = this.table.startUpdate(key);
+
+      // set the columns from the xml
+      NodeList columns = doc.getElementsByTagName("column");
+
+      for(int i = 0; i < columns.getLength(); i++){
+        // get the current column element we're working on
+        Element column = (Element)columns.item(i);
+
+        // extract the name and value children
+        Node name_node = column.getElementsByTagName("name").item(0);
+        Text name = new Text(name_node.getFirstChild().getNodeValue());
+
+        Node value_node = column.getElementsByTagName("value").item(0);
+
+        // decode the base64'd value
+        byte[] value = org.apache.hadoop.hbase.util.Base64.decode(value_node.getFirstChild().getNodeValue());
+
+        // put the value
+        this.table.put(lock_id, name, value);
+      }
+
+      // commit the update
+      if (timestamp != null) {
+        this.table.commit(lock_id, Long.parseLong(timestamp));
+      }
+      else{
+        this.table.commit(lock_id);      
+      }
+
+      // respond with a 200
+      response.setStatus(200);      
+    }
+    catch(Exception e){
+      if (lock_id != -1) {
+        this.table.abort(lock_id);
+      }
+      throw new ServletException(e);
+    }
+  }
+
+  /*
+   * Return region offsets.
+   * @param request
+   * @param response
+   */
+  private void getTableRegions(final HttpServletRequest request,
+      final HttpServletResponse response)
+  throws IOException {
+    // Presumption is that this.table has already been focused on target table.
+    Text [] startKeys = this.table.getStartKeys();
+    // Presumption is that this.table has already been set against target table
+    switch (ContentType.getContentType(request.getHeader(ACCEPT))) {
+      case XML:
+        setResponseHeader(response, startKeys.length > 0? 200: 204,
+            ContentType.XML.toString());
+          XMLOutputter outputter = getXMLOutputter(response.getWriter());
+          outputter.startTag("regions");
+          for (int i = 0; i < startKeys.length; i++) {
+            doElement(outputter, "region", startKeys[i].toString());
+          }
+          outputter.endTag();
+          outputter.endDocument();
+          outputter.getWriter().close();
+        break;
+      case PLAIN:
+        setResponseHeader(response, startKeys.length > 0? 200: 204,
+            ContentType.PLAIN.toString());
+          PrintWriter out = response.getWriter();
+          for (int i = 0; i < startKeys.length; i++) {
+            // TODO: Add in the server location.  Is it needed?
+            out.print(startKeys[i].toString());
+          }
+          out.close();
+        break;
+      case MIME:
+      default:
+        doNotAcceptable(response, "Unsupported Accept Header Content: " +
+          request.getHeader(CONTENT_TYPE));
+    }
+  }
+  /*
+   * Get table metadata.
+   * @param request
+   * @param response
+   * @param tableName
+   * @throws IOException
+   */
+  private void getTableMetadata(final HttpServletRequest request,
+      final HttpServletResponse response, final String tableName)
+  throws IOException {
+    HTableDescriptor [] tables = this.admin.listTables();
+    HTableDescriptor descriptor = null;
+    for (int i = 0; i < tables.length; i++) {
+      if (tables[i].getName().toString().equals(tableName)) {
+        descriptor = tables[i];
+        break;
+      }
+    }
+    if (descriptor == null) {
+      doNotFound(response, "Table not found!");
+    } else {
+      // Presumption is that this.table has already been set against target table
+      ContentType type = ContentType.getContentType(request.getHeader(ACCEPT));
+      switch (type) {
+      case XML:
+        setResponseHeader(response, 200, ContentType.XML.toString());
+        XMLOutputter outputter = getXMLOutputter(response.getWriter());
+        outputter.startTag("table");
+        doElement(outputter, "name", descriptor.getName().toString());
+        outputter.startTag("columnfamilies");
+        for (Map.Entry<Text, HColumnDescriptor> e:
+            descriptor.getFamilies().entrySet()) {
+          outputter.startTag("columnfamily");
+          doElement(outputter, "name", e.getKey().toString());
+          HColumnDescriptor hcd = e.getValue();
+          doElement(outputter, "compression", hcd.getCompression().toString());
+          doElement(outputter, "bloomfilter",
+            hcd.getBloomFilter() == null? "NONE": hcd.getBloomFilter().toString());
+          doElement(outputter, "max-versions",
+            Integer.toString(hcd.getMaxVersions()));
+          doElement(outputter, "maximum-cell-size",
+              Integer.toString(hcd.getMaxValueLength()));
+          outputter.endTag();
+        }
+        outputter.endTag();
+        outputter.endTag();
+        outputter.endDocument();
+        outputter.getWriter().close();
+        break;
+      case PLAIN:
+        setResponseHeader(response, 200, ContentType.PLAIN.toString());
+        PrintWriter out = response.getWriter();
+        out.print(descriptor.toString());
+        out.close();
+        break;
+      case MIME:
+      default:
+        doNotAcceptable(response, "Unsupported Accept Header Content: " +
+          request.getHeader(CONTENT_TYPE));
+      }
+    }
+  }
+  
+  /*
+   * @param request
+   * @param response
+   * @param pathSegments
+   * Delete some or all cells for a row.
+   */
+   private void deleteRow(final HttpServletRequest request,
+    final HttpServletResponse response, final String [] pathSegments)
+  throws IOException, ServletException {
+    // grab the table we're operating on
+    focusTable(getTableName(pathSegments));
+    
+    Text key = new Text(pathSegments[2]);
+
+    String[] columns = request.getParameterValues(COLUMN);
+        
+    // hack - we'll actually test for the presence of the timestamp parameter
+    // eventually
+    boolean timestamp_present = false;
+    if(timestamp_present){ // do a timestamp-aware delete
+      doMethodNotAllowed(response, "DELETE with a timestamp not implemented!");
+    }
+    else{ // ignore timestamps
+      if(columns == null || columns.length == 0){
+        // retrieve all the columns
+        doMethodNotAllowed(response,
+          "DELETE without specified columns not implemented!");
+      } else{
+        // delete each column in turn      
+        for(int i = 0; i < columns.length; i++){
+          this.table.deleteAll(key, new Text(columns[i]));
+        }
+      }
+      response.setStatus(202);
+    }
+  } 
+}
\ No newline at end of file

Modified: lucene/hadoop/trunk/src/contrib/hbase/src/java/org/apache/hadoop/hbase/util/InfoServer.java
URL: http://svn.apache.org/viewvc/lucene/hadoop/trunk/src/contrib/hbase/src/java/org/apache/hadoop/hbase/util/InfoServer.java?rev=599945&r1=599944&r2=599945&view=diff
==============================================================================
--- lucene/hadoop/trunk/src/contrib/hbase/src/java/org/apache/hadoop/hbase/util/InfoServer.java (original)
+++ lucene/hadoop/trunk/src/contrib/hbase/src/java/org/apache/hadoop/hbase/util/InfoServer.java Fri Nov 30 12:15:48 2007
@@ -89,17 +89,26 @@
     this.webServer.addContext(staticContext);
 
     // set up the context for "/" jsp files
+    String webappDir = getWebAppDir(name);
+    this.webAppContext =
+      this.webServer.addWebApplication("/", webappDir);
+    if (name.equals("master")) {
+      // Put up the rest webapp.
+      this.webServer.addWebApplication("/api", getWebAppDir("rest"));
+    }
+    addServlet("stacks", "/stacks", StatusHttpServer.StackServlet.class);
+    addServlet("logLevel", "/logLevel", org.apache.hadoop.log.LogLevel.Servlet.class);
+  }
+  
+  private String getWebAppDir(final String webappName) throws IOException {
     String webappDir = null;
     try {
-      webappDir = getWebAppsPath("webapps" + File.separator + name);
+      webappDir = getWebAppsPath("webapps" + File.separator + webappName);
     } catch (FileNotFoundException e) {
       // Retry.  Resource may be inside jar on a windows machine.
-      webappDir = getWebAppsPath("webapps/" + name);
+      webappDir = getWebAppsPath("webapps/" + webappName);
     }
-    this.webAppContext = 
-      this.webServer.addWebApplication("/", webappDir);
-    addServlet("stacks", "/stacks", StatusHttpServer.StackServlet.class);
-    addServlet("logLevel", "/logLevel", org.apache.hadoop.log.LogLevel.Servlet.class);
+    return webappDir;
   }
 
   /**

Added: lucene/hadoop/trunk/src/contrib/hbase/src/webapps/rest/META-INF/MANIFEST.MF
URL: http://svn.apache.org/viewvc/lucene/hadoop/trunk/src/contrib/hbase/src/webapps/rest/META-INF/MANIFEST.MF?rev=599945&view=auto
==============================================================================
--- lucene/hadoop/trunk/src/contrib/hbase/src/webapps/rest/META-INF/MANIFEST.MF (added)
+++ lucene/hadoop/trunk/src/contrib/hbase/src/webapps/rest/META-INF/MANIFEST.MF Fri Nov 30 12:15:48 2007
@@ -0,0 +1,9 @@
+Manifest-Version: 1.0
+Class-Path: 
+
+Manifest-Version: 1.0
+Class-Path: 
+
+Manifest-Version: 1.0
+Class-Path: 
+

Added: lucene/hadoop/trunk/src/contrib/hbase/src/webapps/rest/WEB-INF/web.xml
URL: http://svn.apache.org/viewvc/lucene/hadoop/trunk/src/contrib/hbase/src/webapps/rest/WEB-INF/web.xml?rev=599945&view=auto
==============================================================================
--- lucene/hadoop/trunk/src/contrib/hbase/src/webapps/rest/WEB-INF/web.xml (added)
+++ lucene/hadoop/trunk/src/contrib/hbase/src/webapps/rest/WEB-INF/web.xml Fri Nov 30 12:15:48 2007
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<web-app id="WebApp_ID" version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
+  <display-name>rest</display-name>
+  <servlet>
+    <description>Hbase REST Interface</description>
+    <display-name>api</display-name>
+    <servlet-name>api</servlet-name>
+    <servlet-class>org.apache.hadoop.hbase.rest.Dispatcher</servlet-class>
+  </servlet>
+  <servlet-mapping>
+    <servlet-name>api</servlet-name>
+    <url-pattern>/*</url-pattern>
+  </servlet-mapping>
+</web-app>



Mime
View raw message