You are viewing a plain text version of this content. The canonical link for it is here.
Posted to solr-commits@lucene.apache.org by sh...@apache.org on 2008/07/29 20:13:58 UTC

svn commit: r680795 - in /lucene/solr/trunk: ./ example/solr/conf/ src/java/org/apache/solr/core/ src/java/org/apache/solr/search/ src/java/org/apache/solr/update/ src/test/org/apache/solr/core/ src/test/test-files/solr/conf/

Author: shalin
Date: Tue Jul 29 11:13:57 2008
New Revision: 680795

URL: http://svn.apache.org/viewvc?rev=680795&view=rev
Log:
SOLR-256 -- Support exposing Solr statistics through JMX

Added:
    lucene/solr/trunk/src/java/org/apache/solr/core/JmxMonitoredMap.java
    lucene/solr/trunk/src/test/org/apache/solr/core/TestJmxIntegration.java
    lucene/solr/trunk/src/test/org/apache/solr/core/TestJmxMonitoredMap.java
Modified:
    lucene/solr/trunk/CHANGES.txt
    lucene/solr/trunk/example/solr/conf/solrconfig.xml
    lucene/solr/trunk/src/java/org/apache/solr/core/SolrConfig.java
    lucene/solr/trunk/src/java/org/apache/solr/core/SolrCore.java
    lucene/solr/trunk/src/java/org/apache/solr/search/SolrIndexSearcher.java
    lucene/solr/trunk/src/java/org/apache/solr/update/UpdateHandler.java
    lucene/solr/trunk/src/test/test-files/solr/conf/solrconfig.xml

Modified: lucene/solr/trunk/CHANGES.txt
URL: http://svn.apache.org/viewvc/lucene/solr/trunk/CHANGES.txt?rev=680795&r1=680794&r2=680795&view=diff
==============================================================================
--- lucene/solr/trunk/CHANGES.txt (original)
+++ lucene/solr/trunk/CHANGES.txt Tue Jul 29 11:13:57 2008
@@ -325,6 +325,8 @@
 
 62. SOLR-611: Expose sort_values returned by QueryComponent in SolrJ's QueryResponse (Dan Rosher via shalin)
 
+63. SOLR-256: Support exposing Solr statistics through JMX (Sharad Agrawal, shalin)
+
     
 Changes in runtime behavior
  1. SOLR-559: use Lucene updateDocument, deleteDocuments methods.  This

Modified: lucene/solr/trunk/example/solr/conf/solrconfig.xml
URL: http://svn.apache.org/viewvc/lucene/solr/trunk/example/solr/conf/solrconfig.xml?rev=680795&r1=680794&r2=680795&view=diff
==============================================================================
--- lucene/solr/trunk/example/solr/conf/solrconfig.xml (original)
+++ lucene/solr/trunk/example/solr/conf/solrconfig.xml Tue Jul 29 11:13:57 2008
@@ -116,6 +116,20 @@
      -->
     <unlockOnStartup>false</unlockOnStartup>
   </mainIndex>
+  
+  <!--	Enables JMX if and only if an existing MBeanServer is found, use 
+  		this if you want to configure JMX through JVM parameters. Remove
+  		this to disable exposing Solr configuration and statistics to JMX.
+  		
+		If you want to connect to a particular server, specify the agentId
+		e.g. <jmx agentId="myAgent" />
+		
+		If you want to start a new MBeanServer, specify the serviceUrl
+		e.g <jmx serviceurl="service:jmx:rmi:///jndi/rmi://localhost:9999/solr" />
+		
+		For more details see http://wiki.apache.org/solr/SolrJmx
+  -->
+  <jmx />
 
   <!-- the default high-performance update handler -->
   <updateHandler class="solr.DirectUpdateHandler2">

