/* * 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.stanbol.commons.solr.managed.standalone; import static org.apache.stanbol.commons.solr.managed.util.ManagementUtils.getArchiveCoreName; import static org.apache.stanbol.commons.solr.managed.util.ManagementUtils.getMetadata; import static org.apache.stanbol.commons.solr.managed.util.ManagementUtils.substituteProperty; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.Map; import java.util.Properties; import java.util.ServiceLoader; import java.util.Set; import java.util.Map.Entry; import javax.xml.parsers.ParserConfigurationException; import org.apache.commons.compress.archivers.ArchiveException; import org.apache.commons.compress.archivers.ArchiveInputStream; import org.apache.commons.io.FileUtils; import org.apache.commons.io.FilenameUtils; import org.apache.solr.core.CoreContainer; import org.apache.solr.core.CoreDescriptor; import org.apache.solr.core.SolrCore; import org.apache.stanbol.commons.solr.managed.IndexMetadata; import org.apache.stanbol.commons.solr.managed.ManagedIndexState; import org.apache.stanbol.commons.solr.managed.ManagedSolrServer; import org.apache.stanbol.commons.solr.managed.util.ManagementUtils; import org.apache.stanbol.commons.solr.utils.ConfigUtils; import org.apache.stanbol.commons.stanboltools.datafileprovider.DataFileProvider; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.xml.sax.SAXException; /** * Basic implementation of the {@link ManagedSolrServer} interface that * can be used without an OSGI environment. * <p> * NOTE: {@link ServiceLoader} is used to search for DataFileProviders outside of * OSGI. An instance of {@link ClassPathDataFileProvider} is registered by * default that loads Index-Archives form "solr/core/". if you want to load * Data-Files form different locations you will need to provide your own * DataFileProvider. Extending {@link ClassPathDataFileProvider} might be the * simplest way to do this. * * @author Rupert Westenthaler * */ public final class StandaloneManagedSolrServer implements ManagedSolrServer { private static final String DEFAULT_SERVER_NAME = "default"; private final Logger log = LoggerFactory.getLogger(StandaloneManagedSolrServer.class); /** * Outside OSGI we need an instance of a data file provider that can load * Index Configuration via the classpath */ private static ServiceLoader<DataFileProvider> dataFileProviders = ServiceLoader.load(DataFileProvider.class); //private static DataFileProvider dataFileProvider = new ClassPathSolrIndexConfigProvider(null); /** * Initialising Solr Indexes with a lot of data may take some time. Especially if the data need to be * copied to the managed directory. Therefore it is important to wait for the initialisation to be * complete before opening an Solr Index on it. * <p> * To this set all cores that are currently initialised are added. As soon as an initialisation completed * this set is notified. */ private Set<String> initCores = new HashSet<String>(); /** * List with the managed Solr servers. The name is used as key */ public static final Map<String, StandaloneManagedSolrServer> managedServers = new HashMap<String,StandaloneManagedSolrServer>(); /** * The directory on the File system used to manage this {@link CoreContainer} */ private File managedSolrDir; /** * The managed Solr server */ private CoreContainer server; private String serverName; /** * Getter for the ManagedSolrServer for the parsed name. If * {@link #getDefaultServerName()} is parsed as name the default managed * Solr server is returned (and created if needed). For any other name * <code>null</code> is returned if no {@link ManagedSolrServer} for this * name is present. * @param name the name * @return The managed Solr server or <code>null</code> if not known. */ public static StandaloneManagedSolrServer getManagedServer(String name){ if(name == null){ name = DEFAULT_SERVER_NAME; } if(name.equals(DEFAULT_SERVER_NAME)){ return createManagedServer(name); } else { synchronized (managedServers) { return managedServers.get(name); } } } /** * Getter for the name of the default managed Solr server * @return the name of the default server */ public static String getDefaultServerName(){ return DEFAULT_SERVER_NAME; } /** * Shutdowns the default server */ public static void shutdownManagedServer() { shutdownManagedServer(DEFAULT_SERVER_NAME); } public static void shutdownManagedServer(String name){ synchronized (managedServers) { StandaloneManagedSolrServer server = managedServers.remove(name); if(server != null){ server.shutdown(); } } } /** * Getter for the default managed Solr server. This method is guaranteed to * NOT return <code>null</code>. * @return the default server */ public static StandaloneManagedSolrServer getManagedServer(){ return getManagedServer(null); } /** * Creates a new ManagedSolrServer with the specified name * @param name the name. MUST NOT be <code>null</code>, empty or contain * any chars that are used as {@link File} separators, extensions. In other * words {@link FilenameUtils#getBaseName(String)} MUST NOT change the * parsed name! * @return The created or already existing {@link ManagedSolrServer} for * this name */ public static StandaloneManagedSolrServer createManagedServer(String name){ synchronized (managedServers) { StandaloneManagedSolrServer server = managedServers.get(name); if(server == null){ server = new StandaloneManagedSolrServer(name); //use server.getServerName(), because NULL is replaced with default managedServers.put(server.getServerName(), server); } return server; } } /** * Private constructor used by the {@link #createManagedServer(String)} * method * @param name the name */ private StandaloneManagedSolrServer(String name) { if(name == null){ throw new IllegalArgumentException("The parsed Name MUST be NULL!"); } else if (name.isEmpty()){ throw new IllegalArgumentException("The parsed Name MUST be Empty!"); } else if(!FilenameUtils.getBaseName(name).equals(name)){ throw new IllegalArgumentException("The parsed Name '"+name+ "' contains path seperator, seperator and/or extension seperator chars!"); } this.serverName = name; //init the manange Solr Directory String configuredDataDir = System.getProperty(MANAGED_SOLR_DIR_PROPERTY, DEFAULT_SOLR_DATA_DIR); // property substitution configuredDataDir = FilenameUtils.separatorsToSystem( substituteProperty(configuredDataDir, null)); // determine the directory holding the SolrIndex managedSolrDir = new File(configuredDataDir,name).getAbsoluteFile(); try { managedSolrDir = managedSolrDir.getCanonicalFile(); } catch (IOException e) { throw new IllegalStateException("Unable to get the Canonical File for '"+configuredDataDir+"'!"); } initServer(); } private void initServer(){ File solrConf = new File(managedSolrDir, "solr.xml"); if (!solrConf.exists()) { try { managedSolrDir = ConfigUtils.copyDefaultConfig((Class<?>) null, managedSolrDir, false); } catch (IOException e) { throw new IllegalStateException(String.format( "Unable to copy default configuration for the manages Solr " + "Directory to the configured path '%s'!", managedSolrDir.getAbsoluteFile()), e); } } server = new CoreContainer(managedSolrDir.getAbsolutePath()); //File solrXml = new File(managedSolrDir,"solr.xml"); server.load(); } private void shutdown() { server.shutdown(); } @Override public IndexMetadata createSolrIndex(String name, ArchiveInputStream ais) throws IOException { if(!isManagedIndex(name)) { return updateIndex(name, ais); } else { throw new IllegalStateException("Can not create core with name '"+name+ "' because a Core with that name does already exist!"); } } @Override public IndexMetadata createSolrIndex(String coreName, String resourceName, Properties properties) throws IOException { if(!isManagedIndex(coreName)) { return updateIndex(coreName,resourceName,properties); } else { throw new IllegalStateException("Can not create core with name '"+coreName+ "' because a Core with that name does already exist!"); } } @Override public File getManagedDirectory() { return managedSolrDir; } @Override public String getServerName() { return serverName; } @Override public Collection<IndexMetadata> getIndexes(ManagedIndexState state) { if(state == ManagedIndexState.ACTIVE){ Collection<IndexMetadata> coreMetadata = new ArrayList<IndexMetadata>(); for(SolrCore core : server.getCores()){ coreMetadata.add(getMetadata(core,serverName)); } return coreMetadata; } else { return Collections.emptyList(); } } @Override public File getSolrIndexDirectory(String name) { if(name == null || name.isEmpty()){ throw new IllegalArgumentException("The parsed index name MUST NOT be NULL nor empty!"); } SolrCore core = server.getCore(name); if(core != null){ File instanceDir = new File(core.getCoreDescriptor().getInstanceDir()); core.close(); return instanceDir; } else { return null; } } @Override public IndexMetadata getIndexMetadata(String indexName) { IndexMetadata metadata; SolrCore core = server.getCore(indexName); if(core != null){ metadata = getMetadata(core,serverName); core.close(); } else { metadata = null; } return metadata; } @Override public ManagedIndexState getIndexState(String indexName) { //if the core is not active it does not exist return server.getCoreNames().contains(indexName) ? ManagedIndexState.ACTIVE : null; } @Override public boolean isManagedIndex(String name) { if(name == null || name.isEmpty()){ throw new IllegalArgumentException("The parsed index name MUST NOT be NULL nor empty!"); } return server.getCoreNames().contains(name); } @Override public void removeIndex(String name, boolean deleteFiles) { if(name == null || name.isEmpty()){ throw new IllegalArgumentException("The parsed index name MUST NOT be NULL nor empty!"); } SolrCore core = server.remove(name); core.close(); //decrease reference count if(deleteFiles){ String instanceDir = core.getCoreDescriptor().getInstanceDir(); while(!core.isClosed()){ //ensure the core is closed! core.close(); } try { FileUtils.deleteDirectory(new File(instanceDir)); } catch (IOException e) { log.error("Unable to delete instance directory '"+ instanceDir+"' of SolrCore '"+name+"'! Please delete this" + "directory manually."); } } } @Override public IndexMetadata updateIndex(String name, ArchiveInputStream ais) throws IOException { return updateIndex(name, ais, null); } @Override public IndexMetadata updateIndex(String name, ArchiveInputStream ais, String archiveCoreName) throws IOException { if (name == null || name.isEmpty()) { throw new IllegalArgumentException("The parsed index name MUST NOT be NULL nor empty!"); } IndexMetadata metadata = new IndexMetadata(); metadata.setIndexName(name); metadata.setServerName(DEFAULT_SERVER_NAME); metadata.setSynchronized(false); metadata.setState(ManagedIndexState.ACTIVE); if (archiveCoreName != null) { metadata.setArchive(archiveCoreName); } return updateCore(metadata, ais); } @Override public IndexMetadata updateIndex(String name, String parsedResourceName, Properties properties) throws IOException { if(name == null || name.isEmpty()){ throw new IllegalArgumentException("The parsed index name MUST NOT be NULL nor empty!"); } String resourceName; if(!ConfigUtils.isValidSolrIndexFileName(parsedResourceName)){ log.debug("add SolrIndexFileExtension to parsed indexArchive {}",parsedResourceName); resourceName = ConfigUtils.appandSolrIndexFileExtension(parsedResourceName, null); } else { resourceName = parsedResourceName; } Map<String,String> comments = new HashMap<String,String>(); if(properties != null){ for(Entry<Object,Object> prop : properties.entrySet()){ comments.put(prop.getKey().toString(),prop.getValue().toString()); } } InputStream is = null; for(Iterator<DataFileProvider> it = dataFileProviders.iterator();is == null && it.hasNext();){ DataFileProvider dfp = it.next(); try { is = dfp.getInputStream(null, resourceName, comments); }catch (IOException e) { //not found } } if(is != null || new File(managedSolrDir,parsedResourceName).isDirectory()){ ArchiveInputStream ais; try { ais = ManagementUtils.getArchiveInputStream(resourceName, is); } catch (ArchiveException e) { throw new IOException("Unable to open ArchiveInputStream for resource '"+ resourceName+"'!",e); } IndexMetadata metadata = new IndexMetadata(); if(properties != null){ metadata.putAll(properties); } metadata.setIndexName(name); metadata.setServerName(DEFAULT_SERVER_NAME); metadata.setSynchronized(false); metadata.setState(ManagedIndexState.ACTIVE); metadata.setArchive(resourceName); return updateCore(metadata, ais); } else { return null; } } public String getDefaultCore(){ return server.getDefaultCoreName(); } @Override public IndexMetadata activateIndex(String indexName) throws IOException, SAXException { //if the index is already active -> return it IndexMetadata metadata = getIndexMetadata(indexName); if(metadata != null){ return metadata; } else { //try to init an core for that directory located within the //managedDir return updateIndex(indexName, null); } } @Override public IndexMetadata deactivateIndex(String indexName) { IndexMetadata metadata; SolrCore core = server.remove(indexName); if(core != null){ metadata = getMetadata(core,serverName); core.close(); metadata.setState(ManagedIndexState.INACTIVE); } else { metadata = null; } return metadata; } /** * registers a {@link SolrCore} to the {@link #server} managed by this * instance. Will replace an already existing {@link SolrCore} with the * same name * @param coreName the name of the {@link SolrCore} to register * @param coreDir the directory for the Core. If <code>null</code> is parsed * {@link #managedSolrDir}/coreName is used as default. */ private void registerCore(String coreName, File coreDir) { if(coreName == null){ coreName = server.getDefaultCoreName(); } if(coreDir == null){ //use the coreName as default coreDir = new File(managedSolrDir,coreName); } if(!coreDir.isDirectory()){ throw new IllegalArgumentException("The Core Directory '"+ coreDir+" for the Core '"+coreName+"' does not exist or is not an directory"); } SolrCore core; CoreDescriptor coreDescriptor = new CoreDescriptor(server, coreName, coreDir.getAbsolutePath()); core = server.create(coreDescriptor); //this will also replace an existing core with the same name server.register(coreName, core, false); server.persist(); //store the new/updated SolrCore in the solr.xml } private IndexMetadata updateCore(IndexMetadata metadata, ArchiveInputStream ais){ String indexName = metadata.getIndexName(); File coreDir = new File(managedSolrDir, indexName); if(!initCores.contains(indexName)){ synchronized (initCores) { log.debug(" > start initializing SolrIndex {}" + indexName); initCores.add(indexName); } try { if(ais != null) { //copy the data //not the third parameter (coreName) is not the name of this //core, but the original name within the indexArchive String archiveCoreName = getArchiveCoreName(metadata); ConfigUtils.copyCore(ais, coreDir, archiveCoreName, false); //third register the new Core } //else the data are already in place registerCore(indexName, coreDir); metadata.setDirectory(coreDir.getAbsolutePath()); } catch (Exception e) { throw new IllegalStateException(String.format( "Unable to copy default configuration for Solr Index %s to the configured path %s", indexName, managedSolrDir.getAbsoluteFile()), e); } finally { // regardless what happened remove the index from the currently init // indexes and notify all other waiting for the initialisation synchronized (initCores) { // initialisation done initCores.remove(indexName); log.debug(" ... notify after trying to init SolrIndex {}",indexName); // notify that the initialisation completed or failed initCores.notifyAll(); } } } else { // the core is currently initialised ... wait until complete synchronized (initCores) { while (initCores.contains(indexName)) { log.info(" > wait for initialisation of SolrIndex {}", indexName); try { initCores.wait(); } catch (InterruptedException e) { // a core is initialised ... back to work } } metadata.setDirectory(coreDir.getAbsolutePath()); } } return metadata; } public String getCoreForDirectory(String coreNameOrPath) { if(coreNameOrPath.charAt(coreNameOrPath.length()-1) != File.separatorChar){ coreNameOrPath = coreNameOrPath+File.separatorChar; } for(SolrCore core : server.getCores()){ String instanceDir = core.getCoreDescriptor().getInstanceDir(); if(FilenameUtils.equalsNormalizedOnSystem( coreNameOrPath, instanceDir)){ return core.getName(); } } return null; } public CoreContainer getCoreContainer() { return server; } @Override public void swapIndexes(String indexName1, String indexName2) { if (!(isManagedIndex(indexName1) && isManagedIndex(indexName2))) { throw new IllegalArgumentException(String.format( "Both core names (%s,%s) must correspond to a managed index", indexName1, indexName2)); } server.swap(indexName1, indexName2); } }