You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sentry.apache.org by sh...@apache.org on 2013/12/14 02:20:26 UTC

[2/7] SENTRY-69: Solr Sentry e2e test framework and simple test (Gregory Chanan via Shreepadma Venugopalan)

http://git-wip-us.apache.org/repos/asf/incubator-sentry/blob/c5dd7da9/sentry-tests/sentry-tests-solr/src/test/resources/solr/collection1/conf/velocity/VM_global_library.vm
----------------------------------------------------------------------
diff --git a/sentry-tests/sentry-tests-solr/src/test/resources/solr/collection1/conf/velocity/VM_global_library.vm b/sentry-tests/sentry-tests-solr/src/test/resources/solr/collection1/conf/velocity/VM_global_library.vm
new file mode 100644
index 0000000..8e88d64
--- /dev/null
+++ b/sentry-tests/sentry-tests-solr/src/test/resources/solr/collection1/conf/velocity/VM_global_library.vm
@@ -0,0 +1,175 @@
+#**
+ *  Global macros used by other templates.
+ *  This file must be named VM_global_library.vm
+ *  in order for Velocity to find it.
+ *#
+
+#macro(param $key)$request.params.get($key)#end
+
+#macro(url_root)/solr#end
+
+## TODO: s/url_for_solr/url_for_core/ and s/url_root/url_for_solr/
+#macro(core_name)$request.core.name#end
+#macro(url_for_solr)#{url_root}#if($request.core.name != "")/$request.core.name#end#end
+#macro(url_for_home)#url_for_solr/browse#end
+
+#macro(q)&q=$!{esc.url($params.get('q'))}#end
+
+#macro(fqs $p)#foreach($fq in $p)#if($velocityCount>1)&#{end}fq=$esc.url($fq)#end#end
+
+#macro(debug)#if($request.params.get('debugQuery'))&debugQuery=true#end#end
+
+#macro(boostPrice)#if($request.params.get('bf') == 'price')&bf=price#end#end        
+
+#macro(annotate)#if($request.params.get('annotateBrowse'))&annotateBrowse=true#end#end
+
+#macro(annTitle $msg)#if($annotate == true)title="$msg"#end#end
+
+#macro(spatial)#if($request.params.get('sfield'))&sfield=store#end#if($request.params.get('pt'))&pt=$request.params.get('pt')#end#if($request.params.get('d'))&d=$request.params.get('d')#end#end
+
+#macro(qOpts)#set($queryOpts = $request.params.get("queryOpts"))#if($queryOpts && $queryOpts != "")&queryOpts=$queryOpts#end#end
+
+#macro(group)#if($request.params.getBool("group") == true)&group=true#end#if($request.params.get("group.field"))#foreach($grp in $request.params.getParams('group.field'))&group.field=$grp#end#end#end
+
+#macro(sort $p)#if($p)#foreach($s in $p)&sort=$esc.url($s)#end#end#end
+
+#macro(lensNoQ)?#if($request.params.getParams('fq') and $list.size($request.params.getParams('fq')) > 0)&#fqs($request.params.getParams('fq'))#end#sort($request.params.getParams('sort'))#debug#boostPrice#annotate#spatial#qOpts#group#end
+#macro(lens)#lensNoQ#q#end
+        
+
+#macro(url_for_lens)#{url_for_home}#lens#end
+
+#macro(url_for_start $start)#url_for_home#lens&start=$start#end
+
+#macro(url_for_filters $p)#url_for_home?#q#boostPrice#spatial#qOpts#if($list.size($p) > 0)&#fqs($p)#end#debug#end
+
+#macro(url_for_nested_facet_query $field)#url_for_home#lens&fq=$esc.url($field)#end
+
+## TODO: convert to use {!raw f=$field}$value (with escaping of course)
+#macro(url_for_facet_filter $field $value)#url_for_home#lens&fq=$esc.url($field):%22$esc.url($value)%22#end
+
+#macro(url_for_facet_date_filter $field $value)#url_for_home#lens&fq=$esc.url($field):$esc.url($value)#end
+
+#macro(url_for_facet_range_filter $field $value)#url_for_home#lens&fq=$esc.url($field):$esc.url($value)#end
+
+
+#macro(link_to_previous_page $text)
+  #if($page.current_page_number > 1)
+    #set($prev_start = $page.start - $page.results_per_page)
+    <a class="prev-page" href="#url_for_start($prev_start)">$text</a>
+  #end
+#end
+
+#macro(link_to_next_page $text)
+  #if($page.current_page_number < $page.page_count)
+    #set($next_start = $page.start + $page.results_per_page)
+    <a class="next-page" href="#url_for_start($next_start)">$text</a>
+  #end
+#end
+
+#macro(link_to_page $page_number $text)
+  #if($page_number == $page.current_page_number)
+    $text
+  #else
+    #if($page_number <= $page.page_count)
+      #set($page_start = $page_number * $page.results_per_page - $page.results_per_page)
+      <a class="page" href="#url_for_start($page_start)">$text</a>
+    #end
+  #end
+#end
+
+#macro(display_facet_query $field, $display, $fieldName)
+  #if($field.size() > 0)
+  <span class="facet-field">$display</span>
+    <ul>
+    #foreach ($facet in $field)
+      #if ($facet.value > 0)
+        #set($facetURL = "#url_for_nested_facet_query($facet.key)")
+        #if ($facetURL != '')
+          <li><a href="$facetURL">$facet.key</a> ($facet.value)</li>
+        #end
+      #end
+    #end
+    </ul>
+  #end      
+#end
+
+
+#macro(display_facet_range $field, $display, $fieldName, $start, $end, $gap, $before, $after)
+  <span class="facet-field">$display</span>
+    <ul>
+    #if($before && $before != "")
+      #set($value = "[* TO " + "#format_value($start)" + "}")
+      #set($facetURL = "#url_for_facet_range_filter($fieldName, $value)")
+      <li><a href="$facetURL">Less than #format_value($start)</a> ($before)</li>
+    #end
+    #foreach ($facet in $field)
+      #set($rangeEnd = "#range_get_to_value($facet.key, $gap)")
+      #set($value = "[" + $facet.key + " TO " + $rangeEnd + "}")
+      #set($facetURL = "#url_for_facet_range_filter($fieldName, $value)")
+      #if ($facetURL != '')
+        <li><a href="$facetURL">$facet.key - #format_value($rangeEnd)</a> ($facet.value)</li>
+      #end
+    #end
+    #if($end && $end != "" && $after > 0)
+      #set($value = "[" + "#format_value($end)" + " TO *}")
+      #set($facetURL = "#url_for_facet_range_filter($fieldName, $value)")
+      <li><a href="$facetURL">More than #format_value($end)</a> ($after)</li>
+    #end
+    </ul>
+#end
+
+## $pivots is a list of facet_pivot
+#macro(display_facet_pivot $pivots, $display)
+  #if($pivots.size() > 0)
+  <span class="facet-field">$display</span>
+    <ul>
+      #foreach ($pivot in $pivots)
+        #foreach ($entry in $pivot.value)
+          <a href="#url_for_facet_filter($entry.field, $entry.value)">$entry.field::$entry.value</a> ($entry.count)
+          <ul>
+            #foreach($nest in $entry.pivot)
+              <a href="#url_for_facet_filter($entry.field, $entry.value)&fq=$esc.url($nest.field):%22$esc.url($nest.value)%22">$nest.field::$nest.value</a> ($nest.count)
+            #end
+          </ul>
+        #end
+      #end
+    </ul>
+  #end
+#end
+
+#macro(field $f)
+  #if($response.response.highlighting.get($docId).get($f).get(0))
+    #set($pad = "")
+    #foreach($v in $response.response.highlighting.get($docId).get($f))
+$pad$v##
+      #set($pad = " ... ")
+    #end
+  #else
+    #foreach($v in $doc.getFieldValues($f))
+$v##
+    #end
+  #end
+#end  
+
+#macro(utc_date $theDate)
+$date.format("yyyy-MM-dd'T'HH:mm:ss'Z'",$theDate,$date.getLocale(),$date.getTimeZone().getTimeZone("UTC"))##
+#end
+
+#macro(format_value $val)
+#if(${val.class.name} == "java.util.Date")
+#utc_date($val)##
+#else
+$val##
+#end
+#end
+
+#macro(range_get_to_value $inval, $gapval)
+#if(${gapval.class.name} == "java.lang.String")
+$inval$gapval##
+#elseif(${gapval.class.name} == "java.lang.Float" || ${inval.class.name} == "java.lang.Float")
+$math.toDouble($math.add($inval,$gapval))##
+#else
+$math.add($inval,$gapval)##
+#end
+#end

http://git-wip-us.apache.org/repos/asf/incubator-sentry/blob/c5dd7da9/sentry-tests/sentry-tests-solr/src/test/resources/solr/collection1/conf/velocity/browse.vm
----------------------------------------------------------------------
diff --git a/sentry-tests/sentry-tests-solr/src/test/resources/solr/collection1/conf/velocity/browse.vm b/sentry-tests/sentry-tests-solr/src/test/resources/solr/collection1/conf/velocity/browse.vm
new file mode 100644
index 0000000..8e9a4ca
--- /dev/null
+++ b/sentry-tests/sentry-tests-solr/src/test/resources/solr/collection1/conf/velocity/browse.vm
@@ -0,0 +1,33 @@
+#**
+ *  Main entry point into the /browse templates
+ *#
+
+#set($searcher = $request.searcher)
+#set($params = $request.params)
+#set($clusters = $response.response.clusters)
+#set($mltResults = $response.response.get("moreLikeThis"))
+#set($annotate = $params.get("annotateBrowse"))
+#parse('query_form.vm')
+#parse('did_you_mean.vm')
+
+<div class="navigators">
+  #parse("facets.vm")
+</div>
+
+<div class="pagination">
+  #parse("pagination_top.vm")
+</div>
+
+## Show Error Message, if any
+<div class="error">
+  #parse("error.vm")
+</div>
+
+## Render Results, actual matching docs
+<div class="results">
+  #parse("results_list.vm")
+</div>
+
+<div class="pagination">
+  #parse("pagination_bottom.vm")
+</div>

http://git-wip-us.apache.org/repos/asf/incubator-sentry/blob/c5dd7da9/sentry-tests/sentry-tests-solr/src/test/resources/solr/collection1/conf/velocity/cluster.vm
----------------------------------------------------------------------
diff --git a/sentry-tests/sentry-tests-solr/src/test/resources/solr/collection1/conf/velocity/cluster.vm b/sentry-tests/sentry-tests-solr/src/test/resources/solr/collection1/conf/velocity/cluster.vm
new file mode 100644
index 0000000..30af3dc
--- /dev/null
+++ b/sentry-tests/sentry-tests-solr/src/test/resources/solr/collection1/conf/velocity/cluster.vm
@@ -0,0 +1,19 @@
+#**
+ *  Check if Clustering is Enabled and then
+ *  call cluster_results.vm
+ *#
+
+<h2 #annTitle("Clusters generated by Carrot2 using the /clustering RequestHandler")>
+  Clusters
+</h2>
+
+## Div tag has placeholder text by default
+<div id="clusters">
+  Run Solr with java -Dsolr.clustering.enabled=true -jar start.jar to see results
+</div>
+
+## Replace the div content *if* Carrot^2 is available
+<script type="text/javascript">
+  $('#clusters').load("#url_for_solr/clustering#lens",
+    {'wt':'velocity', 'v.template':"cluster_results"});
+</script>

