You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lucene.apache.org by sa...@apache.org on 2014/03/12 22:52:51 UTC

svn commit: r1576939 [2/2] - in /lucene/dev/trunk/solr: core/src/java/org/apache/solr/core/ core/src/java/org/apache/solr/rest/ core/src/java/org/apache/solr/rest/schema/ core/src/java/org/apache/solr/rest/schema/analysis/ core/src/java/org/apache/solr...

Added: lucene/dev/trunk/solr/core/src/java/org/apache/solr/rest/SolrConfigRestApi.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/solr/core/src/java/org/apache/solr/rest/SolrConfigRestApi.java?rev=1576939&view=auto
==============================================================================
--- lucene/dev/trunk/solr/core/src/java/org/apache/solr/rest/SolrConfigRestApi.java (added)
+++ lucene/dev/trunk/solr/core/src/java/org/apache/solr/rest/SolrConfigRestApi.java Wed Mar 12 21:52:49 2014
@@ -0,0 +1,75 @@
+package org.apache.solr.rest;
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import org.apache.solr.request.SolrRequestInfo;
+import org.restlet.Application;
+import org.restlet.Restlet;
+import org.restlet.routing.Router;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Collections;
+import java.util.Set;
+
+/**
+ * Restlet servlet handling /<context>/<collection>/config/* URL paths
+ */
+public class SolrConfigRestApi extends Application {
+  public static final Logger log = LoggerFactory.getLogger(SolrConfigRestApi.class);
+
+  private Router router;
+
+  public SolrConfigRestApi() {
+    router = new Router(getContext());
+  }
+
+  /**
+   * TODO: If and when this API has reserved endpoints, add them to the set returned here.
+   * @see SolrSchemaRestApi#getReservedEndpoints()
+   */
+  public static Set<String> getReservedEndpoints() {
+    return Collections.emptySet();
+  }
+
+  @Override
+  public void stop() throws Exception {
+    if (null != router) {
+      router.stop();
+    }
+  }
+
+  /**
+   * Bind URL paths to the appropriate ServerResource subclass. 
+   */
+  @Override
+  public synchronized Restlet createInboundRoot() {
+
+    log.info("createInboundRoot started for /config");
+    
+    router.attachDefault(RestManager.ManagedEndpoint.class);
+    
+    // attach all the dynamically registered /config resources
+    RestManager restManager = 
+        RestManager.getRestManager(SolrRequestInfo.getRequestInfo());
+    restManager.attachManagedResources(RestManager.CONFIG_BASE_PATH, router);    
+
+    log.info("createInboundRoot complete for /config");
+
+    return router;
+  }  
+}

Copied: lucene/dev/trunk/solr/core/src/java/org/apache/solr/rest/SolrSchemaRestApi.java (from r1574954, lucene/dev/trunk/solr/core/src/java/org/apache/solr/rest/SolrRestApi.java)
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/solr/core/src/java/org/apache/solr/rest/SolrSchemaRestApi.java?p2=lucene/dev/trunk/solr/core/src/java/org/apache/solr/rest/SolrSchemaRestApi.java&p1=lucene/dev/trunk/solr/core/src/java/org/apache/solr/rest/SolrRestApi.java&r1=1574954&r2=1576939&rev=1576939&view=diff
==============================================================================
--- lucene/dev/trunk/solr/core/src/java/org/apache/solr/rest/SolrRestApi.java (original)
+++ lucene/dev/trunk/solr/core/src/java/org/apache/solr/rest/SolrSchemaRestApi.java Wed Mar 12 21:52:49 2014
@@ -16,8 +16,8 @@ package org.apache.solr.rest;
  * limitations under the License.
  */
 
+import org.apache.solr.request.SolrRequestInfo;
 import org.apache.solr.rest.schema.CopyFieldCollectionResource;
-import org.apache.solr.rest.schema.DefaultSchemaResource;
 import org.apache.solr.rest.schema.SchemaResource;
 import org.apache.solr.rest.schema.DefaultSearchFieldResource;
 import org.apache.solr.rest.schema.DynamicFieldCollectionResource;
@@ -39,10 +39,16 @@ import org.restlet.routing.Router;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import java.util.Collections;
+import java.util.HashSet;
 import java.util.Locale;
+import java.util.Set;
 