Added: lucene/solr/trunk/src/java/org/apache/solr/core/JmxMonitoredMap.java
URL: http://svn.apache.org/viewvc/lucene/solr/trunk/src/java/org/apache/solr/core/JmxMonitoredMap.java?rev=680795&view=auto
==============================================================================
--- lucene/solr/trunk/src/java/org/apache/solr/core/JmxMonitoredMap.java (added)
+++ lucene/solr/trunk/src/java/org/apache/solr/core/JmxMonitoredMap.java Tue Jul 29 11:13:57 2008
@@ -0,0 +1,295 @@
+/**
+ * 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.
+ */
+package org.apache.solr.core;
+
+import org.apache.solr.common.SolrException;
+import org.apache.solr.common.util.NamedList;
+import org.apache.solr.core.SolrConfig.JmxConfiguration;
+
+import javax.management.*;
+import javax.management.remote.JMXConnectorServer;
+import javax.management.remote.JMXConnectorServerFactory;
+import javax.management.remote.JMXServiceURL;
+import java.lang.reflect.Method;
+import java.util.*;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * <p>
+ * Responsible for finding (or creating) a MBeanServer from given configuration
+ * and registering all SolrInfoMBean objects with JMX.
+ * </p>
+ * <p/>
+ * <p>
+ * Please see http://wiki.apache.org/solr/SolrJmx for instructions on usage and configuration
+ * </p>
+ *
+ * @version $Id$
+ * @see org.apache.solr.core.SolrConfig.JmxConfiguration
+ * @since solr 1.3
+ */
+public class JmxMonitoredMap<K, V> extends
+        ConcurrentHashMap<String, SolrInfoMBean> {
+  private static final Logger LOG = Logger.getLogger(JmxMonitoredMap.class
+          .getName());
+
+  private MBeanServer server = null;
+
+  private String jmxRootName;
+
+  public JmxMonitoredMap(String coreName, JmxConfiguration jmxConfig) {
+    jmxRootName = "solr" + (coreName == null ? "" : "/" + coreName);
+
+    if (jmxConfig.agentId != null && jmxConfig.serviceUrl != null) {
+      throw new SolrException(
+              SolrException.ErrorCode.SERVER_ERROR,
+              "Incorrect JMX Configuration in solrconfig.xml, both agentId and serviceUrl cannot be specified at the same time");
+    }
+
+    if (jmxConfig.serviceUrl == null) {
+      List<MBeanServer> servers = null;
+
+      if (jmxConfig.agentId == null) {
+        // Try to find the first MBeanServer
+        servers = MBeanServerFactory.findMBeanServer(null);
+      } else if (jmxConfig.agentId != null) {
+        // Try to find the first MBean server with the given agentId
+        servers = MBeanServerFactory.findMBeanServer(jmxConfig.agentId);
+        // throw Exception if no servers were found with the given agentId
+        if (servers == null || servers.isEmpty())
+          throw new SolrException(SolrException.ErrorCode.SERVER_ERROR,
+                  "No JMX Servers found with agentId: " + jmxConfig.agentId);
+      }
+
+      if (servers == null || servers.isEmpty()) {
+        LOG
+                .info("No JMX servers found, not exposing Solr information with JMX.");
+        return;
+      }
+      server = servers.get(0);
+      LOG.info("JMX monitoring is enabled. Adding Solr mbeans to JMX Server: "
+              + server);
+    } else {
+      try {
+        // Create a new MBeanServer with the given serviceUrl
+        server = MBeanServerFactory.newMBeanServer();
+        JMXConnectorServer connector = JMXConnectorServerFactory
+                .newJMXConnectorServer(new JMXServiceURL(jmxConfig.serviceUrl),
+                        null, server);
+        connector.start();
+        LOG.info("JMX monitoring is enabled at " + jmxConfig.serviceUrl);
+      } catch (Exception e) {
+        // Release the reference
+        server = null;
+        throw new RuntimeException("Could not start JMX monitoring ", e);
+      }
+    }
+  }
+
+  /**
+   * Clears the map and unregisters all SolrInfoMBeans in the map from
+   * MBeanServer
+   */
+  @Override
+  public void clear() {
+    if (server != null) {
+      for (Map.Entry<String, SolrInfoMBean> entry : entrySet()) {
+        unregister(entry.getKey(), entry.getValue());
+      }
+    }
+
+    super.clear();
+  }
+
+  /**
+   * Adds the SolrInfoMBean to the map and registers the given SolrInfoMBean
+   * instance with the MBeanServer defined for this core. If a SolrInfoMBean is
+   * already registered with the MBeanServer then it is unregistered and then
+   * re-registered.
+   *
+   * @param key      the JMX type name for this SolrInfoMBean
+   * @param infoBean the SolrInfoMBean instance to be registered
+   */
+  @Override
+  public SolrInfoMBean put(String key, SolrInfoMBean infoBean) {
+    if (server != null && infoBean != null) {
+      try {
+        ObjectName name = getObjectName(key, infoBean);
+        if (server.isRegistered(name))
+          server.unregisterMBean(name);
+        SolrDynamicMBean mbean = new SolrDynamicMBean(infoBean);
+        server.registerMBean(mbean, name);
+      } catch (Exception e) {
+        LOG.log(Level.WARNING, "Failed to register info bean: " + key, e);
+      }
+    }
+
+    return super.put(key, infoBean);
+  }
+
+  /**
+   * Removes the SolrInfoMBean object at the given key and unregisters it from
+   * MBeanServer
+   *
+   * @param key the JMX type name for this SolrInfoMBean
+   */
+  @Override
+  public SolrInfoMBean remove(Object key) {
+    SolrInfoMBean infoBean = get(key);
+    if (infoBean != null) {
+      try {
+        unregister((String) key, infoBean);
+      } catch (RuntimeException e) {
+        LOG.log(Level.WARNING, "Failed to unregister info bean: " + key, e);
+      }
+    }
+    return super.remove(key);
+  }
+
+  private void unregister(String key, SolrInfoMBean infoBean) {
+    if (server == null)
+      return;
+
+    try {
+      ObjectName name = getObjectName(key, infoBean);
+      if (server.isRegistered(name)) {
+        server.unregisterMBean(name);
+      } else {
+        LOG.info("Failed to unregister mbean: " + key
+                + " because it was not registered");
+      }
+    } catch (Exception e) {
+      throw new SolrException(SolrException.ErrorCode.SERVER_ERROR,
+              "Failed to unregister info bean: " + key, e);
+    }
+  }
+
+  private ObjectName getObjectName(String key, SolrInfoMBean infoBean)
+          throws MalformedObjectNameException {
+    Hashtable<String, String> map = new Hashtable<String, String>();
+    map.put("type", key);
+    map.put("id", infoBean.getName());
+    return ObjectName.getInstance(jmxRootName, map);
+  }
+
+  /**
+   * DynamicMBean is used to dynamically expose all SolrInfoMBean
+   * getStatistics() NameList keys as String getters.
+   */
+  static class SolrDynamicMBean implements DynamicMBean {
+    private SolrInfoMBean infoBean;
+
+    private HashSet<String> staticStats;
+
+    public SolrDynamicMBean(SolrInfoMBean managedResource) {
+      this.infoBean = managedResource;
+      staticStats = new HashSet<String>();
+
+      // For which getters are already available in SolrInfoMBean
+      staticStats.add("name");
+      staticStats.add("version");
+      staticStats.add("description");
+      staticStats.add("category");
+      staticStats.add("sourceId");
+      staticStats.add("source");
+    }
+
+    public MBeanInfo getMBeanInfo() {
+      ArrayList<MBeanAttributeInfo> attrInfoList = new ArrayList<MBeanAttributeInfo>();
+
+      for (String stat : staticStats) {
+        attrInfoList.add(new MBeanAttributeInfo(stat, String.class.getName(),
+                null, true, false, false));
+      }
+
+      try {
+        NamedList dynamicStats = infoBean.getStatistics();
+        if (dynamicStats != null) {
+          for (int i = 0; i < dynamicStats.size(); i++) {
+            String name = dynamicStats.getName(i);
+            if (!staticStats.contains(name))
+              attrInfoList.add(new MBeanAttributeInfo(dynamicStats.getName(i),
+                      String.class.getName(), null, true, false, false));
+          }
+        }
+      } catch (Exception e) {
+        LOG.log(Level.WARNING, "Could not getStatistics on info bean "
+                + infoBean.getName(), e);
+      }
+
+      MBeanAttributeInfo[] attrInfoArr = attrInfoList
+              .toArray(new MBeanAttributeInfo[attrInfoList.size()]);
+      return new MBeanInfo(getClass().getName(), infoBean
+              .getDescription(), attrInfoArr, null, null, null);
+    }
+
+    public Object getAttribute(String attribute)
+            throws AttributeNotFoundException, MBeanException, ReflectionException {
+      Object val;
+      if (staticStats.contains(attribute) && attribute != null
+              & attribute.length() > 0) {
+        try {
+          String getter = "get" + attribute.substring(0, 1).toUpperCase()
+                  + attribute.substring(1);
+          Method meth = infoBean.getClass().getMethod(getter);
+          val = meth.invoke(infoBean);
+        } catch (Exception e) {
+          throw new AttributeNotFoundException(attribute);
+        }
+      } else {
+        NamedList list = infoBean.getStatistics();
+        val = list.get(attribute);
+      }
+
+      if (val != null)
+        return val.toString();
+      else
+        return val;
+
+    }
+
+    public AttributeList getAttributes(String[] attributes) {
+      AttributeList list = new AttributeList();
+      for (String attribute : attributes) {
+        try {
+          list.add(new Attribute(attribute, getAttribute(attribute)));
+        } catch (Exception e) {
+          LOG.warning("Could not get attibute " + attribute);
+        }
+      }
+
+      return list;
+    }
+
+    public void setAttribute(Attribute attribute)
+            throws AttributeNotFoundException, InvalidAttributeValueException,
+            MBeanException, ReflectionException {
+      throw new UnsupportedOperationException("Operation not Supported");
+    }
+
+    public AttributeList setAttributes(AttributeList attributes) {
+      throw new UnsupportedOperationException("Operation not Supported");
+    }
+
+    public Object invoke(String actionName, Object[] params, String[] signature)
+            throws MBeanException, ReflectionException {
+      throw new UnsupportedOperationException("Operation not Supported");
+    }
+  }
+}