http://git-wip-us.apache.org/repos/asf/incubator-sentry/blob/c5dd7da9/sentry-tests/sentry-tests-solr/src/test/resources/solr/collection1/conf/velocity/cluster_results.vm
----------------------------------------------------------------------
diff --git a/sentry-tests/sentry-tests-solr/src/test/resources/solr/collection1/conf/velocity/cluster_results.vm b/sentry-tests/sentry-tests-solr/src/test/resources/solr/collection1/conf/velocity/cluster_results.vm
new file mode 100644
index 0000000..66d4707
--- /dev/null
+++ b/sentry-tests/sentry-tests-solr/src/test/resources/solr/collection1/conf/velocity/cluster_results.vm
@@ -0,0 +1,31 @@
+#**
+ *  Actual rendering of Clusters
+ *#
+
+## For each cluster
+#foreach ($clusters in $response.response.clusters)
+
+  #set($labels = $clusters.get('labels'))
+  #set($docs = $clusters.get('docs'))
+
+  ## This Cluster's Heading
+  <h3>
+    #foreach ($label in $labels)
+      ## Keep the following line together to prevent
+      ## a space appearing before each comma
+      $label#if( $foreach.hasNext ),#end
+    #end
+  </h3>
+
+  ## This Cluster's Documents
+  <ol>
+    ## For each doc in this cluster
+    #foreach ($cluDoc in $docs)
+      <li>
+        <a href="#url_for_home?q=id:$cluDoc">
+          $cluDoc</a>
+      </li>
+    #end
+  </ol>
+
+#end   ## end for each Cluster

http://git-wip-us.apache.org/repos/asf/incubator-sentry/blob/c5dd7da9/sentry-tests/sentry-tests-solr/src/test/resources/solr/collection1/conf/velocity/debug.vm
----------------------------------------------------------------------
diff --git a/sentry-tests/sentry-tests-solr/src/test/resources/solr/collection1/conf/velocity/debug.vm b/sentry-tests/sentry-tests-solr/src/test/resources/solr/collection1/conf/velocity/debug.vm
new file mode 100644
index 0000000..78a795f
--- /dev/null
+++ b/sentry-tests/sentry-tests-solr/src/test/resources/solr/collection1/conf/velocity/debug.vm
@@ -0,0 +1,28 @@
+#**
+ *  Show Debugging Information, if enabled
+ *#
+
+#if( $params.getBool("debugQuery",false) )
+  <a href="#" onclick='jQuery(this).siblings("pre").toggle(); return false;'>
+    toggle explain</a>
+
+  <pre style="display:none">
+    $response.getExplainMap().get($doc.getFirstValue('id'))
+  </pre>
+
+  <a href="#" onclick='jQuery(this).siblings("pre2").toggle(); return false;'>
+    toggle all fields</a>
+
+  <pre2 style="display:none">
+    #foreach($fieldname in $doc.fieldNames)
+      <br>
+        <span class="field-name">$fieldname :</span>
+        <span>
+          #foreach($value in $doc.getFieldValues($fieldname))
+            $esc.html($value)
+          #end
+        </span>
+      </br>
+    #end
+  </pre2>
+#end

http://git-wip-us.apache.org/repos/asf/incubator-sentry/blob/c5dd7da9/sentry-tests/sentry-tests-solr/src/test/resources/solr/collection1/conf/velocity/did_you_mean.vm
----------------------------------------------------------------------
diff --git a/sentry-tests/sentry-tests-solr/src/test/resources/solr/collection1/conf/velocity/did_you_mean.vm b/sentry-tests/sentry-tests-solr/src/test/resources/solr/collection1/conf/velocity/did_you_mean.vm
new file mode 100644
index 0000000..4456fc9
--- /dev/null
+++ b/sentry-tests/sentry-tests-solr/src/test/resources/solr/collection1/conf/velocity/did_you_mean.vm
@@ -0,0 +1,9 @@
+#**
+ *  Hyperlinked spelling suggestions in results list
+ *#
+
+#set($dym = $response.response.spellcheck.suggestions.collation.collationQuery)
+#if($dym)
+  Did you mean
+  <a href="#{url_for_home}#{lensNoQ}&q=$esc.url($dym)">$esc.html($dym)</a>?
+#end

http://git-wip-us.apache.org/repos/asf/incubator-sentry/blob/c5dd7da9/sentry-tests/sentry-tests-solr/src/test/resources/solr/collection1/conf/velocity/error.vm
----------------------------------------------------------------------
diff --git a/sentry-tests/sentry-tests-solr/src/test/resources/solr/collection1/conf/velocity/error.vm b/sentry-tests/sentry-tests-solr/src/test/resources/solr/collection1/conf/velocity/error.vm
new file mode 100644
index 0000000..fe55e5f
--- /dev/null
+++ b/sentry-tests/sentry-tests-solr/src/test/resources/solr/collection1/conf/velocity/error.vm
@@ -0,0 +1,11 @@
+#**
+ *  Show Error Message, if any
+ *#
+
+## Show Error Message, if any
+## Usually rendered inside div class=error
+
+#if( $response.response.error.code )
+  <h1>ERROR $response.response.error.code</h1>
+  $response.response.error.msg
+#end

http://git-wip-us.apache.org/repos/asf/incubator-sentry/blob/c5dd7da9/sentry-tests/sentry-tests-solr/src/test/resources/solr/collection1/conf/velocity/facet_fields.vm
----------------------------------------------------------------------
diff --git a/sentry-tests/sentry-tests-solr/src/test/resources/solr/collection1/conf/velocity/facet_fields.vm b/sentry-tests/sentry-tests-solr/src/test/resources/solr/collection1/conf/velocity/facet_fields.vm
new file mode 100644
index 0000000..7b48ca2
--- /dev/null
+++ b/sentry-tests/sentry-tests-solr/src/test/resources/solr/collection1/conf/velocity/facet_fields.vm
@@ -0,0 +1,23 @@
+#**
+ *  Display facets based on field values
+ *  e.g.: fields specified by &facet.field=
+ *#
+
+#if($response.facetFields)
+  <h2 #annTitle("Facets generated by adding &facet.field= to the request")>
+    Field Facets
+  </h2>
+  #foreach($field in $response.facetFields)
+    ## Hide facets without value
+    #if($field.values.size() > 0)
+      <span class="facet-field">$field.name</span>
+      <ul>
+        #foreach($facet in $field.values)
+          <li>
+            <a href="#url_for_facet_filter($field.name, $facet.name)">$facet.name</a> ($facet.count)
+          </li>
+        #end
+      </ul>
+    #end  ## end if > 0
+  #end    ## end for each facet field
+#end      ## end if response has facet fields

http://git-wip-us.apache.org/repos/asf/incubator-sentry/blob/c5dd7da9/sentry-tests/sentry-tests-solr/src/test/resources/solr/collection1/conf/velocity/facet_pivot.vm
----------------------------------------------------------------------
diff --git a/sentry-tests/sentry-tests-solr/src/test/resources/solr/collection1/conf/velocity/facet_pivot.vm b/sentry-tests/sentry-tests-solr/src/test/resources/solr/collection1/conf/velocity/facet_pivot.vm
new file mode 100644
index 0000000..f5ba029
--- /dev/null
+++ b/sentry-tests/sentry-tests-solr/src/test/resources/solr/collection1/conf/velocity/facet_pivot.vm
@@ -0,0 +1,12 @@
+#**
+ *  Display Pivot-Based Facets
+ *  e.g.: facets specified by &facet.pivot=
+ *#
+
+<h2 #annTitle("Facets generated by adding &facet.pivot= to the request")>
+  Pivot Facets
+</h2>
+
+#set($pivot = $response.response.facet_counts.facet_pivot)
+
+#display_facet_pivot($pivot, "")

http://git-wip-us.apache.org/repos/asf/incubator-sentry/blob/c5dd7da9/sentry-tests/sentry-tests-solr/src/test/resources/solr/collection1/conf/velocity/facet_queries.vm
----------------------------------------------------------------------
diff --git a/sentry-tests/sentry-tests-solr/src/test/resources/solr/collection1/conf/velocity/facet_queries.vm b/sentry-tests/sentry-tests-solr/src/test/resources/solr/collection1/conf/velocity/facet_queries.vm
new file mode 100644
index 0000000..015c63d
--- /dev/null
+++ b/sentry-tests/sentry-tests-solr/src/test/resources/solr/collection1/conf/velocity/facet_queries.vm
@@ -0,0 +1,12 @@
+#**
+ *  Display facets based on specific facet queries
+ *  e.g.: facets specified by &facet.query=
+ *#
+
+#set($field = $response.response.facet_counts.facet_queries)
+
+<h2 #annTitle("Facets generated by adding &facet.query= to the request")>
+  Query Facets
+</h2>
+
+#display_facet_query($field, "", "")

http://git-wip-us.apache.org/repos/asf/incubator-sentry/blob/c5dd7da9/sentry-tests/sentry-tests-solr/src/test/resources/solr/collection1/conf/velocity/facet_ranges.vm
----------------------------------------------------------------------
diff --git a/sentry-tests/sentry-tests-solr/src/test/resources/solr/collection1/conf/velocity/facet_ranges.vm b/sentry-tests/sentry-tests-solr/src/test/resources/solr/collection1/conf/velocity/facet_ranges.vm
new file mode 100644
index 0000000..fb6cc26
--- /dev/null
+++ b/sentry-tests/sentry-tests-solr/src/test/resources/solr/collection1/conf/velocity/facet_ranges.vm
@@ -0,0 +1,23 @@
+#**
+ *  Display facets based on ranges of values, AKA "Bukets"
+ *  e.g.: ranges specified by &facet.range=
+ *#
+
+<h2 #annTitle("Facets generated by adding &facet.range= to the request")>
+  Range Facets
+</h2>
+
+#foreach ($field in $response.response.facet_counts.facet_ranges)
+  ## Hide facets without value
+  #if($field.value.counts.size() > 0)
+	#set($name = $field.key)
+	#set($display = $name)
+	#set($f = $field.value.counts)
+	#set($start = $field.value.start)
+	#set($end = $field.value.end)
+	#set($gap = $field.value.gap)
+	#set($before = $field.value.before)
+	#set($after = $field.value.after)
+	#display_facet_range($f, $display, $name, $start, $end, $gap, $before, $after)
+  #end  ## end if has any values
+#end    ## end for each facet range

http://git-wip-us.apache.org/repos/asf/incubator-sentry/blob/c5dd7da9/sentry-tests/sentry-tests-solr/src/test/resources/solr/collection1/conf/velocity/facets.vm
----------------------------------------------------------------------
diff --git a/sentry-tests/sentry-tests-solr/src/test/resources/solr/collection1/conf/velocity/facets.vm b/sentry-tests/sentry-tests-solr/src/test/resources/solr/collection1/conf/velocity/facets.vm
new file mode 100644
index 0000000..5a818da
--- /dev/null
+++ b/sentry-tests/sentry-tests-solr/src/test/resources/solr/collection1/conf/velocity/facets.vm
@@ -0,0 +1,10 @@
+#**
+ *  Overall Facet display block
+ *  Invokes the 4 facet and 1 cluster template
+ *#
+
+#parse('facet_fields.vm')
+#parse('facet_queries.vm')
+#parse('facet_ranges.vm')
+#parse('facet_pivot.vm')
+#parse('cluster.vm')

