View Javadoc

1   /*
2    * Copyright 2010 The Apache Software Foundation
3    *
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *     http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing, software
15   * distributed under the License is distributed on an "AS IS" BASIS,
16   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17   * See the License for the specific language governing permissions and
18   * limitations under the License.
19   */
20  
21  package org.apache.hadoop.hbase.rest.model;
22  
23  import java.io.IOException;
24  import java.io.Serializable;
25  import java.io.StringReader;
26  import java.io.StringWriter;
27  import java.util.ArrayList;
28  import java.util.List;
29  
30  import javax.xml.bind.annotation.XmlAttribute;
31  import javax.xml.bind.annotation.XmlElement;
32  import javax.xml.bind.annotation.XmlRootElement;
33  
34  import org.apache.hadoop.hbase.HConstants;
35  import org.apache.hadoop.hbase.client.Scan;
36  import org.apache.hadoop.hbase.filter.BinaryComparator;
37  import org.apache.hadoop.hbase.filter.BinaryPrefixComparator;
38  import org.apache.hadoop.hbase.filter.ColumnCountGetFilter;
39  import org.apache.hadoop.hbase.filter.CompareFilter;
40  import org.apache.hadoop.hbase.filter.Filter;
41  import org.apache.hadoop.hbase.filter.FilterList;
42  import org.apache.hadoop.hbase.filter.FirstKeyOnlyFilter;
43  import org.apache.hadoop.hbase.filter.InclusiveStopFilter;
44  import org.apache.hadoop.hbase.filter.PageFilter;
45  import org.apache.hadoop.hbase.filter.PrefixFilter;
46  import org.apache.hadoop.hbase.filter.QualifierFilter;
47  import org.apache.hadoop.hbase.filter.RegexStringComparator;
48  import org.apache.hadoop.hbase.filter.RowFilter;
49  import org.apache.hadoop.hbase.filter.SingleColumnValueFilter;
50  import org.apache.hadoop.hbase.filter.SkipFilter;
51  import org.apache.hadoop.hbase.filter.SubstringComparator;
52  import org.apache.hadoop.hbase.filter.ValueFilter;
53  import org.apache.hadoop.hbase.filter.WhileMatchFilter;
54  import org.apache.hadoop.hbase.filter.WritableByteArrayComparable;
55  import org.apache.hadoop.hbase.filter.CompareFilter.CompareOp;
56  import org.apache.hadoop.hbase.rest.ProtobufMessageHandler;
57  import org.apache.hadoop.hbase.rest.protobuf.generated.ScannerMessage.Scanner;
58  import org.apache.hadoop.hbase.util.Base64;
59  import org.apache.hadoop.hbase.util.Bytes;
60  
61  import com.google.protobuf.ByteString;
62  
63  import com.sun.jersey.api.json.JSONConfiguration;
64  import com.sun.jersey.api.json.JSONJAXBContext;
65  import com.sun.jersey.api.json.JSONMarshaller;
66  import com.sun.jersey.api.json.JSONUnmarshaller;
67  
68  /**
69   * A representation of Scanner parameters.
70   * 
71   * <pre>
72   * &lt;complexType name="Scanner"&gt;
73   *   &lt;sequence>
74   *     &lt;element name="column" type="base64Binary" minOccurs="0" maxOccurs="unbounded"/&gt;
75   *   &lt;/sequence&gt;
76   *   &lt;element name="filter" type="string" minOccurs="0" maxOccurs="1"&gt;&lt;/element&gt;
77   *   &lt;attribute name="startRow" type="base64Binary"&gt;&lt;/attribute&gt;
78   *   &lt;attribute name="endRow" type="base64Binary"&gt;&lt;/attribute&gt;
79   *   &lt;attribute name="batch" type="int"&gt;&lt;/attribute&gt;
80   *   &lt;attribute name="startTime" type="int"&gt;&lt;/attribute&gt;
81   *   &lt;attribute name="endTime" type="int"&gt;&lt;/attribute&gt;
82   *   &lt;attribute name="maxVersions" type="int"&gt;&lt;/attribute&gt;
83   * &lt;/complexType&gt;
84   * </pre>
85   */
86  @XmlRootElement(name="Scanner")
87  public class ScannerModel implements ProtobufMessageHandler, Serializable {
88  
89    private static final long serialVersionUID = 1L;
90  
91    private byte[] startRow = HConstants.EMPTY_START_ROW;
92    private byte[] endRow = HConstants.EMPTY_END_ROW;;
93    private List<byte[]> columns = new ArrayList<byte[]>();
94    private int batch = Integer.MAX_VALUE;
95    private long startTime = 0;
96    private long endTime = Long.MAX_VALUE;
97    private String filter = null;
98    private int maxVersions = Integer.MAX_VALUE;
99  
100   @XmlRootElement
101   static class FilterModel {
102     
103     @XmlRootElement
104     static class WritableByteArrayComparableModel {
105       @XmlAttribute public String type;
106       @XmlAttribute public String value;
107 
108       static enum ComparatorType {
109         BinaryComparator,
110         BinaryPrefixComparator,
111         RegexStringComparator,
112         SubstringComparator    
113       }
114 
115       public WritableByteArrayComparableModel() { }
116 
117       public WritableByteArrayComparableModel(
118           WritableByteArrayComparable comparator) {
119         String typeName = comparator.getClass().getSimpleName();
120         ComparatorType type = ComparatorType.valueOf(typeName);
121         this.type = typeName;
122         switch (type) {
123           case BinaryComparator:
124           case BinaryPrefixComparator:
125             this.value = Base64.encodeBytes(comparator.getValue());
126             break;
127           case RegexStringComparator:
128           case SubstringComparator:
129             this.value = Bytes.toString(comparator.getValue());
130             break;
131           default:
132             throw new RuntimeException("unhandled filter type: " + type);
133         }
134       }
135 
136       public WritableByteArrayComparable build() {
137         WritableByteArrayComparable comparator;
138         switch (ComparatorType.valueOf(type)) {
139           case BinaryComparator: {
140             comparator = new BinaryComparator(Base64.decode(value));
141           } break;
142           case BinaryPrefixComparator: {
143             comparator = new BinaryPrefixComparator(Base64.decode(value));
144           } break;
145           case RegexStringComparator: {
146             comparator = new RegexStringComparator(value);
147           } break;
148           case SubstringComparator: {
149             comparator = new SubstringComparator(value);
150           } break;
151           default: {
152             throw new RuntimeException("unhandled comparator type: " + type);
153           }
154         }
155         return comparator;
156       }
157 
158     }
159 
160     // a grab bag of fields, would have been a union if this were C
161     @XmlAttribute public String type = null;
162     @XmlAttribute public String op = null;
163     @XmlElement WritableByteArrayComparableModel comparator = null;
164     @XmlAttribute public String value = null;
165     @XmlElement public List<FilterModel> filters = null;
166     @XmlAttribute public Integer limit = null;
167     @XmlAttribute public String family = null;
168     @XmlAttribute public String qualifier = null;
169     @XmlAttribute public Boolean ifMissing = null;
170     @XmlAttribute public Boolean latestVersion = null;
171 
172     static enum FilterType {
173       ColumnCountGetFilter,
174       FilterList,
175       FirstKeyOnlyFilter,
176       InclusiveStopFilter,
177       PageFilter,
178       PrefixFilter,
179       QualifierFilter,
180       RowFilter,
181       SingleColumnValueFilter,
182       SkipFilter,
183       ValueFilter,
184       WhileMatchFilter    
185     }
186 
187     public FilterModel() { }
188     
189     public FilterModel(Filter filter) { 
190       String typeName = filter.getClass().getSimpleName();
191       FilterType type = FilterType.valueOf(typeName);
192       this.type = typeName;
193       switch (type) {
194         case ColumnCountGetFilter:
195           this.limit = ((ColumnCountGetFilter)filter).getLimit();
196           break;
197         case FilterList:
198           this.op = ((FilterList)filter).getOperator().toString();
199           this.filters = new ArrayList<FilterModel>();
200           for (Filter child: ((FilterList)filter).getFilters()) {
201             this.filters.add(new FilterModel(child));
202           }
203           break;
204         case FirstKeyOnlyFilter:
205           break;
206         case InclusiveStopFilter:
207           this.value = 
208             Base64.encodeBytes(((InclusiveStopFilter)filter).getStopRowKey());
209           break;
210         case PageFilter:
211           this.value = Long.toString(((PageFilter)filter).getPageSize());
212           break;
213         case PrefixFilter:
214           this.value = Base64.encodeBytes(((PrefixFilter)filter).getPrefix());
215           break;
216         case QualifierFilter:
217         case RowFilter:
218         case ValueFilter:
219           this.op = ((CompareFilter)filter).getOperator().toString();
220           this.comparator = 
221             new WritableByteArrayComparableModel(
222               ((CompareFilter)filter).getComparator());
223           break;
224         case SingleColumnValueFilter: {
225           SingleColumnValueFilter scvf = (SingleColumnValueFilter) filter;
226           this.family = Base64.encodeBytes(scvf.getFamily());
227           byte[] qualifier = scvf.getQualifier();
228           if (qualifier != null) {
229             this.qualifier = Base64.encodeBytes(qualifier);
230           }
231           this.op = scvf.getOperator().toString();
232           this.comparator = 
233             new WritableByteArrayComparableModel(scvf.getComparator());
234           if (scvf.getFilterIfMissing()) {
235             this.ifMissing = true;
236           }
237           if (scvf.getLatestVersionOnly()) {
238             this.latestVersion = true;
239           }
240         } break;
241         case SkipFilter:
242           this.filters = new ArrayList<FilterModel>();
243           this.filters.add(new FilterModel(((SkipFilter)filter).getFilter()));
244           break;
245         case WhileMatchFilter:
246           this.filters = new ArrayList<FilterModel>();
247           this.filters.add(
248             new FilterModel(((WhileMatchFilter)filter).getFilter()));
249           break;
250         default:
251           throw new RuntimeException("unhandled filter type " + type);
252       }
253     }
254 
255     public Filter build() {
256       Filter filter;
257       switch (FilterType.valueOf(type)) {
258       case ColumnCountGetFilter: {
259         filter = new ColumnCountGetFilter(limit);
260       } break;
261       case FilterList: {
262         List<Filter> list = new ArrayList<Filter>();
263         for (FilterModel model: filters) {
264           list.add(model.build());
265         }
266         filter = new FilterList(FilterList.Operator.valueOf(op), list);
267       } break;
268       case FirstKeyOnlyFilter: {
269         filter = new FirstKeyOnlyFilter();
270       } break;
271       case InclusiveStopFilter: {
272         filter = new InclusiveStopFilter(Base64.decode(value));
273       } break;
274       case PageFilter: {
275         filter = new PageFilter(Long.valueOf(value));
276       } break;
277       case PrefixFilter: {
278         filter = new PrefixFilter(Base64.decode(value));
279       } break;
280       case QualifierFilter: {
281         filter = new QualifierFilter(CompareOp.valueOf(op), comparator.build());
282       } break;
283       case RowFilter: {
284         filter = new RowFilter(CompareOp.valueOf(op), comparator.build());
285       } break;
286       case SingleColumnValueFilter: {
287         filter = new SingleColumnValueFilter(Base64.decode(family),
288           qualifier != null ? Base64.decode(qualifier) : null,
289           CompareOp.valueOf(op), comparator.build());
290         if (ifMissing != null) {
291           ((SingleColumnValueFilter)filter).setFilterIfMissing(ifMissing);
292         }
293         if (latestVersion != null) {
294           ((SingleColumnValueFilter)filter).setLatestVersionOnly(latestVersion);
295         }
296       } break;
297       case SkipFilter: {
298         filter = new SkipFilter(filters.get(0).build());
299       } break;
300       case ValueFilter: {
301         filter = new ValueFilter(CompareOp.valueOf(op), comparator.build());
302       } break;
303       case WhileMatchFilter: {
304         filter = new WhileMatchFilter(filters.get(0).build());
305       } break;
306       default:
307         throw new RuntimeException("unhandled filter type: " + type);
308       }
309       return filter;
310     }
311 
312   }
313 
314   /**
315    * @param s the JSON representation of the filter
316    * @return the filter
317    * @throws Exception
318    */
319   public static Filter buildFilter(String s) throws Exception {
320     JSONJAXBContext context =
321       new JSONJAXBContext(JSONConfiguration.natural().build(),
322         FilterModel.class);
323     JSONUnmarshaller unmarshaller = context.createJSONUnmarshaller();
324     FilterModel model = unmarshaller.unmarshalFromJSON(new StringReader(s),
325       FilterModel.class);
326     return model.build();
327   }
328 
329   /**
330    * @param filter the filter
331    * @return the JSON representation of the filter
332    * @throws Exception 
333    */
334   public static String stringifyFilter(final Filter filter) throws Exception {
335     JSONJAXBContext context =
336       new JSONJAXBContext(JSONConfiguration.natural().build(),
337         FilterModel.class);
338     JSONMarshaller marshaller = context.createJSONMarshaller();
339     StringWriter writer = new StringWriter();
340     marshaller.marshallToJSON(new FilterModel(filter), writer);
341     return writer.toString();
342   }
343 
344   /**
345    * @param scan the scan specification
346    * @throws Exception 
347    */
348   public static ScannerModel fromScan(Scan scan) throws Exception {
349     ScannerModel model = new ScannerModel();
350     model.setStartRow(scan.getStartRow());
351     model.setEndRow(scan.getStopRow());
352     byte[][] families = scan.getFamilies();
353     if (families != null) {
354       for (byte[] column: families) {
355         model.addColumn(column);
356       }
357     }
358     model.setStartTime(scan.getTimeRange().getMin());
359     model.setEndTime(scan.getTimeRange().getMax());
360     int caching = scan.getCaching();
361     if (caching > 0) {
362       model.setBatch(caching);
363     }
364     int maxVersions = scan.getMaxVersions();
365     if (maxVersions > 0) {
366       model.setMaxVersions(maxVersions);
367     }
368     Filter filter = scan.getFilter();
369     if (filter != null) {
370       model.setFilter(stringifyFilter(filter));
371     }
372     return model;
373   }
374 
375   /**
376    * Default constructor
377    */
378   public ScannerModel() {}
379 
380   /**
381    * Constructor
382    * @param startRow the start key of the row-range
383    * @param endRow the end key of the row-range
384    * @param columns the columns to scan
385    * @param batch the number of values to return in batch
386    * @param endTime the upper bound on timestamps of values of interest
387    * @param maxVersions the maximum number of versions to return
388    * @param filter a filter specification
389    * (values with timestamps later than this are excluded)
390    */
391   public ScannerModel(byte[] startRow, byte[] endRow, List<byte[]> columns,
392       int batch, long endTime, int maxVersions, String filter) {
393     super();
394     this.startRow = startRow;
395     this.endRow = endRow;
396     this.columns = columns;
397     this.batch = batch;
398     this.endTime = endTime;
399     this.maxVersions = maxVersions;
400     this.filter = filter;
401   }
402 
403   /**
404    * Constructor 
405    * @param startRow the start key of the row-range
406    * @param endRow the end key of the row-range
407    * @param columns the columns to scan
408    * @param batch the number of values to return in batch
409    * @param startTime the lower bound on timestamps of values of interest
410    * (values with timestamps earlier than this are excluded)
411    * @param endTime the upper bound on timestamps of values of interest
412    * (values with timestamps later than this are excluded)
413    * @param filter a filter specification
414    */
415   public ScannerModel(byte[] startRow, byte[] endRow, List<byte[]> columns,
416       int batch, long startTime, long endTime, String filter) {
417     super();
418     this.startRow = startRow;
419     this.endRow = endRow;
420     this.columns = columns;
421     this.batch = batch;
422     this.startTime = startTime;
423     this.endTime = endTime;
424     this.filter = filter;
425   }
426 
427   /**
428    * Add a column to the column set
429    * @param column the column name, as &lt;column&gt;(:&lt;qualifier&gt;)?
430    */
431   public void addColumn(byte[] column) {
432     columns.add(column);
433   }
434 
435   /**
436    * @return true if a start row was specified
437    */
438   public boolean hasStartRow() {
439     return !Bytes.equals(startRow, HConstants.EMPTY_START_ROW);
440   }
441 
442   /**
443    * @return start row
444    */
445   @XmlAttribute
446   public byte[] getStartRow() {
447     return startRow;
448   }
449 
450   /**
451    * @return true if an end row was specified
452    */
453   public boolean hasEndRow() {
454     return !Bytes.equals(endRow, HConstants.EMPTY_END_ROW);
455   }
456 
457   /**
458    * @return end row
459    */
460   @XmlAttribute
461   public byte[] getEndRow() {
462     return endRow;
463   }
464 
465   /**
466    * @return list of columns of interest in column:qualifier format, or empty for all
467    */
468   @XmlElement(name="column")
469   public List<byte[]> getColumns() {
470     return columns;
471   }
472   
473   /**
474    * @return the number of cells to return in batch
475    */
476   @XmlAttribute
477   public int getBatch() {
478     return batch;
479   }
480 
481   /**
482    * @return the lower bound on timestamps of items of interest
483    */
484   @XmlAttribute
485   public long getStartTime() {
486     return startTime;
487   }
488 
489   /**
490    * @return the upper bound on timestamps of items of interest
491    */
492   @XmlAttribute
493   public long getEndTime() {
494     return endTime;
495   }
496 
497   /**
498    * @return maximum number of versions to return
499    */
500   @XmlAttribute
501   public int getMaxVersions() {
502     return maxVersions;
503   }
504 
505   /**
506    * @return the filter specification
507    */
508   @XmlElement
509   public String getFilter() {
510     return filter;
511   }
512 
513   /**
514    * @param startRow start row
515    */
516   public void setStartRow(byte[] startRow) {
517     this.startRow = startRow;
518   }
519 
520   /**
521    * @param endRow end row
522    */
523   public void setEndRow(byte[] endRow) {
524     this.endRow = endRow;
525   }
526 
527   /**
528    * @param columns list of columns of interest in column:qualifier format, or empty for all
529    */
530   public void setColumns(List<byte[]> columns) {
531     this.columns = columns;
532   }
533 
534   /**
535    * @param batch the number of cells to return in batch
536    */
537   public void setBatch(int batch) {
538     this.batch = batch;
539   }
540 
541   /**
542    * @param maxVersions maximum number of versions to return
543    */
544   public void setMaxVersions(int maxVersions) {
545     this.maxVersions = maxVersions;
546   }
547 
548   /**
549    * @param startTime the lower bound on timestamps of values of interest
550    */
551   public void setStartTime(long startTime) {
552     this.startTime = startTime;
553   }
554 
555   /**
556    * @param endTime the upper bound on timestamps of values of interest
557    */
558   public void setEndTime(long endTime) {
559     this.endTime = endTime;
560   }
561 
562   /**
563    * @param filter the filter specification
564    */
565   public void setFilter(String filter) {
566     this.filter = filter;
567   }
568 
569   @Override
570   public byte[] createProtobufOutput() {
571     Scanner.Builder builder = Scanner.newBuilder();
572     if (!Bytes.equals(startRow, HConstants.EMPTY_START_ROW)) {
573       builder.setStartRow(ByteString.copyFrom(startRow));
574     }
575     if (!Bytes.equals(endRow, HConstants.EMPTY_START_ROW)) {
576       builder.setEndRow(ByteString.copyFrom(endRow));
577     }
578     for (byte[] column: columns) {
579       builder.addColumns(ByteString.copyFrom(column));
580     }
581     builder.setBatch(batch);
582     if (startTime != 0) {
583       builder.setStartTime(startTime);
584     }
585     if (endTime != 0) {
586       builder.setEndTime(endTime);
587     }
588     builder.setBatch(getBatch());
589     builder.setMaxVersions(maxVersions);
590     if (filter != null) {
591       builder.setFilter(filter);
592     }
593     return builder.build().toByteArray();
594   }
595 
596   @Override
597   public ProtobufMessageHandler getObjectFromMessage(byte[] message)
598       throws IOException {
599     Scanner.Builder builder = Scanner.newBuilder();
600     builder.mergeFrom(message);
601     if (builder.hasStartRow()) {
602       startRow = builder.getStartRow().toByteArray();
603     }
604     if (builder.hasEndRow()) {
605       endRow = builder.getEndRow().toByteArray();
606     }
607     for (ByteString column: builder.getColumnsList()) {
608       addColumn(column.toByteArray());
609     }
610     if (builder.hasBatch()) {
611       batch = builder.getBatch();
612     }
613     if (builder.hasStartTime()) {
614       startTime = builder.getStartTime();
615     }
616     if (builder.hasEndTime()) {
617       endTime = builder.getEndTime();
618     }
619     if (builder.hasMaxVersions()) {
620       maxVersions = builder.getMaxVersions();
621     }
622     if (builder.hasFilter()) {
623       filter = builder.getFilter();
624     }
625     return this;
626   }
627 
628 }