package org.apache.solr.core; /* * 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 com.google.common.base.Charsets; import com.google.common.collect.ImmutableList; import org.apache.commons.io.IOUtils; import org.apache.commons.lang.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStreamWriter; import java.io.Writer; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * Writes any changes in core definitions to this instance's solr.xml */ public class SolrXMLCoresLocator implements CoresLocator { private static final Logger logger = LoggerFactory.getLogger(SolrXMLCoresLocator.class); private final String solrXmlTemplate; private final ConfigSolrXmlOld cfg; /** Core name to use if a core definition has no name */ public static final String DEFAULT_CORE_NAME = "collection1"; /** * Create a new SolrXMLCoresLocator * @param originalXML the original content of the solr.xml file * @param cfg the CoreContainer's config object */ public SolrXMLCoresLocator(String originalXML, ConfigSolrXmlOld cfg) { this.solrXmlTemplate = buildTemplate(originalXML); this.cfg = cfg; } private static Pattern POPULATED_CORES_TAG = Pattern.compile("^(.*<cores[^>]*>)(.*)(</cores>.*)$", Pattern.DOTALL); private static Pattern EMPTY_CORES_TAG = Pattern.compile("^(.*<cores[^>]*)/>(.*)$", Pattern.DOTALL); private static Pattern SHARD_HANDLER_TAG = Pattern.compile("(<shardHandlerFactory[^>]*>.*</shardHandlerFactory>)|(<shardHandlerFactory[^>]*/>)", Pattern.DOTALL); private static String CORES_PLACEHOLDER = "{{CORES_PLACEHOLDER}}"; // Package-private for testing // We replace the existing <cores></cores> contents with a template pattern // that we can later replace with the up-to-date core definitions. We also // need to extract the <shardHandlerFactory> section, as, annoyingly, it's // kept inside <cores/>. static String buildTemplate(String originalXML) { String shardHandlerConfig = ""; Matcher shfMatcher = SHARD_HANDLER_TAG.matcher(originalXML); if (shfMatcher.find()) { shardHandlerConfig = shfMatcher.group(0); } Matcher popMatcher = POPULATED_CORES_TAG.matcher(originalXML); if (popMatcher.matches()) { return new StringBuilder(popMatcher.group(1)) .append(CORES_PLACEHOLDER).append(shardHandlerConfig).append(popMatcher.group(3)).toString(); } // Self-closing <cores/> tag gets expanded to <cores></cores> Matcher emptyMatcher = EMPTY_CORES_TAG.matcher(originalXML); if (emptyMatcher.matches()) return new StringBuilder(emptyMatcher.group(1)) .append(">").append(CORES_PLACEHOLDER).append("</cores>") .append(emptyMatcher.group(2)).toString(); // If there's no <cores> tag at all, add one at the end of the file return originalXML.replace("</solr>", "<cores>" + CORES_PLACEHOLDER + "</cores></solr>"); } // protected access for testing protected String buildSolrXML(List<CoreDescriptor> cds) { StringBuilder builder = new StringBuilder(); for (CoreDescriptor cd : cds) { builder.append(buildCoreTag(cd)); } return solrXmlTemplate.replace(CORES_PLACEHOLDER, builder.toString()); } public static final String NEWLINE = System.getProperty("line.separator"); public static final String INDENT = " "; /** * Serialize a coredescriptor as a String containing an XML <core> tag. * @param cd the CoreDescriptor * @return an XML representation of the CoreDescriptor */ protected static String buildCoreTag(CoreDescriptor cd) { StringBuilder builder = new StringBuilder(NEWLINE).append(INDENT).append("<core"); for (Map.Entry<Object, Object> entry : cd.getPersistableStandardProperties().entrySet()) { builder.append(" ").append(entry.getKey()).append("=\"").append(entry.getValue()).append("\""); } Properties userProperties = cd.getPersistableUserProperties(); if (userProperties.isEmpty()) { return builder.append("/>").append(NEWLINE).toString(); } builder.append(">").append(NEWLINE); for (Map.Entry<Object, Object> entry : userProperties.entrySet()) { builder.append(INDENT).append(INDENT) .append("<property name=\"").append(entry.getKey()).append("\" value=\"") .append(entry.getValue()).append("\"/>").append(NEWLINE); } return builder.append("</core>").append(NEWLINE).toString(); } @Override public synchronized final void persist(CoreContainer cc, CoreDescriptor... coreDescriptors) { List<CoreDescriptor> cds = new ArrayList<>(cc.getCoreDescriptors().size() + coreDescriptors.length); cds.addAll(cc.getCoreDescriptors()); cds.addAll(Arrays.asList(coreDescriptors)); doPersist(buildSolrXML(cds)); } protected void doPersist(String xml) { File file = new File(cfg.config.getResourceLoader().getInstanceDir(), ConfigSolr.SOLR_XML_FILE); Writer writer = null; FileOutputStream fos = null; try { fos = new FileOutputStream(file); writer = new OutputStreamWriter(fos, Charsets.UTF_8); writer.write(xml); writer.close(); logger.info("Persisted core descriptions to {}", file.getAbsolutePath()); } catch (IOException e) { logger.error("Couldn't persist core descriptions to {} : {}", file.getAbsolutePath(), e); } finally { IOUtils.closeQuietly(writer); IOUtils.closeQuietly(fos); } } @Override public void create(CoreContainer cc, CoreDescriptor... coreDescriptors) { this.persist(cc, coreDescriptors); } @Override public void delete(CoreContainer cc, CoreDescriptor... coreDescriptors) { // coreDescriptors is kind of a useless param - we persist the current state off cc this.persist(cc); } @Override public void rename(CoreContainer cc, CoreDescriptor oldCD, CoreDescriptor newCD) { // we don't need those params, we just write out the current cc state this.persist(cc); } @Override public void swap(CoreContainer cc, CoreDescriptor cd1, CoreDescriptor cd2) { this.persist(cc); } @Override public List<CoreDescriptor> discover(CoreContainer cc) { ImmutableList.Builder<CoreDescriptor> listBuilder = ImmutableList.builder(); for (String coreName : cfg.getAllCoreNames()) { String name = cfg.getProperty(coreName, CoreDescriptor.CORE_NAME, DEFAULT_CORE_NAME); String instanceDir = cfg.getProperty(coreName, CoreDescriptor.CORE_INSTDIR, ""); Properties coreProperties = new Properties(); for (String propName : CoreDescriptor.standardPropNames) { String propValue = cfg.getProperty(coreName, propName, ""); if (StringUtils.isNotEmpty(propValue)) coreProperties.setProperty(propName, propValue); } coreProperties.putAll(cfg.getCoreProperties(coreName)); listBuilder.add(new CoreDescriptor(cc, name, instanceDir, coreProperties)); } return listBuilder.build(); } // for testing String getTemplate() { return solrXmlTemplate; } public static class NonPersistingLocator extends SolrXMLCoresLocator { public NonPersistingLocator(String originalXML, ConfigSolrXmlOld cfg) { super(originalXML, cfg); this.xml = originalXML; } @Override public void doPersist(String xml) { this.xml = xml; } public String xml; } }