http://git-wip-us.apache.org/repos/asf/incubator-sentry/blob/c5dd7da9/sentry-tests/sentry-tests-solr/src/test/resources/solr/collection1/conf/velocity/footer.vm
----------------------------------------------------------------------
diff --git a/sentry-tests/sentry-tests-solr/src/test/resources/solr/collection1/conf/velocity/footer.vm b/sentry-tests/sentry-tests-solr/src/test/resources/solr/collection1/conf/velocity/footer.vm
new file mode 100644
index 0000000..2e10ac5
--- /dev/null
+++ b/sentry-tests/sentry-tests-solr/src/test/resources/solr/collection1/conf/velocity/footer.vm
@@ -0,0 +1,43 @@
+#**
+ *  Render the bottom section of the page visible to users
+ *#
+
+<hr/>
+<div>
+  <span>Options:</span>
+
+  #if($request.params.get('debugQuery'))
+    <a href="#url_for_home?#q#if($list.size($request.params.getParams('fq')) > 0)&#fqs($request.params.getParams('fq'))#end">
+      disable debug</a>
+  #else
+    <a href="#url_for_lens&debugQuery=true&fl=*,score">
+      enable debug</a>
+  #end
+  -
+  #if($annotate)
+    <a href="#url_for_home?#q#if($list.size($request.params.getParams('fq')) > 0)&#fqs($request.params.getParams('fq'))#end#boostPrice">
+      disable annotation</a>
+  #else
+    <a href="#url_for_lens&annotateBrowse=true">
+      enable annotation</a>
+  #end
+  -
+  <a #annTitle("Click to switch to an XML response: &wt=xml") href="#url_for_lens&wt=xml#if($request.params.get('debugQuery'))&debugQuery=true#end">
+    XML results</a>
+
+</div>
+
+<div>
+  Generated by <a href="http://wiki.apache.org/solr/VelocityResponseWriter">VelocityResponseWriter</a>
+</div>
+<div>
+  <span>Documentation: </span>
+  <a href="http://lucene.apache.org/solr">Solr Home Page</a>, <a href="http://wiki.apache.org/solr">
+    Solr Wiki</a>
+  </div>
+<div>
+  Disclaimer:
+  The locations displayed in this demonstration are purely fictional.
+  It is more than likely that no store with the items listed actually
+  exists at that location!
+</div>

http://git-wip-us.apache.org/repos/asf/incubator-sentry/blob/c5dd7da9/sentry-tests/sentry-tests-solr/src/test/resources/solr/collection1/conf/velocity/head.vm
----------------------------------------------------------------------
diff --git a/sentry-tests/sentry-tests-solr/src/test/resources/solr/collection1/conf/velocity/head.vm b/sentry-tests/sentry-tests-solr/src/test/resources/solr/collection1/conf/velocity/head.vm
new file mode 100644
index 0000000..37fb141
--- /dev/null
+++ b/sentry-tests/sentry-tests-solr/src/test/resources/solr/collection1/conf/velocity/head.vm
@@ -0,0 +1,35 @@
+#**
+ *  Provide elements for the <head> section of the HTML document
+ *#
+
+  ## An example of using an arbitrary request parameter
+  <title>#param('title')</title>
+  <meta http-equiv="content-type" content="text/html; charset=UTF-8"/>
+
+  <script type="text/javascript" src="#{url_root}/js/lib/jquery-1.7.2.min.js"></script>
+  <link rel="stylesheet" type="text/css" href="#{url_for_solr}/admin/file?file=/velocity/main.css&contentType=text/css"/>
+  <link rel="stylesheet" href="#{url_for_solr}/admin/file?file=/velocity/jquery.autocomplete.css&contentType=text/css" type="text/css" />
+  <script type="text/javascript" src="#{url_for_solr}/admin/file?file=/velocity/jquery.autocomplete.js&contentType=text/javascript"></script>
+
+
+    <script>
+    $(document).ready(function(){
+      $("\#q").autocomplete('#{url_for_solr}/terms', {  ## backslash escaped #q as that is a macro defined in VM_global_library.vm
+           extraParams:{
+             'terms.prefix': function() { return $("\#q").val();},
+             'terms.sort': 'count',
+             'terms.fl': 'name',
+             'wt': 'velocity',
+             'v.template': 'suggest'
+           }
+         }
+      ).keydown(function(e){
+        if (e.keyCode === 13){
+          $("#query-form").trigger('submit');
+        }
+      });
+
+      // http://localhost:8983/solr/collection1/terms?terms.fl=name&terms.prefix=i&terms.sort=count&wt=velocity&v.template=suggest
+    });
+
+    </script>

http://git-wip-us.apache.org/repos/asf/incubator-sentry/blob/c5dd7da9/sentry-tests/sentry-tests-solr/src/test/resources/solr/collection1/conf/velocity/header.vm
----------------------------------------------------------------------
diff --git a/sentry-tests/sentry-tests-solr/src/test/resources/solr/collection1/conf/velocity/header.vm b/sentry-tests/sentry-tests-solr/src/test/resources/solr/collection1/conf/velocity/header.vm
new file mode 100644
index 0000000..b6cbfb6
--- /dev/null
+++ b/sentry-tests/sentry-tests-solr/src/test/resources/solr/collection1/conf/velocity/header.vm
@@ -0,0 +1,7 @@
+#**
+ *  Render the top section of the page visible to users
+ *#
+
+<div id="head">
+  <span ><a href="#url_for_home#if($request.params.get('debugQuery'))?debugQuery=true#end"><img src="#{url_root}/img/solr.png" id="logo"/></a></span>
+</div>

http://git-wip-us.apache.org/repos/asf/incubator-sentry/blob/c5dd7da9/sentry-tests/sentry-tests-solr/src/test/resources/solr/collection1/conf/velocity/hit.vm
----------------------------------------------------------------------
diff --git a/sentry-tests/sentry-tests-solr/src/test/resources/solr/collection1/conf/velocity/hit.vm b/sentry-tests/sentry-tests-solr/src/test/resources/solr/collection1/conf/velocity/hit.vm
new file mode 100644
index 0000000..5f6bb6a
--- /dev/null
+++ b/sentry-tests/sentry-tests-solr/src/test/resources/solr/collection1/conf/velocity/hit.vm
@@ -0,0 +1,25 @@
+#**
+ *  Called for each matching document but then
+ *  calls one of product_doc, join_doc or richtext_doc
+ *  depending on which fields the doc has
+ *#
+
+#set($docId = $doc.getFieldValue('id'))
+
+<div class="result-document">
+
+  ## Has a "name" field ?
+  #if($doc.getFieldValue('name'))
+    #parse("product_doc.vm")
+
+  ## Has a "compName_s" field ?
+  #elseif($doc.getFieldValue('compName_s'))
+    #parse("join_doc.vm")
+
+  ## Fallback to richtext_doc
+  #else
+    #parse("richtext_doc.vm")
+
+  #end
+
+</div>

http://git-wip-us.apache.org/repos/asf/incubator-sentry/blob/c5dd7da9/sentry-tests/sentry-tests-solr/src/test/resources/solr/collection1/conf/velocity/hit_grouped.vm
----------------------------------------------------------------------
diff --git a/sentry-tests/sentry-tests-solr/src/test/resources/solr/collection1/conf/velocity/hit_grouped.vm b/sentry-tests/sentry-tests-solr/src/test/resources/solr/collection1/conf/velocity/hit_grouped.vm
new file mode 100644
index 0000000..e1912ea
--- /dev/null
+++ b/sentry-tests/sentry-tests-solr/src/test/resources/solr/collection1/conf/velocity/hit_grouped.vm
@@ -0,0 +1,43 @@
+#**
+ *  Display grouped results
+ *#
+
+<div class="result-document">
+
+  <div class="result-title">
+    <b>$grouping.key</b>
+  </div>
+
+  <div>
+    Total Matches in Group: $grouping.value.matches
+  </div>
+
+  <div>  ## list of groups
+
+    #foreach ($group in $grouping.value.groups)
+      <div class="group-value">
+        #if($group.groupValue)$group.groupValue#{else}<i>No group</i>#end
+        <span #annTitle("The count of the number of documents in this group")>
+          ($group.doclist.numFound)
+        </span>
+      </div>
+
+      <div class="group-doclist"
+        #annTitle("Contains the top scoring documents in the group")
+      >
+        #foreach ($doc in $group.doclist)
+          #set($docId = $doc.getFieldValue('id'))
+          #if($doc.getFieldValue('name'))
+            #parse("product_doc.vm")
+          #elseif($doc.getFieldValue('compName_s'))
+            #parse("join_doc.vm")
+          #else
+            #parse("richtext_doc.vm")
+          #end
+        #end
+      </div>
+
+    #end  ## end of foreach group in grouping.value.groups
+  </div>  ## div tag for entire list of groups
+
+</div>  ## end of div class=result-document

http://git-wip-us.apache.org/repos/asf/incubator-sentry/blob/c5dd7da9/sentry-tests/sentry-tests-solr/src/test/resources/solr/collection1/conf/velocity/hit_plain.vm
----------------------------------------------------------------------
diff --git a/sentry-tests/sentry-tests-solr/src/test/resources/solr/collection1/conf/velocity/hit_plain.vm b/sentry-tests/sentry-tests-solr/src/test/resources/solr/collection1/conf/velocity/hit_plain.vm
new file mode 100644
index 0000000..50c6aae
--- /dev/null
+++ b/sentry-tests/sentry-tests-solr/src/test/resources/solr/collection1/conf/velocity/hit_plain.vm
@@ -0,0 +1,25 @@
+#**
+ *  An extremely plain / debug version of hit.vm
+ *#
+
+<table>
+  ## For each field
+  #foreach( $fieldName in $doc.fieldNames )
+    ## For each value
+    #foreach( $value in $doc.getFieldValues($fieldName) )
+      <tr>
+        ## Field Name
+        <th align="right" valign="top">
+          #if( $foreach.count == 1 )
+            $fieldName:
+          #end
+        </th>
+        ## Field Value(s)
+        <td align="left" valign="top">
+          $esc.html($value) <br/>
+        </td>
+      </tr>
+    #end     ## end for each value
+  #end       ## end for each field
+</table>
+<hr/>

http://git-wip-us.apache.org/repos/asf/incubator-sentry/blob/c5dd7da9/sentry-tests/sentry-tests-solr/src/test/resources/solr/collection1/conf/velocity/join_doc.vm
----------------------------------------------------------------------
diff --git a/sentry-tests/sentry-tests-solr/src/test/resources/solr/collection1/conf/velocity/join_doc.vm b/sentry-tests/sentry-tests-solr/src/test/resources/solr/collection1/conf/velocity/join_doc.vm
new file mode 100644
index 0000000..705e9fd
--- /dev/null
+++ b/sentry-tests/sentry-tests-solr/src/test/resources/solr/collection1/conf/velocity/join_doc.vm
@@ -0,0 +1,20 @@
+#**
+ *  Display documents that are joined to other documents
+ *#
+
+<div class="result-title">
+  <b>#field('compName_s')</b>
+</div>
+
+<div>
+  Id: #field('id')
+  (company-details document for
+    <a href="http://wiki.apache.org/solr/Join" target="_new">join</a>
+  )
+</div>
+
+<div>
+  Address: #field('address_s')
+</div>
+
+#parse('debug.vm')

http://git-wip-us.apache.org/repos/asf/incubator-sentry/blob/c5dd7da9/sentry-tests/sentry-tests-solr/src/test/resources/solr/collection1/conf/velocity/jquery.autocomplete.css
----------------------------------------------------------------------
diff --git a/sentry-tests/sentry-tests-solr/src/test/resources/solr/collection1/conf/velocity/jquery.autocomplete.css b/sentry-tests/sentry-tests-solr/src/test/resources/solr/collection1/conf/velocity/jquery.autocomplete.css
new file mode 100644
index 0000000..70fcb3f
--- /dev/null
+++ b/sentry-tests/sentry-tests-solr/src/test/resources/solr/collection1/conf/velocity/jquery.autocomplete.css
@@ -0,0 +1,48 @@
+.ac_results {
+	padding: 0px;
+	border: 1px solid black;
+	background-color: white;
+	overflow: hidden;
+	z-index: 99999;
+}
+
+.ac_results ul {
+	width: 100%;
+	list-style-position: outside;
+	list-style: none;
+	padding: 0;
+	margin: 0;
+}
+
+.ac_results li {
+	margin: 0px;
+	padding: 2px 5px;
+	cursor: default;
+	display: block;
+	/* 
+	if width will be 100% horizontal scrollbar will apear 
+	when scroll mode will be used
+	*/
+	/*width: 100%;*/
+	font: menu;
+	font-size: 12px;
+	/* 
+	it is very important, if line-height not setted or setted 
+	in relative units scroll will be broken in firefox
+	*/
+	line-height: 16px;
+	overflow: hidden;
+}
+
+.ac_loading {
+	background: white url('indicator.gif') right center no-repeat;
+}
+
+.ac_odd {
+	background-color: #eee;
+}
+
+.ac_over {
+	background-color: #0A246A;
+	color: white;
+}

