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