Modified: lucene/solr/trunk/src/java/org/apache/solr/core/SolrConfig.java
URL: http://svn.apache.org/viewvc/lucene/solr/trunk/src/java/org/apache/solr/core/SolrConfig.java?rev=680795&r1=680794&r2=680795&view=diff
==============================================================================
--- lucene/solr/trunk/src/java/org/apache/solr/core/SolrConfig.java (original)
+++ lucene/solr/trunk/src/java/org/apache/solr/core/SolrConfig.java Tue Jul 29 11:13:57 2008
@@ -26,6 +26,7 @@
 import org.apache.solr.update.SolrIndexConfig;
 import org.apache.lucene.search.BooleanQuery;
 
+import org.w3c.dom.Node;
 import org.xml.sax.SAXException;
 
 import javax.xml.parsers.ParserConfigurationException;
@@ -133,6 +134,15 @@
     pingQueryParams = readPingQueryParams(this);
 
     httpCachingConfig = new HttpCachingConfig(this);
+    
+    Node jmx = (Node) getNode("jmx", false);
+    if (jmx != null) {
+      jmxConfig = new JmxConfiguration(true, get("jmx/@agentId", null), get(
+          "jmx/@serviceUrl", null));
+    } else {
+      jmxConfig = new JmxConfiguration(false, null, null);
+    }
+    
     Config.log.info("Loaded SolrConfig: " + name);
     
     // TODO -- at solr 2.0. this should go away