http://git-wip-us.apache.org/repos/asf/incubator-sentry/blob/c5dd7da9/sentry-tests/sentry-tests-solr/src/test/resources/solr/collection1/conf/velocity/jquery.autocomplete.js
----------------------------------------------------------------------
diff --git a/sentry-tests/sentry-tests-solr/src/test/resources/solr/collection1/conf/velocity/jquery.autocomplete.js b/sentry-tests/sentry-tests-solr/src/test/resources/solr/collection1/conf/velocity/jquery.autocomplete.js
new file mode 100644
index 0000000..ea4b1a3
--- /dev/null
+++ b/sentry-tests/sentry-tests-solr/src/test/resources/solr/collection1/conf/velocity/jquery.autocomplete.js
@@ -0,0 +1,763 @@
+/*
+ * Autocomplete - jQuery plugin 1.1pre
+ *
+ * Copyright (c) 2007 Dylan Verheul, Dan G. Switzer, Anjesh Tuladhar, Jörn Zaefferer
+ *
+ * Dual licensed under the MIT and GPL licenses:
+ *   http://www.opensource.org/licenses/mit-license.php
+ *   http://www.gnu.org/licenses/gpl.html
+ *
+ * Revision: $Id: jquery.autocomplete.js 5785 2008-07-12 10:37:33Z joern.zaefferer $
+ *
+ */
+
+;(function($) {
+	
+$.fn.extend({
+	autocomplete: function(urlOrData, options) {
+		var isUrl = typeof urlOrData == "string";
+		options = $.extend({}, $.Autocompleter.defaults, {
+			url: isUrl ? urlOrData : null,
+			data: isUrl ? null : urlOrData,
+			delay: isUrl ? $.Autocompleter.defaults.delay : 10,
+			max: options && !options.scroll ? 10 : 150
+		}, options);
+		
+		// if highlight is set to false, replace it with a do-nothing function
+		options.highlight = options.highlight || function(value) { return value; };
+		
+		// if the formatMatch option is not specified, then use formatItem for backwards compatibility
+		options.formatMatch = options.formatMatch || options.formatItem;
+		
+		return this.each(function() {
+			new $.Autocompleter(this, options);
+		});
+	},
+	result: function(handler) {
+		return this.bind("result", handler);
+	},
+	search: function(handler) {
+		return this.trigger("search", [handler]);
+	},
+	flushCache: function() {
+		return this.trigger("flushCache");
+	},
+	setOptions: function(options){
+		return this.trigger("setOptions", [options]);
+	},
+	unautocomplete: function() {
+		return this.trigger("unautocomplete");
+	}
+});
+
+$.Autocompleter = function(input, options) {
+
+	var KEY = {
+		UP: 38,
+		DOWN: 40,
+		DEL: 46,
+		TAB: 9,
+		RETURN: 13,
+		ESC: 27,
+		COMMA: 188,
+		PAGEUP: 33,
+		PAGEDOWN: 34,
+		BACKSPACE: 8
+	};
+
+	// Create $ object for input element
+	var $input = $(input).attr("autocomplete", "off").addClass(options.inputClass);
+
+	var timeout;
+	var previousValue = "";
+	var cache = $.Autocompleter.Cache(options);
+	var hasFocus = 0;
+	var lastKeyPressCode;
+	var config = {
+		mouseDownOnSelect: false
+	};
+	var select = $.Autocompleter.Select(options, input, selectCurrent, config);
+	
+	var blockSubmit;
+	
+	// prevent form submit in opera when selecting with return key
+	$.browser.opera && $(input.form).bind("submit.autocomplete", function() {
+		if (blockSubmit) {
+			blockSubmit = false;
+			return false;
+		}
+	});
+	
+	// only opera doesn't trigger keydown multiple times while pressed, others don't work with keypress at all
+	$input.bind(($.browser.opera ? "keypress" : "keydown") + ".autocomplete", function(event) {
+		// track last key pressed
+		lastKeyPressCode = event.keyCode;
+		switch(event.keyCode) {
+		
+			case KEY.UP:
+				event.preventDefault();
+				if ( select.visible() ) {
+					select.prev();
+				} else {
+					onChange(0, true);
+				}
+				break;
+				
+			case KEY.DOWN:
+				event.preventDefault();
+				if ( select.visible() ) {
+					select.next();
+				} else {
+					onChange(0, true);
+				}
+				break;
+				
+			case KEY.PAGEUP:
+				event.preventDefault();
+				if ( select.visible() ) {
+					select.pageUp();
+				} else {
+					onChange(0, true);
+				}
+				break;
+				
+			case KEY.PAGEDOWN:
+				event.preventDefault();
+				if ( select.visible() ) {
+					select.pageDown();
+				} else {
+					onChange(0, true);
+				}
+				break;
+			
+			// matches also semicolon
+			case options.multiple && $.trim(options.multipleSeparator) == "," && KEY.COMMA:
+			case KEY.TAB:
+			case KEY.RETURN:
+				if( selectCurrent() ) {
+					// stop default to prevent a form submit, Opera needs special handling
+					event.preventDefault();
+					blockSubmit = true;
+					return false;
+				}
+				break;
+				
+			case KEY.ESC:
+				select.hide();
+				break;
+				
+			default:
+				clearTimeout(timeout);
+				timeout = setTimeout(onChange, options.delay);
+				break;
+		}
+	}).focus(function(){
+		// track whether the field has focus, we shouldn't process any
+		// results if the field no longer has focus
+		hasFocus++;
+	}).blur(function() {
+		hasFocus = 0;
+		if (!config.mouseDownOnSelect) {
+			hideResults();
+		}
+	}).click(function() {
+		// show select when clicking in a focused field
+		if ( hasFocus++ > 1 && !select.visible() ) {
+			onChange(0, true);
+		}
+	}).bind("search", function() {
+		// TODO why not just specifying both arguments?
+		var fn = (arguments.length > 1) ? arguments[1] : null;
+		function findValueCallback(q, data) {
+			var result;
+			if( data && data.length ) {
+				for (var i=0; i < data.length; i++) {
+					if( data[i].result.toLowerCase() == q.toLowerCase() ) {
+						result = data[i];
+						break;
+					}
+				}
+			}
+			if( typeof fn == "function" ) fn(result);
+			else $input.trigger("result", result && [result.data, result.value]);
+		}
+		$.each(trimWords($input.val()), function(i, value) {
+			request(value, findValueCallback, findValueCallback);
+		});
+	}).bind("flushCache", function() {
+		cache.flush();
+	}).bind("setOptions", function() {
+		$.extend(options, arguments[1]);
+		// if we've updated the data, repopulate
+		if ( "data" in arguments[1] )
+			cache.populate();
+	}).bind("unautocomplete", function() {
+		select.unbind();
+		$input.unbind();
+		$(input.form).unbind(".autocomplete");
+	});
+	
+	
+	function selectCurrent() {
+		var selected = select.selected();
+		if( !selected )
+			return false;
+		
+		var v = selected.result;
+		previousValue = v;
+		
+		if ( options.multiple ) {
+			var words = trimWords($input.val());
+			if ( words.length > 1 ) {
+				v = words.slice(0, words.length - 1).join( options.multipleSeparator ) + options.multipleSeparator + v;
+			}
+			v += options.multipleSeparator;
+		}
+		
+		$input.val(v);
+		hideResultsNow();
+		$input.trigger("result", [selected.data, selected.value]);
+		return true;
+	}
+	
+	function onChange(crap, skipPrevCheck) {
+		if( lastKeyPressCode == KEY.DEL ) {
+			select.hide();
+			return;
+		}
+		
+		var currentValue = $input.val();
+		
+		if ( !skipPrevCheck && currentValue == previousValue )
+			return;
+		
+		previousValue = currentValue;
+		
+		currentValue = lastWord(currentValue);
+		if ( currentValue.length >= options.minChars) {
+			$input.addClass(options.loadingClass);
+			if (!options.matchCase)
+				currentValue = currentValue.toLowerCase();
+			request(currentValue, receiveData, hideResultsNow);
+		} else {
+			stopLoading();
+			select.hide();
+		}
+	};
+	
+	function trimWords(value) {
+		if ( !value ) {
+			return [""];
+		}
+		var words = value.split( options.multipleSeparator );
+		var result = [];
+		$.each(words, function(i, value) {
+			if ( $.trim(value) )
+				result[i] = $.trim(value);
+		});
+		return result;
+	}
+	
+	function lastWord(value) {
+		if ( !options.multiple )
+			return value;
+		var words = trimWords(value);
+		return words[words.length - 1];
+	}
+	
+	// fills in the input box w/the first match (assumed to be the best match)
+	// q: the term entered
+	// sValue: the first matching result
+	function autoFill(q, sValue){
+		// autofill in the complete box w/the first match as long as the user hasn't entered in more data
+		// if the last user key pressed was backspace, don't autofill
+		if( options.autoFill && (lastWord($input.val()).toLowerCase() == q.toLowerCase()) && lastKeyPressCode != KEY.BACKSPACE ) {
+			// fill in the value (keep the case the user has typed)
+			$input.val($input.val() + sValue.substring(lastWord(previousValue).length));
+			// select the portion of the value not typed by the user (so the next character will erase)
+			$.Autocompleter.Selection(input, previousValue.length, previousValue.length + sValue.length);
+		}
+	};
+
+	function hideResults() {
+		clearTimeout(timeout);
+		timeout = setTimeout(hideResultsNow, 200);
+	};
+
+	function hideResultsNow() {
+		var wasVisible = select.visible();
+		select.hide();
+		clearTimeout(timeout);
+		stopLoading();
+		if (options.mustMatch) {
+			// call search and run callback
+			$input.search(
+				function (result){
+					// if no value found, clear the input box
+					if( !result ) {
+						if (options.multiple) {
+							var words = trimWords($input.val()).slice(0, -1);
+							$input.val( words.join(options.multipleSeparator) + (words.length ? options.multipleSeparator : "") );
+						}
+						else
+							$input.val( "" );
+					}
+				}
+			);
+		}
+		if (wasVisible)
+			// position cursor at end of input field
+			$.Autocompleter.Selection(input, input.value.length, input.value.length);
+	};
+
+	function receiveData(q, data) {
+		if ( data && data.length && hasFocus ) {
+			stopLoading();
+			select.display(data, q);
+			autoFill(q, data[0].value);
+			select.show();
+		} else {
+			hideResultsNow();
+		}
+	};
+
+	function request(term, success, failure) {
+		if (!options.matchCase)
+			term = term.toLowerCase();
+		var data = cache.load(term);
+		data = null; // Avoid buggy cache and go to Solr every time 
+		// recieve the cached data
+		if (data && data.length) {
+			success(term, data);
+		// if an AJAX url has been supplied, try loading the data now
+		} else if( (typeof options.url == "string") && (options.url.length > 0) ){
+			
+			var extraParams = {
+				timestamp: +new Date()
+			};
+			$.each(options.extraParams, function(key, param) {
+				extraParams[key] = typeof param == "function" ? param() : param;
+			});
+			
+			$.ajax({
+				// try to leverage ajaxQueue plugin to abort previous requests
+				mode: "abort",
+				// limit abortion to this input
+				port: "autocomplete" + input.name,
+				dataType: options.dataType,
+				url: options.url,
+				data: $.extend({
+					q: lastWord(term),
+					limit: options.max
+				}, extraParams),
+				success: function(data) {
+					var parsed = options.parse && options.parse(data) || parse(data);
+					cache.add(term, parsed);
+					success(term, parsed);
+				}
+			});
+		} else {
+			// if we have a failure, we need to empty the list -- this prevents the the [TAB] key from selecting the last successful match
+			select.emptyList();
+			failure(term);
+		}
+	};
+	
+	function parse(data) {
+		var parsed = [];
+		var rows = data.split("\n");
+		for (var i=0; i < rows.length; i++) {
+			var row = $.trim(rows[i]);
+			if (row) {
+				row = row.split("|");
+				parsed[parsed.length] = {
+					data: row,
+					value: row[0],
+					result: options.formatResult && options.formatResult(row, row[0]) || row[0]
+				};
+			}
+		}
+		return parsed;
+	};
+
+	function stopLoading() {
+		$input.removeClass(options.loadingClass);
+	};
+
+};
+
+$.Autocompleter.defaults = {
+	inputClass: "ac_input",
+	resultsClass: "ac_results",
+	loadingClass: "ac_loading",
+	minChars: 1,
+	delay: 400,
+	matchCase: false,
+	matchSubset: true,
+	matchContains: false,
+	cacheLength: 10,
+	max: 100,
+	mustMatch: false,
+	extraParams: {},
+	selectFirst: false,
+	formatItem: function(row) { return row[0]; },
+	formatMatch: null,
+	autoFill: false,
+	width: 0,
+	multiple: false,
+	multipleSeparator: ", ",
+	highlight: function(value, term) {
+		return value.replace(new RegExp("(?![^&;]+;)(?!<[^<>]*)(" + term.replace(/([\^\$\(\)\[\]\{\}\*\.\+\?\|\\])/gi, "\\$1") + ")(?![^<>]*>)(?![^&;]+;)", "gi"), "<strong>$1</strong>");
+	},
+    scroll: true,
+    scrollHeight: 180
+};
+
+$.Autocompleter.Cache = function(options) {
+
+	var data = {};
+	var length = 0;
+	
+	function matchSubset(s, sub) {
+		if (!options.matchCase) 
+			s = s.toLowerCase();
+		var i = s.indexOf(sub);
+		if (options.matchContains == "word"){
+			i = s.toLowerCase().search("\\b" + sub.toLowerCase());
+		}
+		if (i == -1) return false;
+		return i == 0 || options.matchContains;
+	};
+	
+	function add(q, value) {
+		if (length > options.cacheLength){
+			flush();
+		}
+		if (!data[q]){ 
+			length++;
+		}
+		data[q] = value;
+	}
+	
+	function populate(){
+		if( !options.data ) return false;
+		// track the matches
+		var stMatchSets = {},
+			nullData = 0;
+
+		// no url was specified, we need to adjust the cache length to make sure it fits the local data store
+		if( !options.url ) options.cacheLength = 1;
+		
+		// track all options for minChars = 0
+		stMatchSets[""] = [];
+		
+		// loop through the array and create a lookup structure
+		for ( var i = 0, ol = options.data.length; i < ol; i++ ) {
+			var rawValue = options.data[i];
+			// if rawValue is a string, make an array otherwise just reference the array
+			rawValue = (typeof rawValue == "string") ? [rawValue] : rawValue;
+			
+			var value = options.formatMatch(rawValue, i+1, options.data.length);
+			if ( value === false )
+				continue;
+				
+			var firstChar = value.charAt(0).toLowerCase();
+			// if no lookup array for this character exists, look it up now
+			if( !stMatchSets[firstChar] ) 
+				stMatchSets[firstChar] = [];
+
+			// if the match is a string
+			var row = {
+				value: value,
+				data: rawValue,
+				result: options.formatResult && options.formatResult(rawValue) || value
+			};
+			
+			// push the current match into the set list
+			stMatchSets[firstChar].push(row);
+
+			// keep track of minChars zero items
+			if ( nullData++ < options.max ) {
+				stMatchSets[""].push(row);
+			}
+		};
+
+		// add the data items to the cache
+		$.each(stMatchSets, function(i, value) {
+			// increase the cache size
+			options.cacheLength++;
+			// add to the cache
+			add(i, value);
+		});
+	}
+	
+	// populate any existing data
+	setTimeout(populate, 25);
+	
+	function flush(){
+		data = {};
+		length = 0;
+	}
+	
+	return {
+		flush: flush,
+		add: add,
+		populate: populate,
+		load: function(q) {
+			if (!options.cacheLength || !length)
+				return null;
+			/* 
+			 * if dealing w/local data and matchContains than we must make sure
+			 * to loop through all the data collections looking for matches
+			 */
+			if( !options.url && options.matchContains ){
+				// track all matches
+				var csub = [];
+				// loop through all the data grids for matches
+				for( var k in data ){
+					// don't search through the stMatchSets[""] (minChars: 0) cache
+					// this prevents duplicates
+					if( k.length > 0 ){
+						var c = data[k];
+						$.each(c, function(i, x) {
+							// if we've got a match, add it to the array
+							if (matchSubset(x.value, q)) {
+								csub.push(x);
+							}
+						});
+					}
+				}				
+				return csub;
+			} else 
+			// if the exact item exists, use it
+			if (data[q]){
+				return data[q];
+			} else
+			if (options.matchSubset) {
+				for (var i = q.length - 1; i >= options.minChars; i--) {
+					var c = data[q.substr(0, i)];
+					if (c) {
+						var csub = [];
+						$.each(c, function(i, x) {
+							if (matchSubset(x.value, q)) {
+								csub[csub.length] = x;
+							}
+						});
+						return csub;
+					}
+				}
+			}
+			return null;
+		}
+	};
+};
+
+$.Autocompleter.Select = function (options, input, select, config) {
+	var CLASSES = {
+		ACTIVE: "ac_over"
+	};
+	
+	var listItems,
+		active = -1,
+		data,
+		term = "",
+		needsInit = true,
+		element,
+		list;
+	
+	// Create results
+	function init() {
+		if (!needsInit)
+			return;
+		element = $("<div/>")
+		.hide()
+		.addClass(options.resultsClass)
+		.css("position", "absolute")
+		.appendTo(document.body);
+	
+		list = $("<ul/>").appendTo(element).mouseover( function(event) {
+			if(target(event).nodeName && target(event).nodeName.toUpperCase() == 'LI') {
+	            active = $("li", list).removeClass(CLASSES.ACTIVE).index(target(event));
+			    $(target(event)).addClass(CLASSES.ACTIVE);            
+	        }
+		}).click(function(event) {
+			$(target(event)).addClass(CLASSES.ACTIVE);
+			select();
+			// TODO provide option to avoid setting focus again after selection? useful for cleanup-on-focus
+			input.focus();
+			return false;
+		}).mousedown(function() {
+			config.mouseDownOnSelect = true;
+		}).mouseup(function() {
+			config.mouseDownOnSelect = false;
+		});
+		
+		if( options.width > 0 )
+			element.css("width", options.width);
+			
+		needsInit = false;
+	} 
+	
+	function target(event) {
+		var element = event.target;
+		while(element && element.tagName != "LI")
+			element = element.parentNode;
+		// more fun with IE, sometimes event.target is empty, just ignore it then
+		if(!element)
+			return [];
+		return element;
+	}
+
+	function moveSelect(step) {
+		listItems.slice(active, active + 1).removeClass(CLASSES.ACTIVE);
+		movePosition(step);
+        var activeItem = listItems.slice(active, active + 1).addClass(CLASSES.ACTIVE);
+        if(options.scroll) {
+            var offset = 0;
+            listItems.slice(0, active).each(function() {
+				offset += this.offsetHeight;
+			});
+            if((offset + activeItem[0].offsetHeight - list.scrollTop()) > list[0].clientHeight) {
+                list.scrollTop(offset + activeItem[0].offsetHeight - list.innerHeight());
+            } else if(offset < list.scrollTop()) {
+                list.scrollTop(offset);
+            }
+        }
+	};
+	
+	function movePosition(step) {
+		active += step;
+		if (active < 0) {
+			active = listItems.size() - 1;
+		} else if (active >= listItems.size()) {
+			active = 0;
+		}
+	}
+	
+	function limitNumberOfItems(available) {
+		return options.max && options.max < available
+			? options.max
+			: available;
+	}
+	
+	function fillList() {
+		list.empty();
+		var max = limitNumberOfItems(data.length);
+		for (var i=0; i < max; i++) {
+			if (!data[i])
+				continue;
+			var formatted = options.formatItem(data[i].data, i+1, max, data[i].value, term);
+			if ( formatted === false )
+				continue;
+			var li = $("<li/>").html( options.highlight(formatted, term) ).addClass(i%2 == 0 ? "ac_even" : "ac_odd").appendTo(list)[0];
+			$.data(li, "ac_data", data[i]);
+		}
+		listItems = list.find("li");
+		if ( options.selectFirst ) {
+			listItems.slice(0, 1).addClass(CLASSES.ACTIVE);
+			active = 0;
+		}
+		// apply bgiframe if available
+		if ( $.fn.bgiframe )
+			list.bgiframe();
+	}
+	
+	return {
+		display: function(d, q) {
+			init();
+			data = d;
+			term = q;
+			fillList();
+		},
+		next: function() {
+			moveSelect(1);
+		},
+		prev: function() {
+			moveSelect(-1);
+		},
+		pageUp: function() {
+			if (active != 0 && active - 8 < 0) {
+				moveSelect( -active );
+			} else {
+				moveSelect(-8);
+			}
+		},
+		pageDown: function() {
+			if (active != listItems.size() - 1 && active + 8 > listItems.size()) {
+				moveSelect( listItems.size() - 1 - active );
+			} else {
+				moveSelect(8);
+			}
+		},
+		hide: function() {
+			element && element.hide();
+			listItems && listItems.removeClass(CLASSES.ACTIVE);
+			active = -1;
+		},
+		visible : function() {
+			return element && element.is(":visible");
+		},
+		current: function() {
+			return this.visible() && (listItems.filter("." + CLASSES.ACTIVE)[0] || options.selectFirst && listItems[0]);
+		},
+		show: function() {
+			var offset = $(input).offset();
+			element.css({
+				width: typeof options.width == "string" || options.width > 0 ? options.width : $(input).width(),
+				top: offset.top + input.offsetHeight,
+				left: offset.left
+			}).show();
+            if(options.scroll) {
+                list.scrollTop(0);
+                list.css({
+					maxHeight: options.scrollHeight,
+					overflow: 'auto'
+				});
+				
+                if($.browser.msie && typeof document.body.style.maxHeight === "undefined") {
+					var listHeight = 0;
+					listItems.each(function() {
+						listHeight += this.offsetHeight;
+					});
+					var scrollbarsVisible = listHeight > options.scrollHeight;
+                    list.css('height', scrollbarsVisible ? options.scrollHeight : listHeight );
+					if (!scrollbarsVisible) {
+						// IE doesn't recalculate width when scrollbar disappears
+						listItems.width( list.width() - parseInt(listItems.css("padding-left")) - parseInt(listItems.css("padding-right")) );
+					}
+                }
+                
+            }
+		},
+		selected: function() {
+			var selected = listItems && listItems.filter("." + CLASSES.ACTIVE).removeClass(CLASSES.ACTIVE);
+			return selected && selected.length && $.data(selected[0], "ac_data");
+		},
+		emptyList: function (){
+			list && list.empty();
+		},
+		unbind: function() {
+			element && element.remove();
+		}
+	};
+};
+
+$.Autocompleter.Selection = function(field, start, end) {
+	if( field.createTextRange ){
+		var selRange = field.createTextRange();
+		selRange.collapse(true);
+		selRange.moveStart("character", start);
+		selRange.moveEnd("character", end);
+		selRange.select();
+	} else if( field.setSelectionRange ){
+		field.setSelectionRange(start, end);
+	} else {
+		if( field.selectionStart ){
+			field.selectionStart = start;
+			field.selectionEnd = end;
+		}
+	}
+	field.focus();
+};
+
+})(jQuery);

