You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lucene.apache.org by dw...@apache.org on 2015/08/25 14:21:39 UTC

svn commit: r1697661 - in /lucene/dev/trunk/solr: ./ contrib/clustering/src/java/org/apache/solr/handler/clustering/ contrib/clustering/src/java/org/apache/solr/handler/clustering/carrot2/ contrib/clustering/src/test-files/clustering/solr/collection1/c...

Author: dweiss
Date: Tue Aug 25 12:21:39 2015
New Revision: 1697661

URL: http://svn.apache.org/r1697661
Log:
SOLR-7969: Unavailable clustering engines should not fail the core.


Added:
    lucene/dev/trunk/solr/server/solr/configsets/sample_techproducts_configs/conf/clustering/carrot2/README.txt   (with props)
Modified:
    lucene/dev/trunk/solr/CHANGES.txt
    lucene/dev/trunk/solr/contrib/clustering/src/java/org/apache/solr/handler/clustering/ClusteringComponent.java
    lucene/dev/trunk/solr/contrib/clustering/src/java/org/apache/solr/handler/clustering/ClusteringEngine.java
    lucene/dev/trunk/solr/contrib/clustering/src/java/org/apache/solr/handler/clustering/carrot2/CarrotClusteringEngine.java
    lucene/dev/trunk/solr/contrib/clustering/src/test-files/clustering/solr/collection1/conf/solrconfig.xml
    lucene/dev/trunk/solr/contrib/clustering/src/test/org/apache/solr/handler/clustering/MockDocumentClusteringEngine.java
    lucene/dev/trunk/solr/contrib/clustering/src/test/org/apache/solr/handler/clustering/carrot2/CarrotClusteringEngineTest.java
    lucene/dev/trunk/solr/server/solr/configsets/sample_techproducts_configs/conf/solrconfig.xml

Modified: lucene/dev/trunk/solr/CHANGES.txt
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/solr/CHANGES.txt?rev=1697661&r1=1697660&r2=1697661&view=diff
==============================================================================
--- lucene/dev/trunk/solr/CHANGES.txt (original)
+++ lucene/dev/trunk/solr/CHANGES.txt Tue Aug 25 12:21:39 2015
@@ -23,7 +23,7 @@ Consult the LUCENE_CHANGES.txt file for
 Versions of Major Components
 ---------------------
 Apache Tika 1.7
-Carrot2 3.9.0
+Carrot2 3.10.3
 Velocity 1.7 and Velocity Tools 2.0
 Apache UIMA 2.3.1
 Apache ZooKeeper 3.4.6
@@ -93,7 +93,7 @@ Consult the LUCENE_CHANGES.txt file for
 Versions of Major Components
 ---------------------
 Apache Tika 1.7
-Carrot2 3.9.0
+Carrot2 3.10.3
 Velocity 1.7 and Velocity Tools 2.0
 Apache UIMA 2.3.1
 Apache ZooKeeper 3.4.6
@@ -168,6 +168,8 @@ Optimizations
 Other Changes
 ----------------------
 
+* SOLR-7969: Unavailable clustering engines should not fail the core. (Dawid Weiss)
+
 * SOLR-7790, SOLR-7792, SOLR-7791: Update Carrot2 clustering component to 
   version 3.10.3. Upgrade HPPC library to version 0.7.1, morfologik-stemming 
   to version 1.10.0. (Dawid Weiss)

Modified: lucene/dev/trunk/solr/contrib/clustering/src/java/org/apache/solr/handler/clustering/ClusteringComponent.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/solr/contrib/clustering/src/java/org/apache/solr/handler/clustering/ClusteringComponent.java?rev=1697661&r1=1697660&r2=1697661&view=diff
==============================================================================
--- lucene/dev/trunk/solr/contrib/clustering/src/java/org/apache/solr/handler/clustering/ClusteringComponent.java (original)
+++ lucene/dev/trunk/solr/contrib/clustering/src/java/org/apache/solr/handler/clustering/ClusteringComponent.java Tue Aug 25 12:21:39 2015
@@ -22,12 +22,13 @@ import java.util.Collections;
 import java.util.HashSet;
 import java.util.LinkedHashMap;
 import java.util.Map;