@@ -162,6 +172,9 @@
   public final SolrIndexConfig defaultIndexConfig;
   public final SolrIndexConfig mainIndexConfig;
   
+  //JMX configuration
+  public final JmxConfiguration jmxConfig;
+  
   private final HttpCachingConfig httpCachingConfig;
   public HttpCachingConfig getHttpCachingConfig() {
     return httpCachingConfig;
@@ -197,6 +210,19 @@
     return new LocalSolrQueryRequest(core, pingQueryParams);
   }
 
+  public static class JmxConfiguration {
+    public boolean enabled = false;
+
+    public String agentId;
+
+    public String serviceUrl;
+
+    public JmxConfiguration(boolean enabled, String agentId, String serviceUrl) {
+      this.enabled = enabled;
+      this.agentId = agentId;
+      this.serviceUrl = serviceUrl;
+    }
+  }
 
   public static class HttpCachingConfig {
 

Modified: lucene/solr/trunk/src/java/org/apache/solr/core/SolrCore.java
URL: http://svn.apache.org/viewvc/lucene/solr/trunk/src/java/org/apache/solr/core/SolrCore.java?rev=680795&r1=680794&r2=680795&view=diff
==============================================================================
--- lucene/solr/trunk/src/java/org/apache/solr/core/SolrCore.java (original)
+++ lucene/solr/trunk/src/java/org/apache/solr/core/SolrCore.java Tue Jul 29 11:13:57 2008
@@ -85,7 +85,7 @@
   private final SolrHighlighter highlighter;
   private final Map<String,SearchComponent> searchComponents;
   private final Map<String,UpdateRequestProcessorChain> updateProcessorChains;
-  private final Map<String,SolrInfoMBean> infoRegistry = new java.util.HashMap<String,SolrInfoMBean>();
+  private final Map<String, SolrInfoMBean> infoRegistry;
   
   public long getStartTime() { return startTime; }
 
@@ -182,15 +182,16 @@
   {
     return this.logid;
   }
-  
+
   /**
+   * @return the Info Registry map which contains SolrInfoMBean objects keyed by name
    * @since solr 1.3
    */
-  public Map<String,SolrInfoMBean> getInfoRegistry() {
+  public Map<String, SolrInfoMBean> getInfoRegistry() {
     return infoRegistry;
   }
-  
-  
+
+
   public List<SolrEventListener> parseListener(String path) {
     List<SolrEventListener> lst = new ArrayList<SolrEventListener>();
     log.info( logid+"Searching for listeners: " +path);
@@ -393,6 +394,14 @@
       if (schema==null) {
         schema = new IndexSchema(config, IndexSchema.DEFAULT_SCHEMA_FILE, null);
       }
+      
+      //Initialize JMX
+      if (config.jmxConfig.enabled) {
+        infoRegistry = new JmxMonitoredMap<String, SolrInfoMBean>(name, config.jmxConfig);
+      } else  {
+        log.info("JMX monitoring not detected for core: " + name);
+        infoRegistry = new LinkedHashMap<String, SolrInfoMBean>();
+      }
 
       this.schema = schema;
       this.dataDir = dataDir;
@@ -444,6 +453,7 @@
           solrConfig.get("updateHandler/@class", DirectUpdateHandler.class.getName())
         );
 
+        infoRegistry.put("updateHandler", updateHandler);
 
         // Finally tell anyone who wants to know
         loader.inform( loader );
@@ -552,6 +562,7 @@
    *  1. searcher
    *  2. updateHandler
    *  3. all CloseHooks will be notified
+   *  4. All MBeans will be unregistered from MBeanServer if JMX was enabled
    */
   public void close() {
     log.info(logid+" CLOSING SolrCore!");
@@ -574,7 +585,12 @@
        for( CloseHook hook : closeHooks ) {
          hook.close( this );
        }
-     }
+    }
+    try {
+      infoRegistry.clear();
+    } catch (Exception e) {
+      SolrException.log(log, e);
+    }
   }
 
   public boolean isClosed() {

Modified: lucene/solr/trunk/src/java/org/apache/solr/search/SolrIndexSearcher.java
URL: http://svn.apache.org/viewvc/lucene/solr/trunk/src/java/org/apache/solr/search/SolrIndexSearcher.java?rev=680795&r1=680794&r2=680795&view=diff
==============================================================================
--- lucene/solr/trunk/src/java/org/apache/solr/search/SolrIndexSearcher.java (original)
+++ lucene/solr/trunk/src/java/org/apache/solr/search/SolrIndexSearcher.java Tue Jul 29 11:13:57 2008
@@ -158,8 +158,6 @@
     // for DocSets
     HASHSET_INVERSE_LOAD_FACTOR = solrConfig.hashSetInverseLoadFactor;
     HASHDOCSET_MAXSIZE = solrConfig.hashDocSetMaxSize;
-    // register self
-    core.getInfoRegistry().put(this.name, this);
   }
 
 