http://git-wip-us.apache.org/repos/asf/incubator-sentry/blob/c5dd7da9/sentry-tests/sentry-tests-solr/src/test/resources/solr/collection1/conf/velocity/layout.vm
----------------------------------------------------------------------
diff --git a/sentry-tests/sentry-tests-solr/src/test/resources/solr/collection1/conf/velocity/layout.vm b/sentry-tests/sentry-tests-solr/src/test/resources/solr/collection1/conf/velocity/layout.vm
new file mode 100644
index 0000000..ee67184
--- /dev/null
+++ b/sentry-tests/sentry-tests-solr/src/test/resources/solr/collection1/conf/velocity/layout.vm
@@ -0,0 +1,24 @@
+#**
+ *  Overall HTML page layout
+ *#
+
+<html>
+<head>
+  #parse("head.vm")
+</head>
+  <body>
+    <div id="admin"><a href="#url_root/#/#core_name">Solr Admin</a></div>
+    <div id="header">
+      #parse("header.vm")
+    </div>
+    <div id="tabs">
+      #parse("tabs.vm")
+    </div>
+    <div id="content">
+      $content
+    </div>
+    <div id="footer">
+      #parse("footer.vm")
+    </div>
+  </body>
+</html>

http://git-wip-us.apache.org/repos/asf/incubator-sentry/blob/c5dd7da9/sentry-tests/sentry-tests-solr/src/test/resources/solr/collection1/conf/velocity/main.css
----------------------------------------------------------------------
diff --git a/sentry-tests/sentry-tests-solr/src/test/resources/solr/collection1/conf/velocity/main.css b/sentry-tests/sentry-tests-solr/src/test/resources/solr/collection1/conf/velocity/main.css
new file mode 100644
index 0000000..1018af8
--- /dev/null
+++ b/sentry-tests/sentry-tests-solr/src/test/resources/solr/collection1/conf/velocity/main.css
@@ -0,0 +1,230 @@
+#admin{
+  text-align: right;
+  vertical-align: top; 
+}
+
+#head{
+  width: 100%;
+}
+.array-field {
+  border: 2px solid #474747;
+  background: #FFE9D8;
+  padding: 5px;
+  margin: 5px;
+}
+
+.array-field-list li {
+  list-style: circle;
+  margin-left: 20px;
+}
+
+.parsed_query_header {
+  font-family: Helvetica, Arial, sans-serif;
+  font-size: 10pt;
+  font-weight: bold;
+}
+
+.parsed_query {
+  font-family: Courier, Courier New, monospaced;
+  font-size: 10pt;
+  font-weight: normal;
+}
+
+body {
+  font-family: Helvetica, Arial, sans-serif;
+  font-size: 10pt;
+}
+
+a {
+  color: #43a4b1;
+}
+
+.navigators {
+  float: left;
+  margin: 5px;
+  margin-top: 0px;
+  width: 185px;
+  padding: 5px;
+  top: -20px;
+  position: relative;  
+}
+
+.tabs-bar {
+  padding: 5px;
+  width: 100%;
+  border: 1px solid;
+  border-width: 0px 0px 1px 0px;
+}
+.tab {
+  font-weight: bold;
+  padding: 5px;
+  margin: 0px 5px;
+  border: 1px solid;
+  background-color: #dddddd;
+  border-top-left-radius: 4px;
+  border-top-right-radius: 4px;
+}
+.tab:hover {
+  background: #FEC293;
+}
+.tab.selected {
+  background-color: #ffffff;
+  border-bottom: 1px solid #ffffff;
+}
+
+.navigators h2 {
+  background: #FEC293;
+  padding: 2px 5px;
+}
+
+.navigators ul {
+  list-style: none;
+  margin: 0;
+  margin-bottom: 5px;
+  margin-top: 5px;
+  padding-left: 10px;
+}
+
+.navigators ul li {
+  color: #999;
+  padding: 2px;
+}
+
+
+
+.facet-field {
+  font-weight: bold;
+}
+
+.highlight {
+  color: white;
+  background-color: gray;
+  border: 1px black solid;
+}
+
+.highlight-box {
+  margin-left: 15px;
+}
+
+.field-name {
+  font-weight: bold;
+}
+
+.highlighted-facet-field {
+  background: white;
+}
+
+.constraints {
+  margin-top: 10px;
+}
+
+#query-form{
+  width: 80%;
+}
+
+
+
+.query-box, .constraints {
+  padding: 5px;
+  margin: 5px;
+  font-weight: normal;
+  font-size: 24px;
+  letter-spacing: 0.08em;
+}
+
+.query-box #q {
+  margin-left: 8px;
+  width: 60%;
+  height: 50px;
+  border: 1px solid #999;
+  font-size: 1em;
+  padding: 0.4em;
+}
+
+.query-box {
+  
+}
+
+.query-boost {
+  
+  top: 10px;
+  left: 50px;
+  position: relative;
+  font-size: 0.8em;
+}
+
+.query-box .inputs{
+  left: 180px;
+  position: relative;
+  
+}
+
+#logo {
+  margin: 10px;
+  border-style: none;
+}
+
+.pagination {
+  padding-left: 33%;
+  background: #eee;
+  margin: 5px;
+  margin-left: 210px;
+  padding-top: 5px;
+  padding-bottom: 5px;
+}
+
+.result-document {
+  border: 1px solid #999;
+  padding: 5px;
+  margin: 5px;
+  margin-left: 210px;
+  margin-bottom: 15px;
+}
+
+.result-document div{
+  padding: 5px;
+}
+
+.result-title{
+  width:60%;
+}
+
+.result-body{
+  background: #ddd;
+}
+
+.mlt{
+  
+}
+
+.map{
+  float: right;
+  position: relative;
+  top: -25px;  
+}
+
+.result-document:nth-child(2n+1) {
+  background-color: #eee;
+}
+
+
+.selected-facet-field {
+  font-weight: bold;
+}
+
+li.show {
+  list-style: disc;
+}
+
+.group-value{
+  font-weight: bold;
+}
+
+.error {
+  color: white;
+  background-color: red;
+  left: 210px;
+  width:80%;
+  position: relative;
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-sentry/blob/c5dd7da9/sentry-tests/sentry-tests-solr/src/test/resources/solr/collection1/conf/velocity/mime_type_lists.vm
----------------------------------------------------------------------
diff --git a/sentry-tests/sentry-tests-solr/src/test/resources/solr/collection1/conf/velocity/mime_type_lists.vm b/sentry-tests/sentry-tests-solr/src/test/resources/solr/collection1/conf/velocity/mime_type_lists.vm
new file mode 100644
index 0000000..c9832b4
--- /dev/null
+++ b/sentry-tests/sentry-tests-solr/src/test/resources/solr/collection1/conf/velocity/mime_type_lists.vm
@@ -0,0 +1,68 @@
+#**
+ *  Define some Mime-Types, short and long form
+ *#
+
+## MimeType to extension map for detecting file type
+## and showing proper icon
+## List of types match the icons in /solr/img/filetypes
+
+## Short MimeType Names
+## Was called $supportedtypes
+#set($supportedMimeTypes = "7z;ai;aiff;asc;audio;bin;bz2;c;cfc;cfm;chm;class;conf;cpp;cs;css;csv;deb;divx;doc;dot;eml;enc;file;gif;gz;hlp;htm;html;image;iso;jar;java;jpeg;jpg;js;lua;m;mm;mov;mp3;mpg;odc;odf;odg;odi;odp;ods;odt;ogg;pdf;pgp;php;pl;png;ppt;ps;py;ram;rar;rb;rm;rpm;rtf;sig;sql;swf;sxc;sxd;sxi;sxw;tar;tex;tgz;txt;vcf;video;vsd;wav;wma;wmv;xls;xml;xpi;xvid;zip")
+
+## Long Form: map MimeType headers to our Short names
+## Was called $extMap
+#set( $mimeExtensionsMap = {
+   "application/x-7z-compressed": "7z",
+   "application/postscript": "ai",
+   "application/pgp-signature": "asc",
+   "application/octet-stream": "bin",
+   "application/x-bzip2": "bz2",
+   "text/x-c": "c",
+   "application/vnd.ms-htmlhelp": "chm",
+   "application/java-vm": "class",
+   "text/css": "css",
+   "text/csv": "csv",
+   "application/x-debian-package": "deb",
+   "application/msword": "doc",
+   "message/rfc822": "eml",
+   "image/gif": "gif",
+   "application/winhlp": "hlp",
+   "text/html": "html",
+   "application/java-archive": "jar",
+   "text/x-java-source": "java",
+   "image/jpeg": "jpeg",
+   "application/javascript": "js",
+   "application/vnd.oasis.opendocument.chart": "odc",
+   "application/vnd.oasis.opendocument.formula": "odf",
+   "application/vnd.oasis.opendocument.graphics": "odg",
+   "application/vnd.oasis.opendocument.image": "odi",
+   "application/vnd.oasis.opendocument.presentation": "odp",
+   "application/vnd.oasis.opendocument.spreadsheet": "ods",
+   "application/vnd.oasis.opendocument.text": "odt",
+   "application/pdf": "pdf",
+   "application/pgp-encrypted": "pgp",
+   "image/png": "png",
+   "application/vnd.ms-powerpoint": "ppt",
+   "audio/x-pn-realaudio": "ram",
+   "application/x-rar-compressed": "rar",
+   "application/vnd.rn-realmedia": "rm",
+   "application/rtf": "rtf",
+   "application/x-shockwave-flash": "swf",
+   "application/vnd.sun.xml.calc": "sxc",
+   "application/vnd.sun.xml.draw": "sxd",
+   "application/vnd.sun.xml.impress": "sxi",
+   "application/vnd.sun.xml.writer": "sxw",
+   "application/x-tar": "tar",
+   "application/x-tex": "tex",
+   "text/plain": "txt",
+   "text/x-vcard": "vcf",
+   "application/vnd.visio": "vsd",
+   "audio/x-wav": "wav",
+   "audio/x-ms-wma": "wma",
+   "video/x-ms-wmv": "wmv",
+   "application/vnd.ms-excel": "xls",
+   "application/xml": "xml",
+   "application/x-xpinstall": "xpi",
+   "application/zip": "zip"
+})