-import java.util.Map.Entry;
 import java.util.Set;
 
 import org.apache.commons.lang.StringUtils;
 import org.apache.solr.common.SolrDocument;
 import org.apache.solr.common.SolrDocumentList;
+import org.apache.solr.common.SolrException;
+import org.apache.solr.common.SolrException.ErrorCode;
 import org.apache.solr.common.params.CommonParams;
 import org.apache.solr.common.params.SolrParams;
 import org.apache.solr.common.util.NamedList;
@@ -107,7 +108,9 @@ public class ClusteringComponent extends
       for (Map.Entry<String,Object> entry : initParams) {
         if ("engine".equals(entry.getKey())) {
           NamedList<Object> engineInitParams = (NamedList<Object>) entry.getValue();
-          
+          Boolean optional = engineInitParams.getBooleanArg("optional");
+          optional = (optional == null ? Boolean.FALSE : optional);
+
           String engineClassName = StringUtils.defaultIfBlank( 
               (String) engineInitParams.get("classname"),
               CarrotClusteringEngine.class.getName()); 
@@ -115,6 +118,16 @@ public class ClusteringComponent extends
           // Instantiate the clustering engine and split to appropriate map. 
           final ClusteringEngine engine = loader.newInstance(engineClassName, ClusteringEngine.class);
           final String name = StringUtils.defaultIfBlank(engine.init(engineInitParams, core), "");
+
+          if (!engine.isAvailable()) {
+            if (optional) {
+              log.info("Optional clustering engine not available: " + name);
+            } else {
+              throw new SolrException(ErrorCode.SERVER_ERROR, 
+                  "A required clustering engine failed to initialize, check the logs: " + name);
+            }
+          }
+          
           final ClusteringEngine previousEntry;
           if (engine instanceof SearchClusteringEngine) {
             previousEntry = searchClusteringEngines.put(name, (SearchClusteringEngine) engine);
@@ -152,11 +165,13 @@ public class ClusteringComponent extends
     if (!params.getBool(COMPONENT_NAME, false)) {
       return;
     }
-    String name = getClusteringEngineName(rb);
+
+    final String name = getClusteringEngineName(rb);
     boolean useResults = params.getBool(ClusteringParams.USE_SEARCH_RESULTS, false);
     if (useResults == true) {
-      SearchClusteringEngine engine = getSearchClusteringEngine(rb);
+      SearchClusteringEngine engine = searchClusteringEngines.get(name);
       if (engine != null) {
+        checkAvailable(name, engine);
         DocListAndSet results = rb.getResults();
         Map<SolrDocument,Integer> docIds = Maps.newHashMapWithExpectedSize(results.docList.size());
         SolrDocumentList solrDocList = SolrPluginUtils.docListToSolrDocumentList(
@@ -164,13 +179,15 @@ public class ClusteringComponent extends
         Object clusters = engine.cluster(rb.getQuery(), solrDocList, docIds, rb.req);
         rb.rsp.add("clusters", clusters);
       } else {
-        log.warn("No engine for: " + name);
+        log.warn("No engine named: " + name);
       }
     }
+
     boolean useCollection = params.getBool(ClusteringParams.USE_COLLECTION, false);
     if (useCollection == true) {
       DocumentClusteringEngine engine = documentClusteringEngines.get(name);
       if (engine != null) {
+        checkAvailable(name, engine);
         boolean useDocSet = params.getBool(ClusteringParams.USE_DOC_SET, false);
         NamedList<?> nl = null;
 
@@ -182,15 +199,18 @@ public class ClusteringComponent extends
         }
         rb.rsp.add("clusters", nl);
       } else {
-        log.warn("No engine for " + name);
+        log.warn("No engine named: " + name);
       }
     }
   }
 
-  private SearchClusteringEngine getSearchClusteringEngine(ResponseBuilder rb){
-    return searchClusteringEngines.get(getClusteringEngineName(rb));
+  private void checkAvailable(String name, ClusteringEngine engine) {
+    if (!engine.isAvailable()) {
+      throw new SolrException(ErrorCode.SERVER_ERROR, 
+          "Clustering engine declared, but not available, check the logs: " + name);
+    }
   }
-  
+
   private String getClusteringEngineName(ResponseBuilder rb){
     return rb.req.getParams().get(ClusteringParams.ENGINE_NAME, ClusteringEngine.DEFAULT_ENGINE_NAME);
   }
@@ -204,24 +224,37 @@ public class ClusteringComponent extends
     sreq.params.remove(COMPONENT_NAME);
     if( ( sreq.purpose & ShardRequest.PURPOSE_GET_FIELDS ) != 0 ){
       String fl = sreq.params.get(CommonParams.FL,"*");
-      // if fl=* then we don't need check
-      if( fl.indexOf( '*' ) >= 0 ) return;
-      Set<String> fields = getSearchClusteringEngine(rb).getFieldsToLoad(rb.req);
-      if( fields == null || fields.size() == 0 ) return;
-      StringBuilder sb = new StringBuilder();
-      String[] flparams = fl.split( "[,\\s]+" );
-      Set<String> flParamSet = new HashSet<>(flparams.length);
-      for( String flparam : flparams ){
-        // no need trim() because of split() by \s+
-        flParamSet.add(flparam);
+      // if fl=* then we don't need to check.
+      if (fl.indexOf('*') >= 0) { 
+        return;
       }
-      for( String aFieldToLoad : fields ){
-        if( !flParamSet.contains( aFieldToLoad ) ){
-          sb.append( ',' ).append( aFieldToLoad );
+
+      String name = getClusteringEngineName(rb);
+      SearchClusteringEngine engine = searchClusteringEngines.get(name);
+      if (engine != null) {
+        checkAvailable(name, engine);
+        Set<String> fields = engine.getFieldsToLoad(rb.req);
+        if (fields == null || fields.size() == 0) { 
+          return;
         }
-      }
-      if( sb.length() > 0 ){
-        sreq.params.set( CommonParams.FL, fl + sb.toString() );
+  
+        StringBuilder sb = new StringBuilder();
+        String[] flparams = fl.split( "[,\\s]+" );
+        Set<String> flParamSet = new HashSet<>(flparams.length);
+        for (String flparam : flparams) {
+          // no need trim() because of split() by \s+
+          flParamSet.add(flparam);
+        }
+        for (String aFieldToLoad : fields) {
+          if (!flParamSet.contains(aFieldToLoad )) {
+            sb.append(',').append(aFieldToLoad);
+          }
+        }
+        if (sb.length() > 0) {
+          sreq.params.set(CommonParams.FL, fl + sb.toString());
+        }
+      } else {
+        log.warn("No engine named: " + name);
       }
     }
   }
@@ -229,13 +262,17 @@ public class ClusteringComponent extends
   @Override
   public void finishStage(ResponseBuilder rb) {
     SolrParams params = rb.req.getParams();
-    if (!params.getBool(COMPONENT_NAME, false) || !params.getBool(ClusteringParams.USE_SEARCH_RESULTS, false)) {
+    if (!params.getBool(COMPONENT_NAME, false) || 
+        !params.getBool(ClusteringParams.USE_SEARCH_RESULTS, false)) {
       return;
     }
+
     if (rb.stage == ResponseBuilder.STAGE_GET_FIELDS) {
-      SearchClusteringEngine engine = getSearchClusteringEngine(rb);
+      String name = getClusteringEngineName(rb);
+      SearchClusteringEngine engine = searchClusteringEngines.get(name);
       if (engine != null) {
-        SolrDocumentList solrDocList = (SolrDocumentList)rb.rsp.getValues().get("response");
+        checkAvailable(name, engine);
+        SolrDocumentList solrDocList = (SolrDocumentList) rb.rsp.getValues().get("response");
         // TODO: Currently, docIds is set to null in distributed environment.
         // This causes CarrotParams.PRODUCE_SUMMARY doesn't work.
         // To work CarrotParams.PRODUCE_SUMMARY under distributed mode, we can choose either one of:
@@ -247,8 +284,7 @@ public class ClusteringComponent extends
         Object clusters = engine.cluster(rb.getQuery(), solrDocList, docIds, rb.req);
         rb.rsp.add("clusters", clusters);
       } else {
-        String name = getClusteringEngineName(rb);
-        log.warn("No engine for: " + name);
+        log.warn("No engine named: " + name);
       }
     }
   }
@@ -271,16 +307,27 @@ public class ClusteringComponent extends
    */
   private static <T extends ClusteringEngine> void setupDefaultEngine(String type, LinkedHashMap<String,T> map) {
     // If there's already a default algorithm, leave it as is.
-    if (map.containsKey(ClusteringEngine.DEFAULT_ENGINE_NAME)) {
-      return;
+    String engineName = ClusteringEngine.DEFAULT_ENGINE_NAME;
+    T defaultEngine = map.get(engineName);
+
+    if (defaultEngine == null ||
+        !defaultEngine.isAvailable()) {
+      // If there's no default algorithm, and there are any algorithms available, 
+      // the first definition becomes the default algorithm.
+      for (Map.Entry<String, T> e : map.entrySet()) {
+        if (e.getValue().isAvailable()) {
+          engineName = e.getKey();
+          defaultEngine = e.getValue();
+          map.put(ClusteringEngine.DEFAULT_ENGINE_NAME, defaultEngine);
+          break;
+        }
+      }
     }
-  
-    // If there's no default algorithm, and there are any algorithms available, 
-    // the first definition becomes the default algorithm.
-    if (!map.isEmpty()) {
-      Entry<String,T> first = map.entrySet().iterator().next();
-      map.put(ClusteringEngine.DEFAULT_ENGINE_NAME, first.getValue());
-      log.info("Default engine for " + type + ": " + first.getKey());
+
+    if (defaultEngine != null) {
+      log.info("Default engine for " + type + ": " + engineName + " [" + defaultEngine.getClass().getSimpleName() + "]");
+    } else {
+      log.warn("No default engine for " + type + ".");
     }
   }
 }

Modified: lucene/dev/trunk/solr/contrib/clustering/src/java/org/apache/solr/handler/clustering/ClusteringEngine.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/solr/contrib/clustering/src/java/org/apache/solr/handler/clustering/ClusteringEngine.java?rev=1697661&r1=1697660&r2=1697661&view=diff
==============================================================================
--- lucene/dev/trunk/solr/contrib/clustering/src/java/org/apache/solr/handler/clustering/ClusteringEngine.java (original)
+++ lucene/dev/trunk/solr/contrib/clustering/src/java/org/apache/solr/handler/clustering/ClusteringEngine.java Tue Aug 25 12:21:39 2015
@@ -22,7 +22,7 @@ import org.apache.solr.core.SolrCore;
  * A base class for {@link SearchClusteringEngine} and {@link DocumentClusteringEngine}.
  * @lucene.experimental
  */
-public class ClusteringEngine {
+public abstract class ClusteringEngine {
   public static final String ENGINE_NAME = "name";
   public static final String DEFAULT_ENGINE_NAME = "default";
 
@@ -36,4 +36,6 @@ public class ClusteringEngine {
   public String getName() {
     return name;
   }
+
+  public abstract boolean isAvailable();
 }

Modified: lucene/dev/trunk/solr/contrib/clustering/src/java/org/apache/solr/handler/clustering/carrot2/CarrotClusteringEngine.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/solr/contrib/clustering/src/java/org/apache/solr/handler/clustering/carrot2/CarrotClusteringEngine.java?rev=1697661&r1=1697660&r2=1697661&view=diff
==============================================================================
--- lucene/dev/trunk/solr/contrib/clustering/src/java/org/apache/solr/handler/clustering/carrot2/CarrotClusteringEngine.java (original)
+++ lucene/dev/trunk/solr/contrib/clustering/src/java/org/apache/solr/handler/clustering/carrot2/CarrotClusteringEngine.java Tue Aug 25 12:21:39 2015
@@ -115,6 +115,11 @@ public class CarrotClusteringEngine exte
   private SolrCore core;
 
   @Override
+  public boolean isAvailable() {
+    return clusteringAlgorithmClass != null;
+  }
+  
+  @Override
   @SuppressWarnings("rawtypes")
   public String init(NamedList config, final SolrCore core) {
     this.core = core;
@@ -136,6 +141,17 @@ public class CarrotClusteringEngine exte
     DefaultLexicalDataFactoryDescriptor.attributeBuilder(initAttributes)
       .resourceLookup(resourceLookup);
 
+    // Make sure the requested Carrot2 clustering algorithm class is available
+    String carrotAlgorithmClassName = initParams.get(CarrotParams.ALGORITHM);
+    try {
+      this.clusteringAlgorithmClass = core.getResourceLoader().findClass(
+          carrotAlgorithmClassName, IClusteringAlgorithm.class);
+    } catch (SolrException s) {
+      if (!(s.getCause() instanceof ClassNotFoundException)) {
+        throw s;
+      } 
+    }
+
     // Load Carrot2-Workbench exported attribute XMLs based on the 'name' attribute
     // of this component. This by-name convention lookup is used to simplify configuring algorithms.
     String componentName = initParams.get(ClusteringEngine.ENGINE_NAME);
@@ -208,11 +224,6 @@ public class CarrotClusteringEngine exte
     }
     this.idFieldName = uniqueField.getName();
 
-    // Make sure the requested Carrot2 clustering algorithm class is available
-    String carrotAlgorithmClassName = initParams.get(CarrotParams.ALGORITHM);
-    this.clusteringAlgorithmClass = core.getResourceLoader().findClass(
-        carrotAlgorithmClassName, IClusteringAlgorithm.class);
-
     return result;
   }
 

Modified: lucene/dev/trunk/solr/contrib/clustering/src/test-files/clustering/solr/collection1/conf/solrconfig.xml
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/solr/contrib/clustering/src/test-files/clustering/solr/collection1/conf/solrconfig.xml?rev=1697661&r1=1697660&r2=1697661&view=diff
==============================================================================
--- lucene/dev/trunk/solr/contrib/clustering/src/test-files/clustering/solr/collection1/conf/solrconfig.xml (original)
+++ lucene/dev/trunk/solr/contrib/clustering/src/test-files/clustering/solr/collection1/conf/solrconfig.xml Tue Aug 25 12:21:39 2015
@@ -390,6 +390,11 @@
 
   <searchComponent class="org.apache.solr.handler.clustering.ClusteringComponent" name="clustering-name-decl-order">
     <lst name="engine">
+      <bool name="optional">true</bool>
+      <str name="name">unavailable</str>
+      <str name="carrot.algorithm">org.carrot2.clustering.lingo.UnavailableAlgorithm</str>
+    </lst>
+    <lst name="engine">
       <str name="name">lingo</str>
       <str name="carrot.algorithm">org.carrot2.clustering.lingo.LingoClusteringAlgorithm</str>
     </lst>

Modified: lucene/dev/trunk/solr/contrib/clustering/src/test/org/apache/solr/handler/clustering/MockDocumentClusteringEngine.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/solr/contrib/clustering/src/test/org/apache/solr/handler/clustering/MockDocumentClusteringEngine.java?rev=1697661&r1=1697660&r2=1697661&view=diff
==============================================================================
--- lucene/dev/trunk/solr/contrib/clustering/src/test/org/apache/solr/handler/clustering/MockDocumentClusteringEngine.java (original)
+++ lucene/dev/trunk/solr/contrib/clustering/src/test/org/apache/solr/handler/clustering/MockDocumentClusteringEngine.java Tue Aug 25 12:21:39 2015
@@ -34,4 +34,9 @@ public class MockDocumentClusteringEngin
   public NamedList<?> cluster(SolrParams solrParams) {
     return new NamedList<>();
   }
+  
+  @Override
+  public boolean isAvailable() {
+    return true;
+  }
 }

Modified: lucene/dev/trunk/solr/contrib/clustering/src/test/org/apache/solr/handler/clustering/carrot2/CarrotClusteringEngineTest.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/solr/contrib/clustering/src/test/org/apache/solr/handler/clustering/carrot2/CarrotClusteringEngineTest.java?rev=1697661&r1=1697660&r2=1697661&view=diff
==============================================================================
--- lucene/dev/trunk/solr/contrib/clustering/src/test/org/apache/solr/handler/clustering/carrot2/CarrotClusteringEngineTest.java (original)
+++ lucene/dev/trunk/solr/contrib/clustering/src/test/org/apache/solr/handler/clustering/carrot2/CarrotClusteringEngineTest.java Tue Aug 25 12:21:39 2015
@@ -414,7 +414,7 @@ public class CarrotClusteringEngineTest
     ClusteringComponent comp = (ClusteringComponent) h.getCore().getSearchComponent("clustering-name-decl-order");
     Map<String,SearchClusteringEngine> engines = getSearchClusteringEngines(comp);
     assertEquals(
-        Lists.newArrayList("lingo", "stc", "mock", "default"),
+        Lists.newArrayList("unavailable", "lingo", "stc", "mock", "default"),
         Lists.newArrayList(engines.keySet()));
     assertEquals(
         LingoClusteringAlgorithm.class,

Added: lucene/dev/trunk/solr/server/solr/configsets/sample_techproducts_configs/conf/clustering/carrot2/README.txt
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/solr/server/solr/configsets/sample_techproducts_configs/conf/clustering/carrot2/README.txt?rev=1697661&view=auto
==============================================================================
--- lucene/dev/trunk/solr/server/solr/configsets/sample_techproducts_configs/conf/clustering/carrot2/README.txt (added)
+++ lucene/dev/trunk/solr/server/solr/configsets/sample_techproducts_configs/conf/clustering/carrot2/README.txt Tue Aug 25 12:21:39 2015
@@ -0,0 +1,11 @@
+An override location of the clustering algorithm's resources 
+attribute definitions and lexical resources.
+
+A directory from which to load algorithm-specific stop words,
+stop labels and attribute definition XMLs. 
+
+For an overview of Carrot2 lexical resources, see:
+http://download.carrot2.org/head/manual/#chapter.lexical-resources
+
+For an overview of Lingo3G lexical resources, see:
+http://download.carrotsearch.com/lingo3g/manual/#chapter.lexical-resources

Modified: lucene/dev/trunk/solr/server/solr/configsets/sample_techproducts_configs/conf/solrconfig.xml
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/solr/server/solr/configsets/sample_techproducts_configs/conf/solrconfig.xml?rev=1697661&r1=1697660&r2=1697661&view=diff
==============================================================================
--- lucene/dev/trunk/solr/server/solr/configsets/sample_techproducts_configs/conf/solrconfig.xml (original)
+++ lucene/dev/trunk/solr/server/solr/configsets/sample_techproducts_configs/conf/solrconfig.xml Tue Aug 25 12:21:39 2015
@@ -1360,64 +1360,54 @@
 
        You'll need to set the solr.clustering.enabled system property
        when running solr to run with clustering enabled:
+       -Dsolr.clustering.enabled=true
 
-            java -Dsolr.clustering.enabled=true -jar start.jar
-
-       http://wiki.apache.org/solr/ClusteringComponent
-       http://carrot2.github.io/solr-integration-strategies/
+       https://cwiki.apache.org/confluence/display/solr/Result+Clustering
     -->
   <searchComponent name="clustering"
                    enable="${solr.clustering.enabled:false}"
                    class="solr.clustering.ClusteringComponent" >
-    <lst name="engine">
-      <str name="name">lingo</str>
+    <!--
+    Declaration of "engines" (clustering algorithms).
 
-      <!-- Class name of a clustering algorithm compatible with the Carrot2 framework.
-
-           Currently available open source algorithms are:
-           * org.carrot2.clustering.lingo.LingoClusteringAlgorithm
-           * org.carrot2.clustering.stc.STCClusteringAlgorithm
-           * org.carrot2.clustering.kmeans.BisectingKMeansClusteringAlgorithm
-
-           See http://project.carrot2.org/algorithms.html for more information.
-
-           A commercial algorithm Lingo3G (needs to be installed separately) is defined as:
-           * com.carrotsearch.lingo3g.Lingo3GClusteringAlgorithm
-        -->
-      <str name="carrot.algorithm">org.carrot2.clustering.lingo.LingoClusteringAlgorithm</str>
+    The open source algorithms from Carrot2.org project:
+      * org.carrot2.clustering.lingo.LingoClusteringAlgorithm
+      * org.carrot2.clustering.stc.STCClusteringAlgorithm
+      * org.carrot2.clustering.kmeans.BisectingKMeansClusteringAlgorithm
+    See http://project.carrot2.org/algorithms.html for more information.
 
-      <!-- Override location of the clustering algorithm's resources 
-           (attribute definitions and lexical resources).
-
-           A directory from which to load algorithm-specific stop words,
-           stop labels and attribute definition XMLs. 
+    Commercial algorithm Lingo3G (needs to be installed separately):
+      * com.carrotsearch.lingo3g.Lingo3GClusteringAlgorithm
+    -->
 
-           For an overview of Carrot2 lexical resources, see:
-           http://download.carrot2.org/head/manual/#chapter.lexical-resources
+    <lst name="engine">
+      <str name="name">lingo3g</str>
+      <bool name="optional">true</bool>
+      <str name="carrot.algorithm">com.carrotsearch.lingo3g.Lingo3GClusteringAlgorithm</str>
+      <str name="carrot.resourcesDir">clustering/carrot2</str>
+    </lst>
 
-           For an overview of Lingo3G lexical resources, see:
-           http://download.carrotsearch.com/lingo3g/manual/#chapter.lexical-resources
-       -->
+    <lst name="engine">
+      <str name="name">lingo</str>
+      <str name="carrot.algorithm">org.carrot2.clustering.lingo.LingoClusteringAlgorithm</str>
       <str name="carrot.resourcesDir">clustering/carrot2</str>
     </lst>
 
-    <!-- An example definition for the STC clustering algorithm. -->
     <lst name="engine">
       <str name="name">stc</str>
       <str name="carrot.algorithm">org.carrot2.clustering.stc.STCClusteringAlgorithm</str>
+      <str name="carrot.resourcesDir">clustering/carrot2</str>
     </lst>
 
-    <!-- An example definition for the bisecting kmeans clustering algorithm. -->
     <lst name="engine">
       <str name="name">kmeans</str>
       <str name="carrot.algorithm">org.carrot2.clustering.kmeans.BisectingKMeansClusteringAlgorithm</str>
+      <str name="carrot.resourcesDir">clustering/carrot2</str>
     </lst>
   </searchComponent>
 
-  <!-- A request handler for demonstrating the clustering component
-
-       This is purely as an example.
-
+  <!-- A request handler for demonstrating the clustering component.
+       This is meant as an example.
        In reality you will likely want to add the component to your 
        already specified request handlers. 
     -->
@@ -1447,14 +1437,14 @@
         text^0.5 features^1.0 name^1.2 sku^1.5 id^10.0 manu^1.1 cat^1.4
       </str>
       <str name="q.alt">*:*</str>
-      <str name="rows">10</str>
+      <str name="rows">100</str>
       <str name="fl">*,score</str>
     </lst>
     <arr name="last-components">
       <str>clustering</str>
     </arr>
   </requestHandler>
-  
+
   <!-- Terms Component
 
        http://wiki.apache.org/solr/TermsComponent