@@ -171,6 +169,9 @@
   /** Register sub-objects such as caches
    */
   public void register() {
+    // register self
+    core.getInfoRegistry().put("searcher", this);
+    core.getInfoRegistry().put(name, this);
     for (SolrCache cache : cacheList) {
       cache.setState(SolrCache.State.LIVE);
       core.getInfoRegistry().put(cache.name(), cache);
@@ -184,9 +185,6 @@
    * In particular, the underlying reader and any cache's in use are closed.
    */
   public void close() throws IOException {
-    // unregister first, so no management actions are tried on a closing searcher.
-    core.getInfoRegistry().remove(name);
-
     if (cachingEnabled) {
       StringBuilder sb = new StringBuilder();
       sb.append("Closing ").append(name);
@@ -198,6 +196,7 @@
     } else {
       log.fine("Closing " + name);
     }
+    core.getInfoRegistry().remove(name);
     try {
       searcher.close();
     }

Modified: lucene/solr/trunk/src/java/org/apache/solr/update/UpdateHandler.java
URL: http://svn.apache.org/viewvc/lucene/solr/trunk/src/java/org/apache/solr/update/UpdateHandler.java?rev=680795&r1=680794&r2=680795&view=diff
==============================================================================
--- lucene/solr/trunk/src/java/org/apache/solr/update/UpdateHandler.java (original)
+++ lucene/solr/trunk/src/java/org/apache/solr/update/UpdateHandler.java Tue Jul 29 11:13:57 2008
@@ -116,7 +116,6 @@
     idFieldType = idField!=null ? idField.getType() : null;
     idTerm = idField!=null ? new Term(idField.getName(),"") : null;
     parseEventListeners();
-    core.getInfoRegistry().put("updateHandler", this);
   }
 
   protected SolrIndexWriter createMainIndexWriter(String name, boolean removeAllExisting) throws IOException {

Added: lucene/solr/trunk/src/test/org/apache/solr/core/TestJmxIntegration.java
URL: http://svn.apache.org/viewvc/lucene/solr/trunk/src/test/org/apache/solr/core/TestJmxIntegration.java?rev=680795&view=auto
==============================================================================
--- lucene/solr/trunk/src/test/org/apache/solr/core/TestJmxIntegration.java (added)
+++ lucene/solr/trunk/src/test/org/apache/solr/core/TestJmxIntegration.java Tue Jul 29 11:13:57 2008
@@ -0,0 +1,108 @@
+/**
+ * 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.
+ */
+package org.apache.solr.core;
+
+import org.apache.solr.core.JmxMonitoredMap.SolrDynamicMBean;
+import org.apache.solr.util.AbstractSolrTestCase;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import javax.management.*;
+import java.lang.management.ManagementFactory;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Test for JMX Integration
+ *
+ * @version $Id$
+ * @since solr 1.3
+ */
+public class TestJmxIntegration extends AbstractSolrTestCase {
+
+  @Override
+  public String getSchemaFile() {
+    return "schema.xml";
+  }
+
+  @Override
+  public String getSolrConfigFile() {
+    return "solrconfig.xml";
+  }
+
+  @Before
+  public void setUp() throws Exception {
+    // Make sure that at least one MBeanServer is available
+    MBeanServer mbeanServer = ManagementFactory.getPlatformMBeanServer();
+    super.setUp();
+  }
+
+  @After
+  public void tearDown() throws Exception {
+    super.tearDown();
+  }
+
+  @Test
+  public void testJmxRegistration() throws Exception {
+    List<MBeanServer> servers = MBeanServerFactory.findMBeanServer(null);
+    System.out.println("Servers: " + servers);
+    assertNotNull("MBeanServers were null", servers);
+    assertFalse("No MBeanServer was found", servers.isEmpty());
+
+    MBeanServer mbeanServer = servers.get(0);
+    assertTrue("No MBeans found in server", mbeanServer.getMBeanCount() > 0);
+
+    Set<ObjectInstance> objects = mbeanServer.queryMBeans(null, null);
+    assertFalse("No SolrInfoMBean objects found in mbean server", objects
+            .isEmpty());
+    for (ObjectInstance o : objects) {
+      MBeanInfo mbeanInfo = mbeanServer.getMBeanInfo(o.getObjectName());
+      if (mbeanInfo.getClassName().endsWith(SolrDynamicMBean.class.getName())) {
+        assertTrue("No Attributes found for mbean: " + mbeanInfo, mbeanInfo
+                .getAttributes().length > 0);
+      }
+    }
+  }
+
+  @Test
+  public void testJmxUpdate() throws Exception {
+    List<MBeanServer> servers = MBeanServerFactory.findMBeanServer(null);
+    MBeanServer mbeanServer = servers.get(0);
+
+    Set<ObjectInstance> objects = mbeanServer.queryMBeans(null, Query.match(
+            Query.attr("numDocs"), Query.value("[0-9]")));
+    assertFalse("No MBean for SolrIndexSearcher found in MBeanServer", objects
+            .isEmpty());
+
+    int oldNumDocs = Integer.valueOf((String) mbeanServer.getAttribute(objects
+            .iterator().next().getObjectName(), "numDocs"));
+
+    assertU(adoc("id", "1"));
+    assertU(commit());
+
+    objects = mbeanServer.queryMBeans(null, Query.match(Query.attr("numDocs"),
+            Query.value("[0-9]")));
+    assertFalse("No MBean for SolrIndexSearcher found in MBeanServer", objects
+            .isEmpty());
+
+    int numDocs = Integer.valueOf((String) mbeanServer.getAttribute(objects
+            .iterator().next().getObjectName(), "numDocs"));
+    assertTrue("New numDocs is same as old numDocs as reported by JMX",
+            numDocs > oldNumDocs);
+  }
+}

Added: lucene/solr/trunk/src/test/org/apache/solr/core/TestJmxMonitoredMap.java
URL: http://svn.apache.org/viewvc/lucene/solr/trunk/src/test/org/apache/solr/core/TestJmxMonitoredMap.java?rev=680795&view=auto
==============================================================================
--- lucene/solr/trunk/src/test/org/apache/solr/core/TestJmxMonitoredMap.java (added)
+++ lucene/solr/trunk/src/test/org/apache/solr/core/TestJmxMonitoredMap.java Tue Jul 29 11:13:57 2008
@@ -0,0 +1,155 @@
+/**
+ * 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.
+ */
+package org.apache.solr.core;
+
+import org.apache.solr.common.util.NamedList;
+import org.apache.solr.core.SolrConfig.JmxConfiguration;
+import org.junit.After;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import org.junit.Before;
+import org.junit.Test;
+
+import javax.management.MBeanServerConnection;
+import javax.management.ObjectInstance;
+import javax.management.Query;
+import javax.management.remote.JMXConnector;
+import javax.management.remote.JMXConnectorFactory;
+import javax.management.remote.JMXServiceURL;
+import java.net.ServerSocket;
+import java.net.URL;
+import java.rmi.RemoteException;
+import java.rmi.registry.LocateRegistry;
+import java.util.Set;
+
+/**
+ * Test for JmxMonitoredMap
+ *
+ * @version $Id$
+ * @since solr 1.3
+ */
+public class TestJmxMonitoredMap {
+
+  private int port = 0;
+
+  private JMXConnector connector;
+
+  private MBeanServerConnection mbeanServer;
+
+  private JmxMonitoredMap<String, SolrInfoMBean> monitoredMap;
+
+  @Before
+  public void setUp() throws Exception {
+    for (int i = 0; i < 5; i++) {
+      try {
+        ServerSocket server = new ServerSocket(0);
+        port = server.getLocalPort();
+        server.close();
+        System.out.println("Using port: " + port);
+        try {
+          LocateRegistry.createRegistry(port);
+        } catch (RemoteException e) {
+        }
+        String url = "service:jmx:rmi:///jndi/rmi://:" + port + "/solrjmx";
+        JmxConfiguration config = new JmxConfiguration(true, null, url);
+        monitoredMap = new JmxMonitoredMap<String, SolrInfoMBean>(null, config);
+        JMXServiceURL u = new JMXServiceURL(url);
+        connector = JMXConnectorFactory.connect(u);
+        mbeanServer = connector.getMBeanServerConnection();
+        break;
+      } catch (Exception e) {
+
+      }
+    }
+  }
+
+  @After
+  public void tearDown() throws Exception {
+    try {
+      connector.close();
+    } catch (Exception e) {
+    }
+  }
+
+  @Test
+  public void testPutRemoveClear() throws Exception {
+    MockInfoMBean mock = new MockInfoMBean();
+    monitoredMap.put("mock", mock);
+
+    Set<ObjectInstance> objects = mbeanServer.queryMBeans(null, Query.match(
+            Query.attr("name"), Query.value("mock")));
+    assertFalse("No MBean for mock object found in MBeanServer", objects
+            .isEmpty());
+
+    monitoredMap.remove("mock");
+    objects = mbeanServer.queryMBeans(null, Query.match(Query.attr("name"),
+            Query.value("mock")));
+    assertTrue("MBean for mock object found in MBeanServer even after removal",
+            objects.isEmpty());
+
+    monitoredMap.put("mock", mock);
+    monitoredMap.put("mock2", mock);
+    objects = mbeanServer.queryMBeans(null, Query.match(Query.attr("name"),
+            Query.value("mock")));
+    assertFalse("No MBean for mock object found in MBeanServer", objects
+            .isEmpty());
+
+    monitoredMap.clear();
+    objects = mbeanServer.queryMBeans(null, Query.match(Query.attr("name"),
+            Query.value("mock")));
+    assertTrue(
+            "MBean for mock object found in MBeanServer even after clear has been called",
+            objects.isEmpty());
+  }
+
+  private class MockInfoMBean implements SolrInfoMBean {
+    public String getName() {
+      return "mock";
+    }
+
+    public Category getCategory() {
+      return Category.OTHER;
+    }
+
+    public String getDescription() {
+      return "mock";
+    }
+
+    public URL[] getDocs() {
+      // TODO Auto-generated method stub
+      return null;
+    }
+
+    public String getVersion() {
+      return "mock";
+    }
+
+    public String getSource() {
+      return "mock";
+    }
+
+    @SuppressWarnings("unchecked")
+    public NamedList getStatistics() {
+      return null;
+    }
+
+    public String getSourceId() {
+      return "mock";
+    }
+  }
+
+}

Modified: lucene/solr/trunk/src/test/test-files/solr/conf/solrconfig.xml
URL: http://svn.apache.org/viewvc/lucene/solr/trunk/src/test/test-files/solr/conf/solrconfig.xml?rev=680795&r1=680794&r2=680795&view=diff
==============================================================================
--- lucene/solr/trunk/src/test/test-files/solr/conf/solrconfig.xml (original)
+++ lucene/solr/trunk/src/test/test-files/solr/conf/solrconfig.xml Tue Jul 29 11:13:57 2008
@@ -24,6 +24,8 @@
 
 <config>
 
+  <jmx />
+
   <!-- Used to specify an alternate directory to hold all index data.
        It defaults to "index" if not present, and should probably
        not be changed if replication is in use. -->