http://git-wip-us.apache.org/repos/asf/incubator-sentry/blob/c5dd7da9/sentry-tests/sentry-tests-solr/src/test/resources/solr/collection1/conf/velocity/pagination_bottom.vm
----------------------------------------------------------------------
diff --git a/sentry-tests/sentry-tests-solr/src/test/resources/solr/collection1/conf/velocity/pagination_bottom.vm b/sentry-tests/sentry-tests-solr/src/test/resources/solr/collection1/conf/velocity/pagination_bottom.vm
new file mode 100644
index 0000000..6457699
--- /dev/null
+++ b/sentry-tests/sentry-tests-solr/src/test/resources/solr/collection1/conf/velocity/pagination_bottom.vm
@@ -0,0 +1,22 @@
+#**
+ *  Paging and Statistics at bottom of results
+ *#
+
+## Usually rendered in pagination div tag
+
+#if($response.response.get('grouped'))
+  ## pass
+#else
+
+  #link_to_previous_page("previous")
+
+  <span class="results-found">$page.results_found</span>
+  results found.
+
+  Page <span class="page-num">$page.current_page_number</span>
+    of <span class="page-count">$page.page_count</span>
+
+  #link_to_next_page("next")
+
+#end
+<br/>

http://git-wip-us.apache.org/repos/asf/incubator-sentry/blob/c5dd7da9/sentry-tests/sentry-tests-solr/src/test/resources/solr/collection1/conf/velocity/pagination_top.vm
----------------------------------------------------------------------
diff --git a/sentry-tests/sentry-tests-solr/src/test/resources/solr/collection1/conf/velocity/pagination_top.vm b/sentry-tests/sentry-tests-solr/src/test/resources/solr/collection1/conf/velocity/pagination_top.vm
new file mode 100644
index 0000000..d6b9bd4
--- /dev/null
+++ b/sentry-tests/sentry-tests-solr/src/test/resources/solr/collection1/conf/velocity/pagination_top.vm
@@ -0,0 +1,29 @@
+#**
+ *  Paging and Statistics at top of results
+ *#
+
+## Usually rendered in pagination div tag
+
+## Grouped Results / Not Paginated
+#if($response.response.get('grouped'))
+
+  <span>
+    <span class="results-found">
+      $response.response.get('grouped').size() group(s)
+    </span>
+    found in ${response.responseHeader.QTime} ms
+  </span>
+
+## Regular Results / Use Paging Links if needed
+#else
+
+  <span>
+    <span class="results-found">$page.results_found</span>
+    results found in
+    ${response.responseHeader.QTime} ms
+  </span>
+
+  Page <span class="page-num">$page.current_page_number</span>
+    of <span class="page-count">$page.page_count</span>
+
+#end   ## end else non-grouped results, normal pagination