-public class SolrRestApi extends Application {
-  public static final Logger log = LoggerFactory.getLogger(SolrRestApi.class);
+/**
+ * Restlet servlet handling /&lt;context&gt;/&lt;collection&gt;/schema/* URL paths
+ */
+public class SolrSchemaRestApi extends Application {
+  public static final Logger log = LoggerFactory.getLogger(SolrSchemaRestApi.class);
   public static final String FIELDS_PATH = "/" + IndexSchema.FIELDS;
   
   public static final String DYNAMIC_FIELDS = IndexSchema.DYNAMIC_FIELDS.toLowerCase(Locale.ROOT);
@@ -73,9 +79,28 @@ public class SolrRestApi extends Applica
   public static final String UNIQUE_KEY_FIELD = IndexSchema.UNIQUE_KEY.toLowerCase(Locale.ROOT);
   public static final String UNIQUE_KEY_FIELD_PATH = "/" + UNIQUE_KEY_FIELD;
 
+  /**
+   * Returns reserved endpoints under /schema
+   */
+  public static Set<String> getReservedEndpoints() {
+    Set<String> reservedEndpoints = new HashSet<>();
+    reservedEndpoints.add(RestManager.SCHEMA_BASE_PATH + FIELDS_PATH);
+    reservedEndpoints.add(RestManager.SCHEMA_BASE_PATH + DYNAMIC_FIELDS_PATH);
+    reservedEndpoints.add(RestManager.SCHEMA_BASE_PATH + FIELDTYPES_PATH);
+    reservedEndpoints.add(RestManager.SCHEMA_BASE_PATH + NAME_PATH);
+    reservedEndpoints.add(RestManager.SCHEMA_BASE_PATH + COPY_FIELDS_PATH);
+    reservedEndpoints.add(RestManager.SCHEMA_BASE_PATH + VERSION_PATH);
+    reservedEndpoints.add(RestManager.SCHEMA_BASE_PATH + DEFAULT_SEARCH_FIELD_PATH);
+    reservedEndpoints.add(RestManager.SCHEMA_BASE_PATH + SIMILARITY_PATH);
+    reservedEndpoints.add(RestManager.SCHEMA_BASE_PATH + SOLR_QUERY_PARSER_PATH);
+    reservedEndpoints.add(RestManager.SCHEMA_BASE_PATH + DEFAULT_OPERATOR_PATH);
+    reservedEndpoints.add(RestManager.SCHEMA_BASE_PATH + UNIQUE_KEY_FIELD_PATH);
+    return Collections.unmodifiableSet(reservedEndpoints);
+  }
+
   private Router router;
 
-  public SolrRestApi() {
+  public SolrSchemaRestApi() {
     router = new Router(getContext());
   }
 
@@ -92,8 +117,8 @@ public class SolrRestApi extends Applica
   @Override
   public synchronized Restlet createInboundRoot() {
 
-    log.info("createInboundRoot started");
-    
+    log.info("createInboundRoot started for /schema");
+
     router.attach("", SchemaResource.class);
     // Allow a trailing slash on full-schema requests
     router.attach("/", SchemaResource.class);
@@ -131,10 +156,14 @@ public class SolrRestApi extends Applica
     router.attach(SOLR_QUERY_PARSER_PATH, SolrQueryParserResource.class);
     router.attach(DEFAULT_OPERATOR_PATH, SolrQueryParserDefaultOperatorResource.class);
 
-    router.attachDefault(DefaultSchemaResource.class);
+    router.attachDefault(RestManager.ManagedEndpoint.class);
+    
+    // attach all the dynamically registered schema resources
+    RestManager.getRestManager(SolrRequestInfo.getRequestInfo())
+        .attachManagedResources(RestManager.SCHEMA_BASE_PATH, router);
 
-    log.info("createInboundRoot complete");
+    log.info("createInboundRoot complete for /schema");
 
     return router;
-  }
+  }  
 }

Modified: lucene/dev/trunk/solr/core/src/java/org/apache/solr/rest/schema/BaseFieldResource.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/solr/core/src/java/org/apache/solr/rest/schema/BaseFieldResource.java?rev=1576939&r1=1576938&r2=1576939&view=diff
==============================================================================
--- lucene/dev/trunk/solr/core/src/java/org/apache/solr/rest/schema/BaseFieldResource.java (original)
+++ lucene/dev/trunk/solr/core/src/java/org/apache/solr/rest/schema/BaseFieldResource.java Wed Mar 12 21:52:49 2014
@@ -18,6 +18,7 @@ package org.apache.solr.rest.schema;
 
 import org.apache.solr.common.params.CommonParams;
 import org.apache.solr.common.util.SimpleOrderedMap;
+import org.apache.solr.rest.BaseSolrResource;
 import org.apache.solr.schema.IndexSchema;
 import org.apache.solr.schema.SchemaField;
 import org.restlet.resource.ResourceException;
@@ -28,7 +29,7 @@ import java.util.LinkedHashSet;
 /**
  * Base class for Schema Field and DynamicField requests.
  */
-abstract class BaseFieldResource extends BaseSchemaResource {
+abstract class BaseFieldResource extends BaseSolrResource {
   protected static final String INCLUDE_DYNAMIC_PARAM = "includeDynamic";
   private static final String DYNAMIC_BASE = "dynamicBase";
 

Modified: lucene/dev/trunk/solr/core/src/java/org/apache/solr/rest/schema/BaseFieldTypeResource.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/solr/core/src/java/org/apache/solr/rest/schema/BaseFieldTypeResource.java?rev=1576939&r1=1576938&r2=1576939&view=diff
==============================================================================
--- lucene/dev/trunk/solr/core/src/java/org/apache/solr/rest/schema/BaseFieldTypeResource.java (original)
+++ lucene/dev/trunk/solr/core/src/java/org/apache/solr/rest/schema/BaseFieldTypeResource.java Wed Mar 12 21:52:49 2014
@@ -18,6 +18,7 @@ package org.apache.solr.rest.schema;
  */
 
 import org.apache.solr.common.util.SimpleOrderedMap;
+import org.apache.solr.rest.BaseSolrResource;
 import org.apache.solr.schema.FieldType;
 import org.apache.solr.schema.IndexSchema;
 import org.restlet.resource.ResourceException;
@@ -27,7 +28,7 @@ import java.util.List;
 /**
  * Base class for the FieldType resource classes.
  */
-abstract class BaseFieldTypeResource extends BaseSchemaResource {
+abstract class BaseFieldTypeResource extends BaseSolrResource {
   private boolean showDefaults;
 
   protected BaseFieldTypeResource() {

Modified: lucene/dev/trunk/solr/core/src/java/org/apache/solr/rest/schema/DefaultSearchFieldResource.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/solr/core/src/java/org/apache/solr/rest/schema/DefaultSearchFieldResource.java?rev=1576939&r1=1576938&r2=1576939&view=diff
==============================================================================
--- lucene/dev/trunk/solr/core/src/java/org/apache/solr/rest/schema/DefaultSearchFieldResource.java (original)
+++ lucene/dev/trunk/solr/core/src/java/org/apache/solr/rest/schema/DefaultSearchFieldResource.java Wed Mar 12 21:52:49 2014
@@ -18,6 +18,7 @@ package org.apache.solr.rest.schema;
 
 import org.apache.solr.common.SolrException;
 import org.apache.solr.common.SolrException.ErrorCode;
+import org.apache.solr.rest.BaseSolrResource;
 import org.apache.solr.rest.GETable;
 import org.apache.solr.schema.IndexSchema;
 import org.restlet.representation.Representation;
@@ -29,7 +30,7 @@ import org.slf4j.LoggerFactory;
 /**
  * This class responds to requests at /solr/(corename)/schema/defaultsearchfield
  */
-public class DefaultSearchFieldResource extends BaseSchemaResource implements GETable {
+public class DefaultSearchFieldResource extends BaseSolrResource implements GETable {
   private static final Logger log = LoggerFactory.getLogger(DefaultSearchFieldResource.class);
 
   public DefaultSearchFieldResource() {

Modified: lucene/dev/trunk/solr/core/src/java/org/apache/solr/rest/schema/SchemaNameResource.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/solr/core/src/java/org/apache/solr/rest/schema/SchemaNameResource.java?rev=1576939&r1=1576938&r2=1576939&view=diff
==============================================================================
--- lucene/dev/trunk/solr/core/src/java/org/apache/solr/rest/schema/SchemaNameResource.java (original)
+++ lucene/dev/trunk/solr/core/src/java/org/apache/solr/rest/schema/SchemaNameResource.java Wed Mar 12 21:52:49 2014
@@ -17,6 +17,7 @@ package org.apache.solr.rest.schema;
  */
 
 import org.apache.solr.common.SolrException;
+import org.apache.solr.rest.BaseSolrResource;
 import org.apache.solr.rest.GETable;
 import org.apache.solr.schema.IndexSchema;
 import org.restlet.representation.Representation;
@@ -28,7 +29,7 @@ import org.slf4j.LoggerFactory;
 /**
  * This class responds to requests at /solr/(corename)/schema/name
  */
-public class SchemaNameResource extends BaseSchemaResource implements GETable {
+public class SchemaNameResource extends BaseSolrResource implements GETable {
   private static final Logger log = LoggerFactory.getLogger(SchemaNameResource.class);
    
   public SchemaNameResource() {

Modified: lucene/dev/trunk/solr/core/src/java/org/apache/solr/rest/schema/SchemaResource.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/solr/core/src/java/org/apache/solr/rest/schema/SchemaResource.java?rev=1576939&r1=1576938&r2=1576939&view=diff
==============================================================================
--- lucene/dev/trunk/solr/core/src/java/org/apache/solr/rest/schema/SchemaResource.java (original)
+++ lucene/dev/trunk/solr/core/src/java/org/apache/solr/rest/schema/SchemaResource.java Wed Mar 12 21:52:49 2014
@@ -16,6 +16,7 @@ package org.apache.solr.rest.schema;
  * limitations under the License.
  */
 
+import org.apache.solr.rest.BaseSolrResource;
 import org.apache.solr.rest.GETable;
 import org.apache.solr.schema.IndexSchema;
 import org.restlet.representation.Representation;
@@ -26,7 +27,7 @@ import org.slf4j.LoggerFactory;
 /**
  * This class responds to requests at /solr/(corename)/schema
  */
-public class SchemaResource extends BaseSchemaResource implements GETable {
+public class SchemaResource extends BaseSolrResource implements GETable {
   private static final Logger log = LoggerFactory.getLogger(SchemaResource.class);
 
   public SchemaResource() {

Modified: lucene/dev/trunk/solr/core/src/java/org/apache/solr/rest/schema/SchemaSimilarityResource.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/solr/core/src/java/org/apache/solr/rest/schema/SchemaSimilarityResource.java?rev=1576939&r1=1576938&r2=1576939&view=diff
==============================================================================
--- lucene/dev/trunk/solr/core/src/java/org/apache/solr/rest/schema/SchemaSimilarityResource.java (original)
+++ lucene/dev/trunk/solr/core/src/java/org/apache/solr/rest/schema/SchemaSimilarityResource.java Wed Mar 12 21:52:49 2014
@@ -16,8 +16,8 @@ package org.apache.solr.rest.schema;
  * limitations under the License.
  */
 
+import org.apache.solr.rest.BaseSolrResource;
 import org.apache.solr.rest.GETable;
-import org.apache.solr.rest.SolrRestApi;
 import org.apache.solr.schema.IndexSchema;
 import org.restlet.representation.Representation;
 import org.restlet.resource.ResourceException;
@@ -28,7 +28,7 @@ import org.slf4j.LoggerFactory;
 /**
  * This class responds to requests at /solr/(corename)/schema/similarity
  */
-public class SchemaSimilarityResource extends BaseSchemaResource implements GETable {
+public class SchemaSimilarityResource extends BaseSolrResource implements GETable {
   private static final Logger log = LoggerFactory.getLogger(SchemaSimilarityResource.class);
 
   public SchemaSimilarityResource() {

Modified: lucene/dev/trunk/solr/core/src/java/org/apache/solr/rest/schema/SchemaVersionResource.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/solr/core/src/java/org/apache/solr/rest/schema/SchemaVersionResource.java?rev=1576939&r1=1576938&r2=1576939&view=diff
==============================================================================
--- lucene/dev/trunk/solr/core/src/java/org/apache/solr/rest/schema/SchemaVersionResource.java (original)
+++ lucene/dev/trunk/solr/core/src/java/org/apache/solr/rest/schema/SchemaVersionResource.java Wed Mar 12 21:52:49 2014
@@ -16,8 +16,8 @@ package org.apache.solr.rest.schema;
  * limitations under the License.
  */
 
+import org.apache.solr.rest.BaseSolrResource;
 import org.apache.solr.rest.GETable;
-import org.apache.solr.rest.SolrRestApi;
 import org.apache.solr.schema.IndexSchema;
 import org.restlet.representation.Representation;
 import org.restlet.resource.ResourceException;
@@ -28,7 +28,7 @@ import org.slf4j.LoggerFactory;
 /**
  * This class responds to requests at /solr/(corename)/schema/version
  */
-public class SchemaVersionResource extends BaseSchemaResource implements GETable {
+public class SchemaVersionResource extends BaseSolrResource implements GETable {
   private static final Logger log = LoggerFactory.getLogger(SchemaVersionResource.class);
 
   public SchemaVersionResource() {

Modified: lucene/dev/trunk/solr/core/src/java/org/apache/solr/rest/schema/SolrQueryParserDefaultOperatorResource.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/solr/core/src/java/org/apache/solr/rest/schema/SolrQueryParserDefaultOperatorResource.java?rev=1576939&r1=1576938&r2=1576939&view=diff
==============================================================================
--- lucene/dev/trunk/solr/core/src/java/org/apache/solr/rest/schema/SolrQueryParserDefaultOperatorResource.java (original)
+++ lucene/dev/trunk/solr/core/src/java/org/apache/solr/rest/schema/SolrQueryParserDefaultOperatorResource.java Wed Mar 12 21:52:49 2014
@@ -16,6 +16,7 @@ package org.apache.solr.rest.schema;
  * limitations under the License.
  */
 
+import org.apache.solr.rest.BaseSolrResource;
 import org.apache.solr.rest.GETable;
 import org.apache.solr.schema.IndexSchema;
 import org.restlet.representation.Representation;
@@ -27,7 +28,7 @@ import org.slf4j.LoggerFactory;
 /**
  * This class responds to requests at /solr/(corename)/schema/solrqueryparser/defaultoperator
  */
-public class SolrQueryParserDefaultOperatorResource extends BaseSchemaResource implements GETable {
+public class SolrQueryParserDefaultOperatorResource extends BaseSolrResource implements GETable {
   private static final Logger log = LoggerFactory.getLogger(SolrQueryParserDefaultOperatorResource.class);
 
   public SolrQueryParserDefaultOperatorResource() {

Modified: lucene/dev/trunk/solr/core/src/java/org/apache/solr/rest/schema/SolrQueryParserResource.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/solr/core/src/java/org/apache/solr/rest/schema/SolrQueryParserResource.java?rev=1576939&r1=1576938&r2=1576939&view=diff
==============================================================================
--- lucene/dev/trunk/solr/core/src/java/org/apache/solr/rest/schema/SolrQueryParserResource.java (original)
+++ lucene/dev/trunk/solr/core/src/java/org/apache/solr/rest/schema/SolrQueryParserResource.java Wed Mar 12 21:52:49 2014
@@ -17,6 +17,7 @@ package org.apache.solr.rest.schema;
  */
 
 import org.apache.solr.common.util.SimpleOrderedMap;
+import org.apache.solr.rest.BaseSolrResource;
 import org.apache.solr.rest.GETable;
 import org.apache.solr.schema.IndexSchema;
 import org.restlet.representation.Representation;
@@ -28,7 +29,7 @@ import org.slf4j.LoggerFactory;
 /**
  * This class responds to requests at /solr/(corename)/schema/solrqueryparser
  */
-public class SolrQueryParserResource extends BaseSchemaResource implements GETable {
+public class SolrQueryParserResource extends BaseSolrResource implements GETable {
   private static final Logger log = LoggerFactory.getLogger(SolrQueryParserResource.class);
 
   public SolrQueryParserResource() {

Modified: lucene/dev/trunk/solr/core/src/java/org/apache/solr/rest/schema/UniqueKeyFieldResource.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/solr/core/src/java/org/apache/solr/rest/schema/UniqueKeyFieldResource.java?rev=1576939&r1=1576938&r2=1576939&view=diff
==============================================================================
--- lucene/dev/trunk/solr/core/src/java/org/apache/solr/rest/schema/UniqueKeyFieldResource.java (original)
+++ lucene/dev/trunk/solr/core/src/java/org/apache/solr/rest/schema/UniqueKeyFieldResource.java Wed Mar 12 21:52:49 2014
@@ -16,6 +16,7 @@ package org.apache.solr.rest.schema;
  * limitations under the License.
  */
 
+import org.apache.solr.rest.BaseSolrResource;
 import org.apache.solr.rest.GETable;
 import org.apache.solr.schema.IndexSchema;
 import org.restlet.representation.Representation;
@@ -27,7 +28,7 @@ import org.slf4j.LoggerFactory;
 /**
  * This class responds to requests at /solr/(corename)/schema/uniquekey
  */
-public class UniqueKeyFieldResource extends BaseSchemaResource implements GETable {
+public class UniqueKeyFieldResource extends BaseSolrResource implements GETable {
   private static final Logger log = LoggerFactory.getLogger(UniqueKeyFieldResource.class);
   
   public UniqueKeyFieldResource() {

Added: lucene/dev/trunk/solr/core/src/java/org/apache/solr/rest/schema/analysis/ManagedWordSetResource.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/solr/core/src/java/org/apache/solr/rest/schema/analysis/ManagedWordSetResource.java?rev=1576939&view=auto
==============================================================================
--- lucene/dev/trunk/solr/core/src/java/org/apache/solr/rest/schema/analysis/ManagedWordSetResource.java (added)
+++ lucene/dev/trunk/solr/core/src/java/org/apache/solr/rest/schema/analysis/ManagedWordSetResource.java Wed Mar 12 21:52:49 2014
@@ -0,0 +1,193 @@
+package org.apache.solr.rest.schema.analysis;
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Locale;
+import java.util.Set;
+import java.util.SortedSet;
+import java.util.TreeSet;
+
+import org.apache.solr.common.SolrException;
+import org.apache.solr.common.SolrException.ErrorCode;
+import org.apache.solr.common.util.NamedList;
+import org.apache.solr.core.SolrResourceLoader;
+import org.apache.solr.response.SolrQueryResponse;
+import org.apache.solr.rest.BaseSolrResource;
+import org.apache.solr.rest.ManagedResource;
+import org.apache.solr.rest.ManagedResourceStorage.StorageIO;
+
+/**
+ * ManagedResource implementation for managing a set of words using the REST API;
+ * useful for managing stop words and/or protected words for analysis components 
+ * like the KeywordMarkerFilter.
+ */
+public class ManagedWordSetResource extends ManagedResource 
+  implements ManagedResource.ChildResourceSupport {
+  
+  public static final String WORD_SET_JSON_FIELD = "wordSet";
+  public static final String IGNORE_CASE_INIT_ARG = "ignoreCase";
+      
+  private SortedSet<String> managedWords = null;  
+  
+  public ManagedWordSetResource(String resourceId, SolrResourceLoader loader, StorageIO storageIO) 
+      throws SolrException {
+    super(resourceId, loader, storageIO);
+  }
+
+  /**
+   * Returns the set of words in this managed word set.
+   */
+  public Set<String> getWordSet() {
+    return Collections.unmodifiableSet(managedWords);
+  }
+
+  /**
+   * Returns the boolean value of the {@link #IGNORE_CASE_INIT_ARG} init arg,
+   * or the default value (false) if it has not been specified
+   */
+  public boolean getIgnoreCase() {
+    return getIgnoreCase(managedInitArgs);
+  }
+
+  /**
+   * Returns the boolean value of the {@link #IGNORE_CASE_INIT_ARG} init arg,
+   * or the default value (false) if it has not been specified
+   */
+  public boolean getIgnoreCase(NamedList<?> initArgs) {
+    Boolean ignoreCase = initArgs.getBooleanArg(IGNORE_CASE_INIT_ARG);
+    // ignoreCase = false by default
+    return null == ignoreCase ? false : ignoreCase;
+  }
+               
+  /**
+   * Invoked when loading data from storage to initialize the 
+   * list of words managed by this instance. A load of the
+   * data can happen many times throughout the life cycle of this
+   * object.
+   */
+  @SuppressWarnings("unchecked")
+  @Override
+  protected void onManagedDataLoadedFromStorage(NamedList<?> initArgs, Object data)
+      throws SolrException {
+
+    // the default behavior is to not ignore case,
+    boolean ignoreCase = getIgnoreCase(initArgs);
+    if (null == initArgs.get(IGNORE_CASE_INIT_ARG)) {
+      // Explicitly include the default value of ignoreCase
+      ((NamedList<Object>)initArgs).add(IGNORE_CASE_INIT_ARG, false);
+    }
+
+    managedWords = new TreeSet<>();
+    if (data != null) {
+      List<String> wordList = (List<String>)data;
+      if (ignoreCase) {
+        // if we're ignoring case, just lowercase all terms as we add them
+        for (String word : wordList) {
+          managedWords.add(word.toLowerCase(Locale.ROOT));
+        }
+      } else {
+        managedWords.addAll(wordList);        
+      }
+    }
+    
+    log.info("Loaded "+managedWords.size()+" words for "+getResourceId());      
+  }
+          
+  /**
+   * Implements the GET request to provide the list of words to the client.
+   * Alternatively, if a specific word is requested, then it is returned
+   * or a 404 is raised, indicating that the requested word does not exist.
+   */
+  @Override
+  public void doGet(BaseSolrResource endpoint, String childId) {
+    SolrQueryResponse response = endpoint.getSolrResponse();
+    if (childId != null) {
+      // downcase arg if we're configured to ignoreCase
+      String key = getIgnoreCase() ? childId.toLowerCase(Locale.ROOT) : childId;       
+      if (!managedWords.contains(key))
+        throw new SolrException(ErrorCode.NOT_FOUND, 
+            String.format(Locale.ROOT, "%s not found in %s", childId, getResourceId()));
+        
+      response.add(childId, key);
+    } else {
+      response.add(WORD_SET_JSON_FIELD, buildMapToStore(managedWords));      
+    }
+  }  
+
+  /**
+   * Deletes words managed by this resource.
+   */
+  @Override
+  public synchronized void doDeleteChild(BaseSolrResource endpoint, String childId) {
+    // downcase arg if we're configured to ignoreCase
+    String key = getIgnoreCase() ? childId.toLowerCase(Locale.ROOT) : childId;       
+    if (!managedWords.contains(key))
+      throw new SolrException(ErrorCode.NOT_FOUND, 
+          String.format(Locale.ROOT, "%s not found in %s", childId, getResourceId()));
+  
+    managedWords.remove(key);
+    storeManagedData(managedWords);
+    log.info("Removed word: {}", key);
+  }  
+  
+  /**
+   * Applies updates to the word set being managed by this resource.
+   */
+  @SuppressWarnings("unchecked")
+  @Override
+  protected Object applyUpdatesToManagedData(Object updates) {
+    boolean madeChanges = false;
+    List<String> words = (List<String>)updates;
+    
+    log.info("Applying updates: "+words);
+    boolean ignoreCase = getIgnoreCase();    
+    for (String word : words) {
+      if (ignoreCase)
+        word = word.toLowerCase(Locale.ROOT);
+      
+      if (managedWords.add(word)) {
+        madeChanges = true;
+        log.info("Added word: {}", word);
+      }
+    }              
+    return madeChanges ? managedWords : null;
+  }
+  
+  @Override
+  protected boolean updateInitArgs(NamedList<?> updatedArgs) {
+    if (updatedArgs == null || updatedArgs.size() == 0) {
+      return false;
+    }
+    boolean currentIgnoreCase = getIgnoreCase(managedInitArgs);
+    boolean updatedIgnoreCase = getIgnoreCase(updatedArgs);
+    if (currentIgnoreCase == true && updatedIgnoreCase == false) {
+      throw new SolrException(ErrorCode.BAD_REQUEST,
+          "Changing a managed word set's ignoreCase arg from true to false is not permitted.");
+    } else if (currentIgnoreCase == false && updatedIgnoreCase == true) {
+      // rebuild the word set on policy change from case-sensitive to case-insensitive
+      SortedSet<String> updatedWords = new TreeSet<>();
+      for (String word : managedWords) {
+        updatedWords.add(word.toLowerCase(Locale.ROOT));
+      }
+      managedWords = updatedWords;
+    }
+    // otherwise currentIgnoreCase == updatedIgnoreCase: nothing to do
+    return super.updateInitArgs(updatedArgs);
+  }  
+}

Added: lucene/dev/trunk/solr/core/src/java/org/apache/solr/rest/schema/analysis/package.html
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/solr/core/src/java/org/apache/solr/rest/schema/analysis/package.html?rev=1576939&view=auto
==============================================================================
--- lucene/dev/trunk/solr/core/src/java/org/apache/solr/rest/schema/analysis/package.html (added)
+++ lucene/dev/trunk/solr/core/src/java/org/apache/solr/rest/schema/analysis/package.html Wed Mar 12 21:52:49 2014
@@ -0,0 +1,29 @@
+<!doctype html public "-//w3c//dtd html 4.0 transitional//en">
+<!--
+  Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  -->
+
+<!doctype html public "-//w3c//dtd html 4.0 transitional//en">
+<html>
+<head>
+  <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
+</head>
+<body>
+<p>
+  Analysis-related functionality for RESTful API access to the Solr Schema using Restlet.
+</p>
+</body>
+</html>

Modified: lucene/dev/trunk/solr/core/src/java/org/apache/solr/servlet/SolrDispatchFilter.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/solr/core/src/java/org/apache/solr/servlet/SolrDispatchFilter.java?rev=1576939&r1=1576938&r2=1576939&view=diff
==============================================================================
--- lucene/dev/trunk/solr/core/src/java/org/apache/solr/servlet/SolrDispatchFilter.java (original)
+++ lucene/dev/trunk/solr/core/src/java/org/apache/solr/servlet/SolrDispatchFilter.java Wed Mar 12 21:52:49 2014
@@ -20,6 +20,7 @@ package org.apache.solr.servlet;
 import org.apache.commons.io.IOUtils;
 import org.apache.commons.lang.StringUtils;
 import org.apache.http.client.HttpClient;
+import org.apache.http.client.methods.HttpDelete;
 import org.apache.http.client.methods.HttpEntityEnclosingRequestBase;
 import org.apache.http.client.methods.HttpGet;
 import org.apache.http.client.methods.HttpHead;
@@ -364,8 +365,9 @@ public class SolrDispatchFilter implemen
           // get or create/cache the parser for the core
           SolrRequestParsers parser = config.getRequestParsers();
 
-          // Handle /schema/* paths via Restlet
-          if( path.startsWith("/schema") ) {
+          // Handle /schema/* and /config/* paths via Restlet
+          if( path.equals("/schema") || path.startsWith("/schema/")
+              || path.equals("/config") || path.startsWith("/config/")) {
             solrReq = parser.parse(core, path, req);
             SolrRequestInfo.setRequestInfo(new SolrRequestInfo(solrReq, new SolrQueryResponse()));
             if( path.equals(req.getServletPath()) ) {
@@ -536,6 +538,9 @@ public class SolrDispatchFilter implemen
         entityRequest.setEntity(entity);
         method = entityRequest;
       }
+      else if ("DELETE".equals(req.getMethod())) {
+        method = new HttpDelete(urlstr);
+      }
       else {
         throw new SolrException(SolrException.ErrorCode.SERVER_ERROR,
           "Unexpected method type: " + req.getMethod());

Modified: lucene/dev/trunk/solr/core/src/java/org/apache/solr/servlet/SolrRequestParsers.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/solr/core/src/java/org/apache/solr/servlet/SolrRequestParsers.java?rev=1576939&r1=1576938&r2=1576939&view=diff
==============================================================================
--- lucene/dev/trunk/solr/core/src/java/org/apache/solr/servlet/SolrRequestParsers.java (original)
+++ lucene/dev/trunk/solr/core/src/java/org/apache/solr/servlet/SolrRequestParsers.java Wed Mar 12 21:52:49 2014
@@ -665,7 +665,9 @@ public class SolrRequestParsers 
     {
       String method = req.getMethod().toUpperCase(Locale.ROOT);
       if ("GET".equals(method) || "HEAD".equals(method) 
-          || ("PUT".equals(method) && req.getRequestURI().contains("/schema"))) {
+          || (("PUT".equals(method) || "DELETE".equals(method))
+              && (req.getRequestURI().contains("/schema")
+                  || req.getRequestURI().contains("/config")))) {
         return parseQueryString(req.getQueryString());
       }
       if ("POST".equals( method ) ) {

Modified: lucene/dev/trunk/solr/core/src/test-files/solr/collection1/conf/solrconfig.xml
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/solr/core/src/test-files/solr/collection1/conf/solrconfig.xml?rev=1576939&r1=1576938&r2=1576939&view=diff
==============================================================================
--- lucene/dev/trunk/solr/core/src/test-files/solr/collection1/conf/solrconfig.xml (original)
+++ lucene/dev/trunk/solr/core/src/test-files/solr/collection1/conf/solrconfig.xml Wed Mar 12 21:52:49 2014
@@ -575,5 +575,13 @@
     <processor class="solr.RunUpdateProcessorFactory" />
   </updateRequestProcessorChain>  
 
+  <restManager>
+    <!-- 
+    IMPORTANT: Due to the Lucene SecurityManager, tests can only write to their runtime directory or below.
+    But its easier to just keep everything in memory for testing so no remnants are left behind.
+    -->
+    <str name="storageIO">org.apache.solr.rest.ManagedResourceStorage$InMemoryStorageIO</str>
+  </restManager>
+
 </config>
 

Modified: lucene/dev/trunk/solr/core/src/test/org/apache/solr/rest/SolrRestletTestBase.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/solr/core/src/test/org/apache/solr/rest/SolrRestletTestBase.java?rev=1576939&r1=1576938&r2=1576939&view=diff
==============================================================================
--- lucene/dev/trunk/solr/core/src/test/org/apache/solr/rest/SolrRestletTestBase.java (original)
+++ lucene/dev/trunk/solr/core/src/test/org/apache/solr/rest/SolrRestletTestBase.java Wed Mar 12 21:52:49 2014
@@ -24,13 +24,26 @@ import org.restlet.ext.servlet.ServerSer
 import java.util.SortedMap;
 import java.util.TreeMap;
 
+/**
+ * Base class for Solr Restlet-based tests. Creates test harness,
+ * including "extra" servlets for all Solr Restlet Application subclasses.
+ */
 abstract public class SolrRestletTestBase extends RestTestBase {
+
+  /**
+   * Creates test harness, including "extra" servlets for all
+   * Solr Restlet Application subclasses.
+   */
   @BeforeClass
   public static void init() throws Exception {
     final SortedMap<ServletHolder,String> extraServlets = new TreeMap<>();
-    final ServletHolder solrRestApi = new ServletHolder("SolrRestApi", ServerServlet.class);
-    solrRestApi.setInitParameter("org.restlet.application", "org.apache.solr.rest.SolrRestApi");
-    extraServlets.put(solrRestApi, "/schema/*");  // '/schema/*' matches '/schema', '/schema/', and '/schema/whatever...'
+    final ServletHolder solrSchemaRestApi = new ServletHolder("SolrSchemaRestApi", ServerServlet.class);
+    solrSchemaRestApi.setInitParameter("org.restlet.application", "org.apache.solr.rest.SolrSchemaRestApi");
+    extraServlets.put(solrSchemaRestApi, "/schema/*");  // '/schema/*' matches '/schema', '/schema/', and '/schema/whatever...'
+
+    final ServletHolder solrConfigRestApi = new ServletHolder("SolrConfigRestApi", ServerServlet.class);
+    solrConfigRestApi.setInitParameter("org.restlet.application", "org.apache.solr.rest.SolrConfigRestApi");
+    extraServlets.put(solrConfigRestApi, "/config/*");
 
     createJettyAndHarness(TEST_HOME(), "solrconfig.xml", "schema-rest.xml", "/solr", true, extraServlets);
   }

Added: lucene/dev/trunk/solr/core/src/test/org/apache/solr/rest/TestManagedResource.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/solr/core/src/test/org/apache/solr/rest/TestManagedResource.java?rev=1576939&view=auto
==============================================================================
--- lucene/dev/trunk/solr/core/src/test/org/apache/solr/rest/TestManagedResource.java (added)
+++ lucene/dev/trunk/solr/core/src/test/org/apache/solr/rest/TestManagedResource.java Wed Mar 12 21:52:49 2014
@@ -0,0 +1,327 @@
+package org.apache.solr.rest;
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.Serializable;
+import java.io.StringReader;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.lucene.util.BytesRef;
+import org.apache.solr.SolrTestCaseJ4;
+import org.apache.solr.common.SolrException;
+import org.apache.solr.common.util.NamedList;
+import org.apache.solr.core.SolrResourceLoader;
+import org.apache.solr.rest.ManagedResourceStorage.StorageIO;
+import org.junit.Test;
+import org.noggit.JSONParser;
+import org.noggit.ObjectBuilder;
+
+/**
+ * Tests {@link ManagedResource} functionality.
+ */
+public class TestManagedResource extends SolrTestCaseJ4 {
+
+  /**
+   * Mock class that acts like an analysis component that depends on
+   * data managed by a ManagedResource
+   */
+  private class MockAnalysisComponent implements ManagedResourceObserver {
+    
+    private boolean wasNotified = false;
+
+    @SuppressWarnings("unchecked")
+    @Override
+    public void onManagedResourceInitialized(NamedList<?> args, ManagedResource res) throws SolrException {
+
+      assertEquals("someVal", args.get("someArg"));
+
+      assertTrue(res instanceof ManagedTestResource);      
+      ManagedTestResource testRes = (ManagedTestResource)res;
+      List<String> data = (List<String>)testRes.managedData;
+      assertTrue(data.contains("1"));
+      assertTrue(data.contains("2"));
+      assertTrue(data.contains("3"));
+      
+      wasNotified = true;
+    }
+  }
+
+  private class ManagedTestResource extends ManagedResource {
+
+    private Object managedData;
+    
+    private ManagedTestResource(String resourceId, SolrResourceLoader loader,
+        StorageIO storageIO) throws SolrException {
+      super(resourceId, loader, storageIO);
+    }
+
+    @Override
+    protected void onManagedDataLoadedFromStorage(NamedList<?> managedInitArgs, Object managedData)
+        throws SolrException {
+
+      assertNotNull(managedData);
+      assertTrue(managedData instanceof List);
+
+      // {'initArgs':{'someArg':'someVal', 'arg2':true, 'arg3':['one','two','three'],
+      //              'arg4':18, 'arg5':0.9, 'arg6':{ 'uno':1, 'dos':2 }},'"
+      assertEquals("someVal", managedInitArgs.get("someArg"));
+      assertEquals(true, managedInitArgs.get("arg2"));
+      List<String> arg3List = Arrays.asList("one", "two", "three");
+      assertEquals(arg3List, managedInitArgs.get("arg3"));
+      assertEquals(18L, managedInitArgs.get("arg4"));
+      assertEquals(0.9, managedInitArgs.get("arg5"));
+      Map<String,Long> arg6map = new LinkedHashMap<>(2);
+      arg6map.put("uno", 1L);
+      arg6map.put("dos", 2L);
+      assertEquals(arg6map, managedInitArgs.get("arg6"));
+
+      this.managedData = managedData;
+    }
+
+    
+    // NOTE: These methods are better tested from the REST API
+    // so they are stubbed out here and not used in this test
+    
+    @Override
+    protected Object applyUpdatesToManagedData(Object updates) {
+      return null;
+    }
+
+    @Override
+    public void doDeleteChild(BaseSolrResource endpoint, String childId) {}
+
+    @Override
+    public void doGet(BaseSolrResource endpoint, String childId) {}    
+  }
+
+  /**
+   * Implements a Java serialization based storage format.
+   */
+  private class SerializableStorage extends ManagedResourceStorage {
+    
+    SerializableStorage(StorageIO storageIO, SolrResourceLoader loader) {
+      super(storageIO, loader);
+    }
+
+    @Override
+    public Object load(String resourceId) throws IOException {
+      String storedId = getStoredResourceId(resourceId);
+      InputStream inputStream = storageIO.openInputStream(storedId);
+      if (inputStream == null) {
+        return null;
+      }
+      Object serialized = null;
+      ObjectInputStream ois = null;
+      try {
+        ois = new ObjectInputStream(inputStream);
+        serialized = ois.readObject();
+      } catch (ClassNotFoundException e) {
+        // unlikely
+        throw new IOException(e);
+      } finally {
+        if (ois != null) {
+          try {
+            ois.close();
+          } catch (Exception ignore){}
+        }
+      }
+      return serialized;      
+    }
+
+    @Override
+    public void store(String resourceId, Object toStore) throws IOException {
+      if (!(toStore instanceof Serializable))
+        throw new IOException("Instance of "+
+          toStore.getClass().getName()+" is not Serializable!");
+      
+      String storedId = getStoredResourceId(resourceId);
+      ObjectOutputStream oos = null;
+      try {
+        oos = new ObjectOutputStream(storageIO.openOutputStream(storedId));
+        oos.writeObject(toStore);
+        oos.flush();
+      } finally {
+        if (oos != null) {
+          try {
+            oos.close();
+          } catch (Exception ignore){}
+        }
+      }    
+    }
+
+    @Override
+    public String getStoredResourceId(String resourceId) {
+      return resourceId.replace('/','_')+".bin";
+    }
+  }  
+  
+  private class CustomStorageFormatResource extends ManagedTestResource {
+    private CustomStorageFormatResource(String resourceId, SolrResourceLoader loader,
+        StorageIO storageIO) throws SolrException {
+      super(resourceId, loader, storageIO);
+    }
+    
+    @Override
+    protected ManagedResourceStorage createStorage(StorageIO storageIO, SolrResourceLoader loader) 
+        throws SolrException
+    {
+      return new SerializableStorage(storageIO, loader); 
+    }
+  }
+
+  /**
+   * Tests managed data storage to and loading from {@link ManagedResourceStorage.InMemoryStorageIO}.
+   */
+  @SuppressWarnings("unchecked")
+  @Test
+  public void testLoadingAndStoringOfManagedData() throws Exception {
+    String resourceId = "/config/test/foo";
+    String storedResourceId = "_config_test_foo.json";
+    
+    MockAnalysisComponent observer = new MockAnalysisComponent();
+    List<ManagedResourceObserver> observers = 
+        Arrays.asList((ManagedResourceObserver)observer);
+    
+    // put some data in the storage impl so that we can test 
+    // initialization of managed data from storage
+    String storedJson = "{'initArgs':{'someArg':'someVal', 'arg2':true, 'arg3':['one','two','three'],"
+                      + " 'arg4':18, 'arg5':0.9, 'arg6':{ 'uno':1, 'dos':2}},'"
+                      + ManagedResource.MANAGED_JSON_LIST_FIELD+"':['1','2','3']}";
+    ManagedResourceStorage.InMemoryStorageIO storageIO = 
+        new ManagedResourceStorage.InMemoryStorageIO();
+    storageIO.storage.put(storedResourceId, new BytesRef(json(storedJson)));
+    
+    ManagedTestResource res = 
+        new ManagedTestResource(resourceId, new SolrResourceLoader("./"), storageIO);
+    res.loadManagedDataAndNotify(observers);
+    
+    assertTrue("Observer was not notified by ManagedResource!", observer.wasNotified);
+
+    // now update the managed data (as if it came from the REST API)
+    List<String> updatedData = new ArrayList<>();
+    updatedData.add("1");
+    updatedData.add("2");
+    updatedData.add("3");
+    updatedData.add("4");    
+    res.storeManagedData(updatedData);
+    
+    StringReader stringReader = 
+        new StringReader(storageIO.storage.get(storedResourceId).utf8ToString());
+    Map<String,Object> jsonObject = 
+        (Map<String,Object>) ObjectBuilder.getVal(new JSONParser(stringReader)); 
+    List<String> jsonList = 
+        (List<String>)jsonObject.get(ManagedResource.MANAGED_JSON_LIST_FIELD);
+    
+    assertTrue("Managed data was not updated correctly!", jsonList.contains("4"));    
+  }
+  
+  /**
+   * The ManagedResource storage framework allows the end developer to use a different
+   * storage format other than JSON, as demonstrated by this test. 
+   */
+  @SuppressWarnings("rawtypes")
+  @Test
+  public void testCustomStorageFormat() throws Exception {
+    String resourceId = "/schema/test/foo";
+    String storedResourceId = "_schema_test_foo.bin";
+    
+    MockAnalysisComponent observer = new MockAnalysisComponent();
+    List<ManagedResourceObserver> observers = 
+        Arrays.asList((ManagedResourceObserver)observer);
+    
+    // put some data in the storage impl so that we can test 
+    // initialization of managed data from storage
+    Map<String,Object> storedData = new HashMap<>();
+    Map<String,Object> initArgs = new HashMap<>();
+
+    // {'initArgs':{'someArg':'someVal', 'arg2':true, 'arg3':['one','two','three'],
+    //              'arg4':18, 'arg5':0.9, 'arg6':{ 'uno':1, 'dos':2 }},'"
+    initArgs.put("someArg", "someVal");
+    initArgs.put("arg2", Boolean.TRUE);
+    List<String> arg3list = Arrays.asList("one", "two", "three");
+    initArgs.put("arg3", arg3list);
+    initArgs.put("arg4", 18L);
+    initArgs.put("arg5", 0.9);
+    Map<String,Long> arg6map = new HashMap<>();
+    arg6map.put("uno", 1L);
+    arg6map.put("dos", 2L);
+    initArgs.put("arg6", arg6map);
+
+    storedData.put("initArgs", initArgs);
+    List<String> managedList = new ArrayList<>();
+    managedList.add("1");
+    managedList.add("2");
+    managedList.add("3");
+    storedData.put(ManagedResource.MANAGED_JSON_LIST_FIELD, managedList);
+    ManagedResourceStorage.InMemoryStorageIO storageIO = 
+        new ManagedResourceStorage.InMemoryStorageIO();
+    storageIO.storage.put(storedResourceId, ser2bytes((Serializable)storedData));
+    
+    CustomStorageFormatResource res = 
+        new CustomStorageFormatResource(resourceId, new SolrResourceLoader("./"), storageIO);
+    res.loadManagedDataAndNotify(observers);
+    
+    assertTrue("Observer was not notified by ManagedResource!", observer.wasNotified);
+
+    // now store some data (as if it came from the REST API)
+    List<String> updatedData = new ArrayList<>();
+    updatedData.add("1");
+    updatedData.add("2");
+    updatedData.add("3");
+    updatedData.add("4");    
+    res.storeManagedData(updatedData);
+    
+    Object stored = res.storage.load(resourceId);
+    assertNotNull(stored);
+    assertTrue(stored instanceof Map);
+    Map storedMap = (Map)stored;
+    assertNotNull(storedMap.get("initArgs"));
+    List storedList = (List)storedMap.get(ManagedResource.MANAGED_JSON_LIST_FIELD);
+    assertTrue(storedList.contains("4"));
+  }
+
+
+  /**
+   * Converts the given Serializable object to bytes
+   */
+  private BytesRef ser2bytes(Serializable ser) throws Exception {
+    ByteArrayOutputStream out = new ByteArrayOutputStream();
+    ObjectOutputStream oos = null;
+    try {
+      oos = new ObjectOutputStream(out);
+      oos.writeObject(ser);
+      oos.flush();
+    } finally {
+      if (oos != null) {
+        try {
+          oos.close();
+        } catch (Exception ignore){}
+      }
+    } 
+    return new BytesRef(out.toByteArray());    
+  }
+}

Added: lucene/dev/trunk/solr/core/src/test/org/apache/solr/rest/TestManagedResourceStorage.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/solr/core/src/test/org/apache/solr/rest/TestManagedResourceStorage.java?rev=1576939&view=auto
==============================================================================
--- lucene/dev/trunk/solr/core/src/test/org/apache/solr/rest/TestManagedResourceStorage.java (added)
+++ lucene/dev/trunk/solr/core/src/test/org/apache/solr/rest/TestManagedResourceStorage.java Wed Mar 12 21:52:49 2014
@@ -0,0 +1,134 @@
+package org.apache.solr.rest;
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.lucene.util.LuceneTestCase.Slow;
+import org.apache.solr.cloud.AbstractZkTestCase;
+import org.apache.solr.common.cloud.SolrZkClient;
+import org.apache.solr.common.util.NamedList;
+import org.apache.solr.core.SolrResourceLoader;
+import org.apache.solr.rest.ManagedResourceStorage.StorageIO;
+import org.apache.solr.rest.ManagedResourceStorage.FileStorageIO;
+import org.apache.solr.rest.ManagedResourceStorage.ZooKeeperStorageIO;
+import org.apache.solr.rest.ManagedResourceStorage.JsonStorage;
+import org.junit.Test;
+
+/**
+ * Depends on ZK for testing ZooKeeper backed storage logic.
+ */
+@Slow
+public class TestManagedResourceStorage extends AbstractZkTestCase {
+
+  /**
+   * Runs persisted managed resource creation and update tests on Zookeeper storage.
+   */
+  @Test
+  public void testZkBasedJsonStorage() throws Exception {
+    
+    // test using ZooKeeper
+    assertTrue("Not using ZooKeeper", h.getCoreContainer().isZooKeeperAware());    
+    SolrZkClient zkClient = h.getCoreContainer().getZkController().getZkClient();    
+    SolrResourceLoader loader = new SolrResourceLoader("./");    
+    // Solr unit tests can only write to their working directory due to
+    // a custom Java Security Manager installed in the test environment
+    NamedList<String> initArgs = new NamedList<>();
+    try {
+      ZooKeeperStorageIO zkStorageIO = new ZooKeeperStorageIO(zkClient, "/test");
+      zkStorageIO.configure(loader, initArgs);
+      doStorageTests(loader, zkStorageIO);
+    } finally {
+      zkClient.close();
+    }
+  }
+
+
+  /**
+   * Runs persisted managed resource creation and update tests on JSON storage.
+   */
+  @Test
+  public void testFileBasedJsonStorage() throws Exception {    
+    SolrResourceLoader loader = new SolrResourceLoader("./");    
+    // Solr unit tests can only write to their working directory due to
+    // a custom Java Security Manager installed in the test environment
+    NamedList<String> initArgs = new NamedList<>();
+    initArgs.add(ManagedResourceStorage.STORAGE_DIR_INIT_ARG, "./managed");    
+    FileStorageIO fileStorageIO = new FileStorageIO();
+    fileStorageIO.configure(loader, initArgs);
+    doStorageTests(loader, fileStorageIO);
+  }
+
+  /**
+   * Called from tests for each storage type to run creation and update tests
+   * on a persisted managed resource.
+   */
+  @SuppressWarnings("unchecked")
+  private void doStorageTests(SolrResourceLoader loader, StorageIO storageIO) throws Exception {
+    String resourceId = "/test/foo";
+    
+    JsonStorage jsonStorage = new JsonStorage(storageIO, loader);
+    
+    Map<String,String> managedInitArgs = new HashMap<>();
+    managedInitArgs.put("ignoreCase","true");
+    managedInitArgs.put("dontIgnoreCase", "false");
+    
+    List<String> managedList = new ArrayList<>(); // we need a mutable List for this test
+    managedList.addAll(Arrays.asList("a","b","c","d","e"));
+    
+    Map<String,Object> toStore = new HashMap<>();
+    toStore.put(ManagedResource.INIT_ARGS_JSON_FIELD, managedInitArgs);
+    toStore.put(ManagedResource.MANAGED_JSON_LIST_FIELD, managedList);
+    
+    jsonStorage.store(resourceId, toStore);
+    
+    String storedResourceId = jsonStorage.getStoredResourceId(resourceId);
+    assertTrue(storedResourceId+" file not found!", storageIO.exists(storedResourceId));
+    
+    Object fromStorage = jsonStorage.load(resourceId);
+    assertNotNull(fromStorage);    
+    
+    Map<String,Object> storedMap = (Map<String,Object>)fromStorage;
+    Map<String,Object> storedArgs = (Map<String,Object>)storedMap.get(ManagedResource.INIT_ARGS_JSON_FIELD);
+    assertNotNull(storedArgs);
+    assertEquals("true", storedArgs.get("ignoreCase"));
+    List<String> storedList = (List<String>)storedMap.get(ManagedResource.MANAGED_JSON_LIST_FIELD);
+    assertNotNull(storedList);
+    assertTrue(storedList.size() == managedList.size());
+    assertTrue(storedList.contains("a"));    
+    
+    // now verify you can update existing data
+    managedInitArgs.put("anotherArg", "someValue");
+    managedList.add("f");
+    jsonStorage.store(resourceId, toStore);    
+    fromStorage = jsonStorage.load(resourceId);
+    assertNotNull(fromStorage);    
+    
+    storedMap = (Map<String,Object>)fromStorage;
+    storedArgs = (Map<String,Object>)storedMap.get(ManagedResource.INIT_ARGS_JSON_FIELD);
+    assertNotNull(storedArgs);
+    assertEquals("someValue", storedArgs.get("anotherArg"));
+    storedList = (List<String>)storedMap.get(ManagedResource.MANAGED_JSON_LIST_FIELD);
+    assertNotNull(storedList);
+    assertTrue(storedList.size() == managedList.size());
+    assertTrue(storedList.contains("e"));        
+  }
+}

Added: lucene/dev/trunk/solr/core/src/test/org/apache/solr/rest/TestRestManager.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/solr/core/src/test/org/apache/solr/rest/TestRestManager.java?rev=1576939&view=auto
==============================================================================
--- lucene/dev/trunk/solr/core/src/test/org/apache/solr/rest/TestRestManager.java (added)
+++ lucene/dev/trunk/solr/core/src/test/org/apache/solr/rest/TestRestManager.java Wed Mar 12 21:52:49 2014
@@ -0,0 +1,225 @@
+package org.apache.solr.rest;
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import java.util.Arrays;
+import java.util.Locale;
+import java.util.Set;
+
+import org.apache.solr.common.SolrException;
+import org.apache.solr.common.util.NamedList;
+import org.apache.solr.core.SolrResourceLoader;
+import org.apache.solr.rest.ManagedResourceStorage.StorageIO;
+import org.apache.solr.rest.schema.analysis.ManagedWordSetResource;
+import org.junit.Test;
+import org.noggit.JSONUtil;
+
+/**
+ * Tests {@link RestManager} functionality, including resource registration,
+ * and REST API requests and responses.
+ */
+public class TestRestManager extends SolrRestletTestBase {
+  
+  private class BogusManagedResource extends ManagedResource {
+
+    protected BogusManagedResource(String resourceId,
+        SolrResourceLoader loader, StorageIO storageIO) throws SolrException {
+      super(resourceId, loader, storageIO);
+    }
+
+    @Override
+    protected void onManagedDataLoadedFromStorage(NamedList<?> managedInitArgs, Object managedData)
+        throws SolrException {}
+
+    @Override
+    protected Object applyUpdatesToManagedData(Object updates) {
+      return null;
+    }
+
+    @Override
+    public void doDeleteChild(BaseSolrResource endpoint, String childId) {}
+
+    @Override
+    public void doGet(BaseSolrResource endpoint, String childId) {}
+    
+  }
+  
+  private class MockAnalysisComponent implements ManagedResourceObserver {
+
+    @Override
+    public void onManagedResourceInitialized(NamedList<?> args, ManagedResource res)
+        throws SolrException {
+      assertTrue(res instanceof ManagedWordSetResource);      
+    }
+  }
+  
+  /**
+   * Test RestManager initialization and handling of registered ManagedResources. 
+   */
+  @Test
+  public void testManagedResourceRegistrationAndInitialization() throws Exception {
+    // first, we need to register some ManagedResources, which is done with the registry
+    // provided by the SolrResourceLoader
+    SolrResourceLoader loader = new SolrResourceLoader("./");
+    
+    RestManager.Registry registry = loader.getManagedResourceRegistry();
+    assertNotNull("Expected a non-null RestManager.Registry from the SolrResourceLoader!", registry);
+    
+    String resourceId = "/config/test/foo";
+    registry.registerManagedResource(resourceId, 
+                                     ManagedWordSetResource.class, 
+                                     new MockAnalysisComponent());
+    
+    // verify the two different components can register the same ManagedResource in the registry
+    registry.registerManagedResource(resourceId, 
+                                     ManagedWordSetResource.class, 
+                                     new MockAnalysisComponent());
+    
+    // verify we can register another resource under a different resourceId
+    registry.registerManagedResource("/config/test/foo2", 
+                                     ManagedWordSetResource.class, 
+                                     new MockAnalysisComponent());
+
+    ignoreException("REST API path .* already registered to instances of ");
+
+    String failureMessage = "Should not be able to register a different"
+                          + " ManagedResource implementation for {}";
+
+    // verify that some other hooligan cannot register another type
+    // of ManagedResource implementation under the same resourceId
+    try {
+      registry.registerManagedResource(resourceId, 
+                                       BogusManagedResource.class, 
+                                       new MockAnalysisComponent());
+      fail(String.format(Locale.ROOT, failureMessage, resourceId));
+    } catch (SolrException solrExc) {
+      // expected output
+    }
+
+    resetExceptionIgnores();
+
+    ignoreException("is a reserved endpoint used by the Solr REST API!");
+
+    failureMessage = "Should not be able to register reserved endpoint {}";
+
+    // verify that already-spoken-for REST API endpoints can't be registered
+    Set<String> reservedEndpoints = registry.getReservedEndpoints();
+    assertTrue(reservedEndpoints.size() > 2);
+    assertTrue(reservedEndpoints.contains(RestManager.SCHEMA_BASE_PATH + RestManager.MANAGED_ENDPOINT));
+    assertTrue(reservedEndpoints.contains(RestManager.CONFIG_BASE_PATH + RestManager.MANAGED_ENDPOINT));
+    for (String endpoint : reservedEndpoints) {
+
+      try {
+        registry.registerManagedResource
+            (endpoint, BogusManagedResource.class, new MockAnalysisComponent());
+        fail(String.format(Locale.ROOT, failureMessage, endpoint));
+      } catch (SolrException solrExc) {
+        // expected output
+      }
+
+      // also try to register already-spoken-for REST API endpoints with a child segment
+      endpoint += "/kid";
+      try {
+        registry.registerManagedResource
+            (endpoint, BogusManagedResource.class, new MockAnalysisComponent());
+        fail(String.format(Locale.ROOT, failureMessage, endpoint));
+      } catch (SolrException solrExc) {
+        // expected output
+      }
+    }
+
+    resetExceptionIgnores();
+    
+    NamedList<String> initArgs = new NamedList<>();
+    RestManager restManager = new RestManager();
+    restManager.init(loader, initArgs, new ManagedResourceStorage.InMemoryStorageIO());
+    
+    ManagedResource res = restManager.getManagedResource(resourceId);
+    assertTrue(res instanceof ManagedWordSetResource);    
+    assertEquals(res.getResourceId(), resourceId);
+    
+    restManager.getManagedResource("/config/test/foo2"); // exception if it isn't registered
+  }
+
+  /**
+   * Tests {@link RestManager}'s responses to REST API requests on /config/managed
+   * and /schema/managed.  Also tests {@link ManagedWordSetResource} functionality
+   * through the REST API.
+   */
+  @Test
+  public void testRestManagerEndpoints() throws Exception {
+    // relies on these ManagedResources being activated in the schema-rest.xml used by this test
+    assertJQ("/schema/managed",
+             "/responseHeader/status==0");
+    /*
+     * TODO: can't assume these will be here unless schema-rest.xml includes these declarations
+     * 
+             "/managedResources/[0]/class=='org.apache.solr.rest.schema.analysis.ManagedWordSetResource'",
+             "/managedResources/[0]/resourceId=='/schema/analysis/stopwords/english'",
+             "/managedResources/[1]/class=='org.apache.solr.rest.schema.analysis.ManagedSynonymFilterFactory$SynonymManager'",
+             "/managedResources/[1]/resourceId=='/schema/analysis/synonyms/english'");
+    */
+    
+    // no pre-existing managed config components
+    assertJQ("/config/managed", "/managedResources==[]");
+        
+    // add a ManagedWordSetResource for managing protected words (for stemming)
+    String newEndpoint = "/schema/analysis/protwords/english";
+    
+    assertJPut(newEndpoint, json("{ 'class':'solr.ManagedWordSetResource' }"), "/responseHeader/status==0");
+        
+    assertJQ("/schema/managed"
+        ,"/managedResources/[0]/class=='org.apache.solr.rest.schema.analysis.ManagedWordSetResource'"
+        ,"/managedResources/[0]/resourceId=='/schema/analysis/protwords/english'");
+    
+    // query the resource we just created
+    assertJQ(newEndpoint, "/wordSet/managedList==[]");
+    
+    // add some words to this new word list manager
+    assertJPut(newEndpoint, JSONUtil.toJSON(Arrays.asList("this", "is", "a", "test")), "/responseHeader/status==0");
+
+    assertJQ(newEndpoint
+        ,"/wordSet/managedList==['a','is','test','this']"
+        ,"/wordSet/initArgs=={'ignoreCase':false}"); // make sure the default is serialized even if not specified
+
+    // Test for case-sensitivity - "Test" lookup should fail
+    assertJQ(newEndpoint + "/Test", "/responseHeader/status==404");
+
+    // Switch to case-insensitive
+    assertJPut(newEndpoint, json("{ 'initArgs':{ 'ignoreCase':'true' } }"), "/responseHeader/status==0");
+
+    // Test for case-insensitivity - "Test" lookup should succeed
+    assertJQ(newEndpoint + "/Test", "/responseHeader/status==0");
+
+    // Switch to case-sensitive - this request should fail: changing ignoreCase from true to false is not permitted
+    assertJPut(newEndpoint, json("{ 'initArgs':{ 'ignoreCase':false } }"), "/responseHeader/status==400");
+
+    // Test XML response format
+    assertQ(newEndpoint + "?wt=xml"
+        ,"/response/lst[@name='responseHeader']/int[@name='status']=0"
+        ,"/response/lst[@name='wordSet']/arr[@name='managedList']/str[1]='a'"
+        ,"/response/lst[@name='wordSet']/arr[@name='managedList']/str[2]='is'"
+        ,"/response/lst[@name='wordSet']/arr[@name='managedList']/str[3]='test'"
+        ,"/response/lst[@name='wordSet']/arr[@name='managedList']/str[4]='this'");
+
+    // delete the one we created above
+    assertJDelete(newEndpoint, "/responseHeader/status==0");
+
+    // make sure it's really gone
+    assertJQ("/config/managed", "/managedResources==[]");
+  }
+}
\ No newline at end of file

Modified: lucene/dev/trunk/solr/core/src/test/org/apache/solr/rest/schema/TestClassNameShortening.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/solr/core/src/test/org/apache/solr/rest/schema/TestClassNameShortening.java?rev=1576939&r1=1576938&r2=1576939&view=diff
==============================================================================
--- lucene/dev/trunk/solr/core/src/test/org/apache/solr/rest/schema/TestClassNameShortening.java (original)
+++ lucene/dev/trunk/solr/core/src/test/org/apache/solr/rest/schema/TestClassNameShortening.java Wed Mar 12 21:52:49 2014
@@ -31,8 +31,8 @@ public class TestClassNameShortening ext
   @BeforeClass
   public static void init() throws Exception {
     final SortedMap<ServletHolder,String> extraServlets = new TreeMap<>();
-    final ServletHolder solrRestApi = new ServletHolder("SolrRestApi", ServerServlet.class);
-    solrRestApi.setInitParameter("org.restlet.application", "org.apache.solr.rest.SolrRestApi");
+    final ServletHolder solrRestApi = new ServletHolder("SolrSchemaRestApi", ServerServlet.class);
+    solrRestApi.setInitParameter("org.restlet.application", "org.apache.solr.rest.SolrSchemaRestApi");
     extraServlets.put(solrRestApi, "/schema/*");  // '/schema/*' matches '/schema', '/schema/', and '/schema/whatever...'
 
     createJettyAndHarness(TEST_HOME(), "solrconfig-minimal.xml", "schema-class-name-shortening-on-serialization.xml", 

Modified: lucene/dev/trunk/solr/core/src/test/org/apache/solr/rest/schema/TestManagedSchemaFieldResource.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/solr/core/src/test/org/apache/solr/rest/schema/TestManagedSchemaFieldResource.java?rev=1576939&r1=1576938&r2=1576939&view=diff
==============================================================================
--- lucene/dev/trunk/solr/core/src/test/org/apache/solr/rest/schema/TestManagedSchemaFieldResource.java (original)
+++ lucene/dev/trunk/solr/core/src/test/org/apache/solr/rest/schema/TestManagedSchemaFieldResource.java Wed Mar 12 21:52:49 2014
@@ -47,8 +47,8 @@ public class TestManagedSchemaFieldResou
     FileUtils.copyDirectory(new File(TEST_HOME()), tmpSolrHome.getAbsoluteFile());
     
     final SortedMap<ServletHolder,String> extraServlets = new TreeMap<>();
-    final ServletHolder solrRestApi = new ServletHolder("SolrRestApi", ServerServlet.class);
-    solrRestApi.setInitParameter("org.restlet.application", "org.apache.solr.rest.SolrRestApi");
+    final ServletHolder solrRestApi = new ServletHolder("SolrSchemaRestApi", ServerServlet.class);
+    solrRestApi.setInitParameter("org.restlet.application", "org.apache.solr.rest.SolrSchemaRestApi");
     extraServlets.put(solrRestApi, "/schema/*");  // '/schema/*' matches '/schema', '/schema/', and '/schema/whatever...'
 
     System.setProperty("managed.schema.mutable", "true");

Modified: lucene/dev/trunk/solr/core/src/test/org/apache/solr/rest/schema/TestSerializedLuceneMatchVersion.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/solr/core/src/test/org/apache/solr/rest/schema/TestSerializedLuceneMatchVersion.java?rev=1576939&r1=1576938&r2=1576939&view=diff
==============================================================================
--- lucene/dev/trunk/solr/core/src/test/org/apache/solr/rest/schema/TestSerializedLuceneMatchVersion.java (original)
+++ lucene/dev/trunk/solr/core/src/test/org/apache/solr/rest/schema/TestSerializedLuceneMatchVersion.java Wed Mar 12 21:52:49 2014
@@ -31,8 +31,8 @@ public class TestSerializedLuceneMatchVe
   @BeforeClass
   public static void init() throws Exception {
     final SortedMap<ServletHolder,String> extraServlets = new TreeMap<>();
-    final ServletHolder solrRestApi = new ServletHolder("SolrRestApi", ServerServlet.class);
-    solrRestApi.setInitParameter("org.restlet.application", "org.apache.solr.rest.SolrRestApi");
+    final ServletHolder solrRestApi = new ServletHolder("SolrSchemaRestApi", ServerServlet.class);
+    solrRestApi.setInitParameter("org.restlet.application", "org.apache.solr.rest.SolrSchemaRestApi");
     extraServlets.put(solrRestApi, "/schema/*");  // '/schema/*' matches '/schema', '/schema/', and '/schema/whatever...'
 
     createJettyAndHarness(TEST_HOME(), "solrconfig-minimal.xml", "schema-rest-lucene-match-version.xml",

Modified: lucene/dev/trunk/solr/core/src/test/org/apache/solr/schema/TestCloudManagedSchemaAddField.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/solr/core/src/test/org/apache/solr/schema/TestCloudManagedSchemaAddField.java?rev=1576939&r1=1576938&r2=1576939&view=diff
==============================================================================
--- lucene/dev/trunk/solr/core/src/test/org/apache/solr/schema/TestCloudManagedSchemaAddField.java (original)
+++ lucene/dev/trunk/solr/core/src/test/org/apache/solr/schema/TestCloudManagedSchemaAddField.java Wed Mar 12 21:52:49 2014
@@ -58,8 +58,8 @@ public class TestCloudManagedSchemaAddFi
   @Override
   public SortedMap<ServletHolder,String> getExtraServlets() {
     final SortedMap<ServletHolder,String> extraServlets = new TreeMap<>();
-    final ServletHolder solrRestApi = new ServletHolder("SolrRestApi", ServerServlet.class);
-    solrRestApi.setInitParameter("org.restlet.application", "org.apache.solr.rest.SolrRestApi");
+    final ServletHolder solrRestApi = new ServletHolder("SolrSchemaRestApi", ServerServlet.class);
+    solrRestApi.setInitParameter("org.restlet.application", "org.apache.solr.rest.SolrSchemaRestApi");
     extraServlets.put(solrRestApi, "/schema/*");  // '/schema/*' matches '/schema', '/schema/', and '/schema/whatever...'
     return extraServlets;
   }

Modified: lucene/dev/trunk/solr/solrj/src/java/org/apache/solr/common/util/NamedList.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/solr/solrj/src/java/org/apache/solr/common/util/NamedList.java?rev=1576939&r1=1576938&r2=1576939&view=diff
==============================================================================
--- lucene/dev/trunk/solr/solrj/src/java/org/apache/solr/common/util/NamedList.java (original)
+++ lucene/dev/trunk/solr/solrj/src/java/org/apache/solr/common/util/NamedList.java Wed Mar 12 21:52:49 2014
@@ -81,6 +81,31 @@ public class NamedList<T> implements Clo
   }
 
   /**
+   * Creates a NamedList instance containing the "name,value" pairs contained in the
+   * Map.
+   *
+   * <p>
+   * Modifying the contents of the Map after calling this constructor may change
+   * the NamedList (in future versions of Solr), but this is not guaranteed and should
+   * not be relied upon.  To modify the NamedList, refer to {@link #add(String, Object)}
+   * or {@link #remove(String)}.
+   * </p>
+   *
+   * @param nameValueMap the name value pairs
+   */
+  public NamedList(Map<String,? extends T> nameValueMap) {
+    if (null == nameValueMap) {
+      nvPairs = new ArrayList<>();
+    } else {
+      nvPairs = new ArrayList<>(nameValueMap.size());
+      for (Map.Entry<String,? extends T> ent : nameValueMap.entrySet()) {
+        nvPairs.add(ent.getKey());
+        nvPairs.add(ent.getValue());
+      }
+    }
+  }
+
+  /**
    * Creates an instance backed by an explicitly specified list of
    * pairwise names/values.
    *
@@ -528,6 +553,30 @@ public class NamedList<T> implements Clo
    *           not a Boolean or a String.
    */
   public Boolean removeBooleanArg(final String name) {
+    Boolean bool = getBooleanArg(name);
+    if (null != bool) {
+      remove(name);
+    }
+    return bool;
+  }
+
+  /**
+   * Used for getting a boolean argument from a NamedList object.  If the name
+   * is not present, returns null.  If there is more than one value with that
+   * name, or if the value found is not a Boolean or a String, throws an
+   * exception.  If there is only one value present and it is a Boolean or a
+   * String, the value is returned as a Boolean.  The NamedList is not
+   * modified. See {@link #remove(String)}, {@link #removeAll(String)}
+   * and {@link #removeConfigArgs(String)} for additional ways of gathering
+   * configuration information from a NamedList.
+   *
+   * @param name The key to look up in the NamedList.
+   * @return The boolean value found.
+   * @throws SolrException
+   *           If multiple values are found for the name or the value found is
+   *           not a Boolean or a String.
+   */
+  public Boolean getBooleanArg(final String name) {
     Boolean bool;
     List<T> values = getAll(name);
     if (0 == values.size()) {
@@ -544,12 +593,11 @@ public class NamedList<T> implements Clo
       bool = Boolean.parseBoolean(o.toString());
     } else {
       throw new SolrException(SolrException.ErrorCode.SERVER_ERROR,
-          "'" + name + "' must have type 'bool' or 'str'; found " + o.getClass());
+          "'" + name + "' must have type Boolean or CharSequence; found " + o.getClass());
     }
-    remove(name);
     return bool;
   }
-  
+
   /**
    * Used for getting one or many arguments from NamedList objects that hold
    * configuration parameters. Finds all entries in the NamedList that match

Modified: lucene/dev/trunk/solr/test-framework/src/java/org/apache/solr/util/RestTestBase.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/solr/test-framework/src/java/org/apache/solr/util/RestTestBase.java?rev=1576939&r1=1576938&r2=1576939&view=diff
==============================================================================
--- lucene/dev/trunk/solr/test-framework/src/java/org/apache/solr/util/RestTestBase.java (original)
+++ lucene/dev/trunk/solr/test-framework/src/java/org/apache/solr/util/RestTestBase.java Wed Mar 12 21:52:49 2014
@@ -422,8 +422,73 @@ abstract public class RestTestBase exten
       }
     }
   }
+  
+  /**
+   * Deletes a resource and then matches some JSON test expressions against the 
+   * response using the default double delta tolerance.
+   * @see org.apache.solr.JSONTestUtil#DEFAULT_DELTA
+   * @see #assertJDelete(String,double,String...)
+   */
+  public static void assertJDelete(String request, String... tests) throws Exception {
+    assertJDelete(request, JSONTestUtil.DEFAULT_DELTA, tests);
+  }
+
+  /**
+   * Deletes a resource and then matches some JSON test expressions against the 
+   * response using the specified double delta tolerance.
+   */
+  public static void assertJDelete(String request, double delta, String... tests) throws Exception {
+    int queryStartPos = request.indexOf('?');
+    String query;
+    String path;
+    if (-1 == queryStartPos) {
+      query = "";
+      path = request;
+    } else {
+      query = request.substring(queryStartPos + 1);
+      path = request.substring(0, queryStartPos);
+    }
+    query = setParam(query, "wt", "json");
+    request = path + '?' + setParam(query, "indent", "on");
+
+    String response;
+    boolean failed = true;
+    try {
+      response = restTestHarness.delete(request);
+      failed = false;
+    } finally {
+      if (failed) {
+        log.error("REQUEST FAILED: " + request);
+      }
+    }
 
+    for (String test : tests) {
+      if (null == test || 0 == test.length()) continue;
+      String testJSON = json(test);
 
+      try {
+        failed = true;
+        String err = JSONTestUtil.match(response, testJSON, delta);
+        failed = false;
+        if (err != null) {
+          log.error("query failed JSON validation. error=" + err +
+              "\n expected =" + testJSON +
+              "\n response = " + response +
+              "\n request = " + request + "\n"
+          );
+          throw new RuntimeException(err);
+        }
+      } finally {
+        if (failed) {
+          log.error("JSON query validation threw an exception." +
+              "\n expected =" + testJSON +
+              "\n response = " + response +
+              "\n request = " + request + "\n"
+          );
+        }
+      }
+    }
+  }
 
   /**
    * Insures that the given param is included in the query with the given value.

Modified: lucene/dev/trunk/solr/test-framework/src/java/org/apache/solr/util/RestTestHarness.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/solr/test-framework/src/java/org/apache/solr/util/RestTestHarness.java?rev=1576939&r1=1576938&r2=1576939&view=diff
==============================================================================
--- lucene/dev/trunk/solr/test-framework/src/java/org/apache/solr/util/RestTestHarness.java (original)
+++ lucene/dev/trunk/solr/test-framework/src/java/org/apache/solr/util/RestTestHarness.java Wed Mar 12 21:52:49 2014
@@ -23,6 +23,7 @@ import javax.xml.xpath.XPathExpressionEx
 
 import org.apache.http.HttpEntity;
 import org.apache.http.client.HttpClient;
+import org.apache.http.client.methods.HttpDelete;
 import org.apache.http.client.methods.HttpGet;
 import org.apache.http.client.methods.HttpPost;
 import org.apache.http.client.methods.HttpPut;
@@ -110,6 +111,18 @@ public class RestTestHarness extends Bas
   }
 
   /**
+   * Processes a DELETE request using a URL path (with no context path) + optional query params,
+   * e.g. "/schema/analysis/protwords/english", and returns the response content.
+   *
+   * @param request the URL path and optional query params
+   * @return The response to the DELETE request
+   */
+  public String delete(String request) throws IOException {
+    HttpDelete httpDelete = new HttpDelete(getBaseURL() + request);
+    return getResponse(httpDelete);
+  }
+
+  /**
    * Processes a POST request using a URL path (with no context path) + optional query params,
    * e.g. "/schema/fields/newfield", PUTs the given content, and returns the response content.
    *
@@ -160,7 +173,10 @@ public class RestTestHarness extends Bas
       throw new RuntimeException(e);
     }
   }
-  
+
+  /**
+   * Executes the given request and returns the response.
+   */
   private String getResponse(HttpUriRequest request) throws IOException {
     HttpEntity entity = null;
     try {

Modified: lucene/dev/trunk/solr/webapp/web/WEB-INF/web.xml
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/solr/webapp/web/WEB-INF/web.xml?rev=1576939&r1=1576938&r2=1576939&view=diff
==============================================================================
--- lucene/dev/trunk/solr/webapp/web/WEB-INF/web.xml (original)
+++ lucene/dev/trunk/solr/webapp/web/WEB-INF/web.xml Wed Mar 12 21:52:49 2014
@@ -131,7 +131,16 @@
     <servlet-class>org.restlet.ext.servlet.ServerServlet</servlet-class>
     <init-param>
       <param-name>org.restlet.application</param-name>
-      <param-value>org.apache.solr.rest.SolrRestApi</param-value>
+      <param-value>org.apache.solr.rest.SolrSchemaRestApi</param-value>
+    </init-param>
+  </servlet>
+
+  <servlet>
+    <servlet-name>SolrConfigRestApi</servlet-name>
+    <servlet-class>org.restlet.ext.servlet.ServerServlet</servlet-class>
+    <init-param>
+      <param-name>org.restlet.application</param-name>
+      <param-value>org.apache.solr.rest.SolrConfigRestApi</param-value>
     </init-param>
   </servlet>
   
@@ -168,6 +177,11 @@
     <url-pattern>/schema/*</url-pattern>
   </servlet-mapping>
   
+  <servlet-mapping>
+    <servlet-name>SolrConfigRestApi</servlet-name>
+    <url-pattern>/config/*</url-pattern>
+  </servlet-mapping>
+  
   <mime-mapping>
     <extension>.xsl</extension>
     <!-- per http://www.w3.org/TR/2006/PR-xslt20-20061121/ -->