/* * 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.handler.admin; import org.apache.commons.io.IOUtils; import org.apache.solr.handler.RequestHandlerBase; import org.apache.solr.request.SolrQueryRequest; import org.apache.solr.client.solrj.impl.XMLResponseParser; import org.apache.solr.core.SolrInfoBean; import org.apache.solr.common.SolrException; import org.apache.solr.common.SolrException.ErrorCode; import org.apache.solr.common.util.ContentStream; import org.apache.solr.common.util.NamedList; import org.apache.solr.common.util.SimpleOrderedMap; import org.apache.solr.response.BinaryResponseWriter; import org.apache.solr.response.SolrQueryResponse; import java.io.StringReader; import java.text.NumberFormat; import java.util.Locale; import java.util.Set; import java.util.Map; import java.util.HashSet; /** * A request handler that provides info about all * registered SolrInfoMBeans. */ @SuppressWarnings("unchecked") public class SolrInfoMBeanHandler extends RequestHandlerBase { /** * Take an array of any type and generate a Set containing the toString. * Set is guarantee to never be null (but may be empty) */ private Set<String> arrayToSet(Object[] arr) { HashSet<String> r = new HashSet<>(); if (null == arr) return r; for (Object o : arr) { if (null != o) r.add(o.toString()); } return r; } @Override public void handleRequestBody(SolrQueryRequest req, SolrQueryResponse rsp) throws Exception { NamedList<NamedList<NamedList<Object>>> cats = getMBeanInfo(req); if(req.getParams().getBool("diff", false)) { ContentStream body = null; try { body = req.getContentStreams().iterator().next(); } catch(Exception ex) { throw new SolrException(ErrorCode.BAD_REQUEST, "missing content-stream for diff"); } String content = IOUtils.toString(body.getReader()); NamedList<NamedList<NamedList<Object>>> ref = fromXML(content); // Normalize the output SolrQueryResponse wrap = new SolrQueryResponse(); wrap.add("solr-mbeans", cats); cats = (NamedList<NamedList<NamedList<Object>>>) BinaryResponseWriter.getParsedResponse(req, wrap).get("solr-mbeans"); // Get rid of irrelevant things ref = normalize(ref); cats = normalize(cats); // Only the changes boolean showAll = req.getParams().getBool("all", false); rsp.add("solr-mbeans", getDiff(ref,cats, showAll)); } else { rsp.add("solr-mbeans", cats); } rsp.setHttpCaching(false); // never cache, no matter what init config looks like } static NamedList<NamedList<NamedList<Object>>> fromXML(String content) { int idx = content.indexOf("<response>"); if(idx<0) { throw new SolrException(ErrorCode.BAD_REQUEST, "Body does not appear to be an XML response"); } try { XMLResponseParser parser = new XMLResponseParser(); return (NamedList<NamedList<NamedList<Object>>>) parser.processResponse(new StringReader(content)).get("solr-mbeans"); } catch(Exception ex) { throw new SolrException(ErrorCode.BAD_REQUEST, "Unable to read original XML", ex); } } protected NamedList<NamedList<NamedList<Object>>> getMBeanInfo(SolrQueryRequest req) { NamedList<NamedList<NamedList<Object>>> cats = new NamedList<>(); String[] requestedCats = req.getParams().getParams("cat"); if (null == requestedCats || 0 == requestedCats.length) { for (SolrInfoBean.Category cat : SolrInfoBean.Category.values()) { cats.add(cat.name(), new SimpleOrderedMap<NamedList<Object>>()); } } else { for (String catName : requestedCats) { cats.add(catName,new SimpleOrderedMap<NamedList<Object>>()); } } Set<String> requestedKeys = arrayToSet(req.getParams().getParams("key")); Map<String, SolrInfoBean> reg = req.getCore().getInfoRegistry(); for (Map.Entry<String, SolrInfoBean> entry : reg.entrySet()) { addMBean(req, cats, requestedKeys, entry.getKey(),entry.getValue()); } for (SolrInfoBean infoMBean : req.getCore().getCoreContainer().getResourceLoader().getInfoMBeans()) { addMBean(req,cats,requestedKeys,infoMBean.getName(),infoMBean); } return cats; } private void addMBean(SolrQueryRequest req, NamedList<NamedList<NamedList<Object>>> cats, Set<String> requestedKeys, String key, SolrInfoBean m) { if ( ! ( requestedKeys.isEmpty() || requestedKeys.contains(key) ) ) return; NamedList<NamedList<Object>> catInfo = cats.get(m.getCategory().name()); if ( null == catInfo ) return; NamedList<Object> mBeanInfo = new SimpleOrderedMap<>(); mBeanInfo.add("class", m.getName()); mBeanInfo.add("description", m.getDescription()); if (req.getParams().getFieldBool(key, "stats", false)) mBeanInfo.add("stats", m.getMetricsSnapshot()); catInfo.add(key, mBeanInfo); } protected NamedList<NamedList<NamedList<Object>>> getDiff( NamedList<NamedList<NamedList<Object>>> ref, NamedList<NamedList<NamedList<Object>>> now, boolean includeAll ) { NamedList<NamedList<NamedList<Object>>> changed = new NamedList<>(); // Cycle through each category for(int i=0;i<ref.size();i++) { String category = ref.getName(i); NamedList<NamedList<Object>> ref_cat = ref.get(category); NamedList<NamedList<Object>> now_cat = now.get(category); if(now_cat != null) { String ref_txt = ref_cat+""; String now_txt = now_cat+""; if(!ref_txt.equals(now_txt)) { // Something in the category changed // Now iterate the real beans NamedList<NamedList<Object>> cat = new SimpleOrderedMap<>(); for(int j=0;j<ref_cat.size();j++) { String name = ref_cat.getName(j); NamedList<Object> ref_bean = ref_cat.get(name); NamedList<Object> now_bean = now_cat.get(name); ref_txt = ref_bean+""; now_txt = now_bean+""; if(!ref_txt.equals(now_txt)) { // System.out.println( "----" ); // System.out.println( category +" : " + name ); // System.out.println( "REF: " + ref_txt ); // System.out.println( "NOW: " + now_txt ); // Calculate the differences NamedList diff = diffNamedList(ref_bean,now_bean); diff.add( "_changed_", true ); // flag the changed thing cat.add(name, diff); } else if(includeAll) { cat.add(name, ref_bean); } } if(cat.size()>0) { changed.add(category, cat); } } else if(includeAll) { changed.add(category, ref_cat); } } } return changed; } public NamedList diffNamedList(NamedList ref, NamedList now) { NamedList out = new SimpleOrderedMap(); for(int i=0; i<ref.size(); i++) { String name = ref.getName(i); Object r = ref.getVal(i); Object n = now.remove(name); if(n == null) { if(r!=null) { out.add("REMOVE "+name, r); } } else { out.add(name, diffObject(r,n)); } } for(int i=0; i<now.size(); i++) { String name = now.getName(i); Object v = now.getVal(i); if(v!=null) { out.add("ADD "+name, v); } } return out; } public Object diffObject(Object ref, Object now) { if (now instanceof Map) { now = new NamedList((Map)now); } if(ref instanceof NamedList) { return diffNamedList((NamedList)ref, (NamedList)now); } if(ref.equals(now)) { return ref; } StringBuilder str = new StringBuilder(); str.append("Was: ") .append(ref).append(", Now: ").append(now); if(ref instanceof Number) { NumberFormat nf = NumberFormat.getIntegerInstance(Locale.ROOT); if((ref instanceof Double) || (ref instanceof Float)) { nf = NumberFormat.getInstance(Locale.ROOT); } double dref = ((Number)ref).doubleValue(); double dnow = ((Number)now).doubleValue(); double diff = Double.NaN; if(Double.isNaN(dref)) { diff = dnow; } else if(Double.isNaN(dnow)) { diff = dref; } else { diff = dnow-dref; } str.append( ", Delta: ").append(nf.format(diff)); } return str.toString(); } /** * The 'avgRequestsPerSecond' field will make everything look like it changed */ public NamedList normalize(NamedList input) { input.remove("avgRequestsPerSecond"); for(int i=0; i<input.size(); i++) { Object v = input.getVal(i); if(v instanceof NamedList) { input.setVal(i, normalize((NamedList)v)); } } return input; } @Override public String getDescription() { return "Get Info (and statistics) for registered SolrInfoMBeans"; } @Override public Category getCategory() { return Category.ADMIN; } }