http://git-wip-us.apache.org/repos/asf/incubator-sentry/blob/c5dd7da9/sentry-tests/sentry-tests-solr/src/test/resources/solr/collection1/conf/velocity/product_doc.vm
----------------------------------------------------------------------
diff --git a/sentry-tests/sentry-tests-solr/src/test/resources/solr/collection1/conf/velocity/product_doc.vm b/sentry-tests/sentry-tests-solr/src/test/resources/solr/collection1/conf/velocity/product_doc.vm
new file mode 100644
index 0000000..c4ebbb2
--- /dev/null
+++ b/sentry-tests/sentry-tests-solr/src/test/resources/solr/collection1/conf/velocity/product_doc.vm
@@ -0,0 +1,32 @@
+#**
+ *  Render a hit representing a Product
+ *  assumed to have a field called "name"
+ *#
+
+<div class="result-title"><b>#field('name')</b><span class="mlt">   #if($params.getBool('mlt', false) == false)<a href="#lensNoQ&q=id:$docId&mlt=true">More Like This</a>#end</span></div>
+##do we have a physical store for this product
+#set($store = $doc.getFieldValue('store'))
+#if($store)<div class="map"><img src="http://maps.google.com/maps/api/staticmap?&zoom=12&size=150x80&maptype=roadmap&markers=$doc.getFieldValue('store')&sensor=false" /><div><small><a target="_map" href="http://maps.google.com/?q=$store&amp;source=embed">Larger Map</a></small></div></div>#end
+<div>Id: #field('id')</div>
+<div>Price: #field('price_c')</div>
+<div>Features: #field('features')</div>
+<div>In Stock: #field('inStock')</div>
+<div class="mlt">
+  #set($mlt = $mltResults.get($docId))
+  #set($mltOn = $params.getBool('mlt'))
+  #if($mltOn == true)<div class="field-name">Similar Items</div>#end
+  #if ($mltOn && $mlt && $mlt.size() > 0)
+  <ul>
+    #foreach($mltHit in $mlt)
+      #set($mltId = $mltHit.getFieldValue('id'))
+      <li><div><a href="#url_for_home?q=id:$mltId">$mltId</a></div><div><span class="field-name">Name:</span> $mltHit.getFieldValue('name')</div>
+        <div><span class="field-name">Price:</span> $!number.currency($mltHit.getFieldValue('price')) <span class="field-name">In Stock:</span> $mltHit.getFieldValue('inStock')</div>
+
+      </li>
+    #end
+  </ul>
+  #elseif($mltOn && $mlt.size() == 0)
+    <div>No Similar Items Found</div>
+  #end
+</div>
+#parse('debug.vm')

http://git-wip-us.apache.org/repos/asf/incubator-sentry/blob/c5dd7da9/sentry-tests/sentry-tests-solr/src/test/resources/solr/collection1/conf/velocity/query.vm
----------------------------------------------------------------------
diff --git a/sentry-tests/sentry-tests-solr/src/test/resources/solr/collection1/conf/velocity/query.vm b/sentry-tests/sentry-tests-solr/src/test/resources/solr/collection1/conf/velocity/query.vm
new file mode 100644
index 0000000..c904c7d
--- /dev/null
+++ b/sentry-tests/sentry-tests-solr/src/test/resources/solr/collection1/conf/velocity/query.vm
@@ -0,0 +1,42 @@
+<div class="query-box">
+  <form id="query-form" action="#{url_for_home}" method="GET">
+    <div class="inputs">
+      <span #annTitle("Add the query using the &q= parameter")>Find: <input type="text" id="q" name="q" value="$!esc.html($params.get('q'))"/> <input type="submit" id="querySubmit"/> <input type="reset"/></span>
+      <div class="query-boost"><span #annTitle("Add the boost function &bf=price to the query")><input type="checkbox" name="bf" value="price" #if($request.params.get('bf') == 'price')checked="true"#end>Boost by Price</input></span>
+      #parse("querySpatial.vm")
+      #parse("queryGroup.vm")
+      </div>
+  </div>
+
+    #if($request.params.get('debugQuery'))
+      <input type="hidden" name="debugQuery" value="true"/>
+    #end
+    #if($annotate == true)
+      <input type="hidden" name="annotateBrowse" value="true"/>
+    #end
+    #foreach($fq in $request.params.getParams('fq'))
+      #if ($fq != "{!bbox}")
+        <input type="hidden" name="fq" id="allFQs" value="$esc.html($fq)"/>
+      #end
+    #end
+    <div class="constraints" #annTitle("Lists out the &fq filters.  Click to remove.")>
+      #foreach($fq in $params.getParams('fq'))
+        #set($previous_fq_count=$velocityCount - 1)
+        #if($fq != '')
+        &gt; <a style="{text-decoration: line-through;}" href="#url_for_filters($request.params.getParams('fq').subList(0,$previous_fq_count))">$fq</a>
+        #end
+      #end
+    </div>
+    <div class="parsed_query_header">
+     #if($request.params.get('debugQuery'))
+        <a href="#" onclick='jQuery(this).siblings("div").toggle(); return false;'>toggle parsed query</a>
+        <div class="parsed_query" style="display:none">$response.response.debug.parsedquery</div>
+      #end
+      #set($queryOpts = $request.params.get("queryOpts"))
+      #if($queryOpts && $queryOpts != "")
+        <input type="hidden" name="queryOpts" value="$queryOpts"/>
+      #end
+    </div>
+  </form>
+
+</div>

http://git-wip-us.apache.org/repos/asf/incubator-sentry/blob/c5dd7da9/sentry-tests/sentry-tests-solr/src/test/resources/solr/collection1/conf/velocity/query_form.vm
----------------------------------------------------------------------
diff --git a/sentry-tests/sentry-tests-solr/src/test/resources/solr/collection1/conf/velocity/query_form.vm b/sentry-tests/sentry-tests-solr/src/test/resources/solr/collection1/conf/velocity/query_form.vm
new file mode 100644
index 0000000..3685c85
--- /dev/null
+++ b/sentry-tests/sentry-tests-solr/src/test/resources/solr/collection1/conf/velocity/query_form.vm
@@ -0,0 +1,64 @@
+#**
+ *  Renders the main query form
+ *#
+
+<div class="query-box">
+  <form id="query-form" action="#{url_for_home}" method="GET">
+
+    <div class="inputs">
+      <span #annTitle("Add the query using the &q= parameter")>
+        Find:
+        <input type="text" id="q" name="q" value="$!esc.html($params.get('q'))"/>
+        <input type="submit" id="querySubmit"/>
+        <input type="reset"/>
+      </span>
+      <div class="query-boost">
+        <span #annTitle("Add the boost function &bf=price to the query")>
+          <input type="checkbox" name="bf" value="price"
+            #if($request.params.get('bf') == 'price')checked="true"#end
+          >
+            Boost by Price
+          </input>
+        </span>
+      #parse("query_spatial.vm")
+      #parse("query_group.vm")
+      </div>
+  </div>
+
+    #if($request.params.get('debugQuery'))
+      <input type="hidden" name="debugQuery" value="true"/>
+    #end
+    #if($annotate == true)
+      <input type="hidden" name="annotateBrowse" value="true"/>
+    #end
+    #foreach($fq in $request.params.getParams('fq'))
+      #if ($fq != "{!bbox}")
+        <input type="hidden" name="fq" id="allFQs" value="$esc.html($fq)"/>
+      #end
+    #end
+
+    <div class="constraints" #annTitle("Lists out the &fq filters.  Click to remove.")>
+      #foreach($fq in $params.getParams('fq'))
+        #set($previous_fq_count=$velocityCount - 1)
+        #if($fq != '')
+          &gt;
+          <a style="{text-decoration: line-through;}"
+            href="#url_for_filters($request.params.getParams('fq').subList(0,$previous_fq_count))"
+          >$fq</a>
+        #end
+      #end
+    </div>
+
+    <div class="parsed_query_header">
+      #if($request.params.get('debugQuery'))
+        <a href="#" onclick='jQuery(this).siblings("div").toggle(); return false;'>toggle parsed query</a>
+        <div class="parsed_query" style="display:none">$response.response.debug.parsedquery</div>
+      #end
+      #set($queryOpts = $request.params.get("queryOpts"))
+      #if($queryOpts && $queryOpts != "")
+        <input type="hidden" name="queryOpts" value="$queryOpts"/>
+      #end
+    </div>
+
+  </form>
+</div>

http://git-wip-us.apache.org/repos/asf/incubator-sentry/blob/c5dd7da9/sentry-tests/sentry-tests-solr/src/test/resources/solr/collection1/conf/velocity/query_group.vm
----------------------------------------------------------------------
diff --git a/sentry-tests/sentry-tests-solr/src/test/resources/solr/collection1/conf/velocity/query_group.vm b/sentry-tests/sentry-tests-solr/src/test/resources/solr/collection1/conf/velocity/query_group.vm
new file mode 100644
index 0000000..42a72b2
--- /dev/null
+++ b/sentry-tests/sentry-tests-solr/src/test/resources/solr/collection1/conf/velocity/query_group.vm
@@ -0,0 +1,43 @@
+#**
+ *  Query settings for grouping by fields,
+ *  e.g.: Manufacturer or Popularity
+ *#
+
+#set($queryOpts = $params.get("queryOpts"))
+
+#if($queryOpts == "group")
+  <div>
+    #set($groupF = $request.params.get('group.field'))
+
+    <label #annTitle("Add the &group.field parameter. Multiselect is supported")>
+      Group By:
+      <select id="group" name="group.field" multiple="true">
+        ## TODO: Handle multiple selects correctly
+        ## TODO: fix empty / "No Group" selection
+
+        <option value=""
+          #if($groupF == '')selected="true"#end
+        >
+          No Group
+        </option>
+
+        <option value="manu_exact"
+          #if($groupF == 'manu_exact')selected="true"#end
+        >
+          Manufacturer
+        </option>
+
+        <option value="popularity"
+          #if($groupF == 'popularity')selected="true"#end
+        >
+          Popularity
+        </option>
+
+      </select>
+    </label>  
+
+    <input type="hidden" name="group" value="true"/>
+
+  </div>
+
+#end

http://git-wip-us.apache.org/repos/asf/incubator-sentry/blob/c5dd7da9/sentry-tests/sentry-tests-solr/src/test/resources/solr/collection1/conf/velocity/query_spatial.vm
----------------------------------------------------------------------
diff --git a/sentry-tests/sentry-tests-solr/src/test/resources/solr/collection1/conf/velocity/query_spatial.vm b/sentry-tests/sentry-tests-solr/src/test/resources/solr/collection1/conf/velocity/query_spatial.vm
new file mode 100644
index 0000000..97671ae
--- /dev/null
+++ b/sentry-tests/sentry-tests-solr/src/test/resources/solr/collection1/conf/velocity/query_spatial.vm
@@ -0,0 +1,75 @@
+#**
+ *  Query logic for selecting location / Geospatial search
+ *#
+
+#set($queryOpts = $params.get("queryOpts"))
+
+#if($queryOpts == "spatial")
+
+  <div>
+
+    #set($loc = $request.params.get('pt'))
+    ## Normalize first trip through to "none" because
+    ## an empty string generates an error message later on
+    #if( ! $loc )
+      #set( $loc = "none" )
+    #end
+
+    #set($dist = $request.params.get('d', "10"))
+
+    ## Cities for The Select List
+    #set( $cities = {
+      "none": "No Filter",
+      "45.17614,-93.87341": "Buffalo, MN",
+      "37.7752,-100.0232": "Dodge City, KS",
+      "35.0752,-97.032": "Oklahoma City, OK",
+      "37.7752,-122.4232": "San Francisco CA"
+    })
+
+    <label #annTitle("Add the &pt parameter")>
+      Location Filter:
+      <select id="pt" name="pt">
+
+        ## Generate <option> tag for each city
+        #foreach( $city_lon_lat in $cities.keySet() )
+          #set( $city_name = $cities.get($city_lon_lat) )
+          <option value="$city_lon_lat"
+            #if($loc == $city_lon_lat)selected="true"#end
+          >
+            $city_name
+          </option>
+        #end
+
+      </select>
+
+    </label>
+
+    <span #annTitle("Add the &d parameter")>
+      Distance (KM):
+      <input id="d" name="d" type="text" size="6"
+        value="#if($dist != '')${dist}#{else}10#end"  ## TODO: isn't the default of 10 above sufficient?  no if/else needed?
+      />
+    </span>
+
+    <input type="hidden" name="sfield" value="store"/>
+    <input type="hidden" id="spatialFQ" name="fq" value=""/>
+    <input type="hidden" name="queryOpts" value="spatial"/>        
+
+  </div>
+
+  <script type="text/javascript">
+    $('#query-form').submit(function() {
+      if ($("#pt").val() != "none") {
+        $("#spatialFQ").val("{!bbox}");
+      }
+      $fqs = $("#allFQs").val();
+      $fqs = $fqs.replace("{!bbox}", "");
+      if ($fqs == ''){
+        $("#allFQs").remove();
+      }
+      $("#allFQs").val($fqs);
+      return true;
+    });
+  </script>
+
+#end

http://git-wip-us.apache.org/repos/asf/incubator-sentry/blob/c5dd7da9/sentry-tests/sentry-tests-solr/src/test/resources/solr/collection1/conf/velocity/results_list.vm
----------------------------------------------------------------------
diff --git a/sentry-tests/sentry-tests-solr/src/test/resources/solr/collection1/conf/velocity/results_list.vm b/sentry-tests/sentry-tests-solr/src/test/resources/solr/collection1/conf/velocity/results_list.vm
new file mode 100644
index 0000000..10d1e57
--- /dev/null
+++ b/sentry-tests/sentry-tests-solr/src/test/resources/solr/collection1/conf/velocity/results_list.vm
@@ -0,0 +1,22 @@
+#**
+ *  Render the main Results List
+ *#
+
+## Usually displayed inside <div class="results">
+
+#if($response.response.get('grouped'))
+
+  #foreach($grouping in $response.response.get('grouped'))
+    #parse("hit_grouped.vm")
+  #end
+
+#else
+
+  #foreach($doc in $response.results)
+    #parse("hit.vm")
+    ## Can get an extremely simple view of the doc
+    ## which might be nicer for debugging
+    ##parse("hit_plain.vm")
+  #end
+
+#end

http://git-wip-us.apache.org/repos/asf/incubator-sentry/blob/c5dd7da9/sentry-tests/sentry-tests-solr/src/test/resources/solr/collection1/conf/velocity/richtext_doc.vm
----------------------------------------------------------------------
diff --git a/sentry-tests/sentry-tests-solr/src/test/resources/solr/collection1/conf/velocity/richtext_doc.vm b/sentry-tests/sentry-tests-solr/src/test/resources/solr/collection1/conf/velocity/richtext_doc.vm
new file mode 100644
index 0000000..6ffb04c
--- /dev/null
+++ b/sentry-tests/sentry-tests-solr/src/test/resources/solr/collection1/conf/velocity/richtext_doc.vm
@@ -0,0 +1,153 @@
+#**
+ *  Render a complex document in the results list
+ *#
+
+## Load Mime-Type List and Mapping
+#parse('mime_type_lists.vm')
+## Sets:
+## * supportedMimeTypes, AKA supportedtypes
+## * mimeExtensionsMap, AKA extMap
+
+## Title
+#if($doc.getFieldValue('title'))
+  #set($title = $esc.html($doc.getFirstValue('title')))
+#else
+  #set($title = "["+$doc.getFieldValue('id')+"]")
+#end
+
+## URL
+#if($doc.getFieldValue('url'))
+  #set($url = $doc.getFieldValue('url'))
+#elseif($doc.getFieldValue('resourcename'))
+  #set($url = "file:///$doc.getFieldValue('resourcename')")
+#else
+  #set($url = "$doc.getFieldValue('id')")
+#end
+
+## Sort out Mime-Type
+#set($ct = $list.get($doc.getFirstValue('content_type').split(";"),0))
+#set($filename = $doc.getFieldValue('resourcename'))
+#set($filetype = false)
+#set($filetype = $mimeExtensionsMap.get($ct))
+
+## TODO: falling back to file extension is convenient,
+## except when you don't have an icon for that extension
+## example "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
+## document with a .docx extension.
+## It'd be nice to fall back to an "unknown" or the existing "file" type
+## We sort of do this below, but only if the filename has no extension
+## (anything after the last dot).
+
+#if(!$filetype)
+  #set($filetype = $filename.substring($filename.lastIndexOf(".")).substring(1))
+#end
+
+## #if(!$filetype)
+##   #set($filetype = "file")
+## #end
+## #if(!$supportedMimeTypes.contains($filetype))
+##   #set($filetype = "file")
+## #end
+
+## Row 1: Icon and Title and mlt link
+<div class="result-title">
+  ## Icon
+  ## Small file type icons from http://www.splitbrain.org/projects/file_icons (public domain)
+  <img src="#{url_root}/img/filetypes/${filetype}.png" align="center">
+
+  ## Title, hyperlinked
+  <a href="${url}" target="_blank">
+    <b>$title</b></a>
+
+  ## Link for MLT / More Like This / Find Similar
+  <span class="mlt">
+    #if($params.getBool('mlt', false) == false)
+      <a href="#lensNoQ&q=id:%22$docId%22&mlt=true">
+        More Like This</a>
+    #end
+  </span>
+
+</div>
+
+## Row 2?: ID / URL
+<div>
+  Id: #field('id')
+</div>
+
+## Resource Name
+<div>
+  #if($doc.getFieldValue('resourcename'))
+    Resource name: $filename 
+  #elseif($url)
+    URL: $url
+  #end
+  #if($ct)
+    ($ct)
+  #end
+</div>
+
+## Author
+#if($doc.getFieldValue('author'))
+  <div>
+    Author: #field('author')
+  </div>
+#end
+
+## Last_Modified Date
+#if($doc.getFieldValue('last_modified'))
+  <div>
+    last-modified:
+    #field('last_modified')
+  </div>
+#end
+
+## Main content of doc
+<div class="result-body">
+  #field('content')
+</div>
+
+## Display Similar Documents / MLT = More Like This
+<div class="mlt">
+  #set($mlt = $mltResults.get($docId))
+  #set($mltOn = $params.getBool('mlt'))
+  #if($mltOn == true)
+    <div class="field-name">
+      Similar Items
+    </div>
+  #end
+  ## If has MLT enabled An Entries to show
+  #if ($mltOn && $mlt && $mlt.size() > 0)
+    <ul>
+      #foreach($mltHit in $mlt)
+        #set($mltId = $mltHit.getFieldValue('id'))
+        <li>
+          <div>
+            <a href="#url_for_home?q=id:$mltId">
+              $mltId</a>
+          </div>
+          <div>
+            <span class="field-name">
+              Title:
+            </span>
+            $mltHit.getFieldValue('title')
+          </div>
+          <div>
+            <span class="field-name">
+              Author:
+            </span>
+            $mltHit.getFieldValue('author')
+            <span class="field-name">
+              Description:
+            </span>
+            $mltHit.getFieldValue('description')
+          </div>
+        </li>
+      #end    ## end for each mltHit in $mlt
+    </ul>
+  ## Else MLT Enabled but no mlt results for this query
+  #elseif($mltOn && $mlt.size() == 0)
+    <div>No Similar Items Found</div>
+  #end
+</div>  ## div class=mlt
+
+#parse('debug.vm')

http://git-wip-us.apache.org/repos/asf/incubator-sentry/blob/c5dd7da9/sentry-tests/sentry-tests-solr/src/test/resources/solr/collection1/conf/velocity/suggest.vm
----------------------------------------------------------------------
diff --git a/sentry-tests/sentry-tests-solr/src/test/resources/solr/collection1/conf/velocity/suggest.vm b/sentry-tests/sentry-tests-solr/src/test/resources/solr/collection1/conf/velocity/suggest.vm
new file mode 100644
index 0000000..829b8dc
--- /dev/null
+++ b/sentry-tests/sentry-tests-solr/src/test/resources/solr/collection1/conf/velocity/suggest.vm
@@ -0,0 +1,8 @@
+#**
+ *  Provides cynamic spelling suggestions
+ *  as you type in the search form
+ *#
+
+#foreach($t in $response.response.terms.name)
+  $t.key
+#end

http://git-wip-us.apache.org/repos/asf/incubator-sentry/blob/c5dd7da9/sentry-tests/sentry-tests-solr/src/test/resources/solr/collection1/conf/velocity/tabs.vm
----------------------------------------------------------------------
diff --git a/sentry-tests/sentry-tests-solr/src/test/resources/solr/collection1/conf/velocity/tabs.vm b/sentry-tests/sentry-tests-solr/src/test/resources/solr/collection1/conf/velocity/tabs.vm
new file mode 100644
index 0000000..fb4d8f2
--- /dev/null
+++ b/sentry-tests/sentry-tests-solr/src/test/resources/solr/collection1/conf/velocity/tabs.vm
@@ -0,0 +1,50 @@
+#**
+ *  Provides navigation/access to Advanced search options
+ *  Usually displayed near the top of the page
+ *#
+
+##TODO: Make some nice tabs here
+
+#set($queryOpts = $params.get("queryOpts"))
+
+<div class="tabs-bar" #annTitle("Click the link to demonstrate various Solr capabilities")>
+
+  <span>Type of Search:</span>
+
+  ##queryOpts=$queryOpts
+
+  ## return to Simple Search
+  ##set( $selected = ($queryOpts && $queryOpts != "") )
+  #set( $selected = ! $queryOpts )
+  <span class="tab #if($selected)selected#end">
+    #if($selected)
+      Simple
+    #else
+      <a href="#url_for_home/?#debug#annotate">
+        Simple</a>
+    #end
+  </span>
+
+  ## GEO-Spatial / Location Based
+  #set( $selected = ($queryOpts == "spatial") )
+  <span class="tab #if($selected)selected#end">
+    #if($selected)
+      Spatial
+    #else
+      <a href="#url_for_home?&queryOpts=spatial#debug#annotate">
+        Spatial</a>
+    #end
+  </span>
+
+  ## Group By Field
+  #set( $selected = ($queryOpts == "group") )
+  <span class="tab #if($selected)selected#end">
+    #if($selected)
+      Group By
+    #else
+      <a href="#url_for_home?#debug#annotate&queryOpts=group&group=true&group.field=manu_exact">
+        Group By</a>
+    #end
+  </span>
+
+</div>