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 java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; import org.apache.lucene.store.Directory; import org.apache.lucene.store.NativeFSLockFactory; import org.apache.lucene.store.NoLockFactory; import org.apache.lucene.store.SimpleFSLockFactory; import org.apache.lucene.store.SingleInstanceLockFactory; import org.apache.solr.common.SolrException; import org.apache.solr.common.util.NamedList; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * A {@link DirectoryFactory} impl base class for caching Directory instances * per path. Most DirectoryFactory implementations will want to extend this * class and simply implement {@link DirectoryFactory#create(String)}. * */ public abstract class CachingDirectoryFactory extends DirectoryFactory { protected class CacheValue { public Directory directory; public int refCnt = 1; public String path; public boolean doneWithDir = false; public String toString() { return "CachedDir<<" + directory.toString() + ";refCount=" + refCnt + ";path=" + path + ";done=" + doneWithDir + ">>"; } } private static Logger log = LoggerFactory .getLogger(CachingDirectoryFactory.class); protected Map<String,CacheValue> byPathCache = new HashMap<String,CacheValue>(); protected Map<Directory,CacheValue> byDirectoryCache = new HashMap<Directory,CacheValue>(); protected Map<Directory,List<CloseListener>> closeListeners = new HashMap<Directory,List<CloseListener>>(); public interface CloseListener { public void onClose(); } @Override public void addCloseListener(Directory dir, CloseListener closeListener) { synchronized (this) { if (!byDirectoryCache.containsKey(dir)) { throw new IllegalArgumentException("Unknown directory: " + dir + " " + byDirectoryCache); } List<CloseListener> listeners = closeListeners.get(dir); if (listeners == null) { listeners = new ArrayList<CloseListener>(); closeListeners.put(dir, listeners); } listeners.add(closeListener); closeListeners.put(dir, listeners); } } @Override public void doneWithDirectory(Directory directory) throws IOException { synchronized (this) { CacheValue cacheValue = byDirectoryCache.get(directory); if (cacheValue == null) { throw new IllegalArgumentException("Unknown directory: " + directory + " " + byDirectoryCache); } cacheValue.doneWithDir = true; if (cacheValue.refCnt == 0) { cacheValue.refCnt++; // this will go back to 0 in close close(directory); } } } /* * (non-Javadoc) * * @see org.apache.solr.core.DirectoryFactory#close() */ @Override public void close() throws IOException { synchronized (this) { for (CacheValue val : byDirectoryCache.values()) { try { val.directory.close(); } catch (Throwable t) { SolrException.log(log, "Error closing directory", t); } } byDirectoryCache.clear(); byPathCache.clear(); } } private void close(Directory directory) throws IOException { synchronized (this) { CacheValue cacheValue = byDirectoryCache.get(directory); if (cacheValue == null) { throw new IllegalArgumentException("Unknown directory: " + directory + " " + byDirectoryCache); } log.debug("Closing: {}", cacheValue); cacheValue.refCnt--; if (cacheValue.refCnt == 0 && cacheValue.doneWithDir) { log.info("Closing directory:" + cacheValue.path); directory.close(); byDirectoryCache.remove(directory); byPathCache.remove(cacheValue.path); List<CloseListener> listeners = closeListeners.remove(directory); if (listeners != null) { for (CloseListener listener : listeners) { listener.onClose(); } closeListeners.remove(directory); } } } } protected abstract Directory create(String path) throws IOException; @Override public boolean exists(String path) { // back compat behavior File dirFile = new File(path); return dirFile.canRead() && dirFile.list().length > 0; } /* * (non-Javadoc) * * @see org.apache.solr.core.DirectoryFactory#get(java.lang.String, * java.lang.String) */ @Override public final Directory get(String path, String rawLockType) throws IOException { return get(path, rawLockType, false); } /* * (non-Javadoc) * * @see org.apache.solr.core.DirectoryFactory#get(java.lang.String, * java.lang.String, boolean) */ @Override public final Directory get(String path, String rawLockType, boolean forceNew) throws IOException { String fullPath = new File(path).getAbsolutePath(); synchronized (this) { CacheValue cacheValue = byPathCache.get(fullPath); Directory directory = null; if (cacheValue != null) { directory = cacheValue.directory; if (forceNew) { cacheValue.doneWithDir = true; if (cacheValue.refCnt == 0) { close(cacheValue.directory); } } } if (directory == null || forceNew) { directory = create(fullPath); CacheValue newCacheValue = new CacheValue(); newCacheValue.directory = directory; newCacheValue.path = fullPath; injectLockFactory(directory, path, rawLockType); byDirectoryCache.put(directory, newCacheValue); byPathCache.put(fullPath, newCacheValue); log.info("return new directory for " + fullPath + " forceNew:" + forceNew); } else { cacheValue.refCnt++; } return directory; } } /* * (non-Javadoc) * * @see * org.apache.solr.core.DirectoryFactory#incRef(org.apache.lucene.store.Directory * ) */ public void incRef(Directory directory) { synchronized (this) { CacheValue cacheValue = byDirectoryCache.get(directory); if (cacheValue == null) { throw new IllegalArgumentException("Unknown directory: " + directory); } cacheValue.refCnt++; } } public void init(NamedList args) {} /* * (non-Javadoc) * * @see * org.apache.solr.core.DirectoryFactory#release(org.apache.lucene.store.Directory * ) */ @Override public void release(Directory directory) throws IOException { if (directory == null) { throw new NullPointerException(); } close(directory); } private static Directory injectLockFactory(Directory dir, String lockPath, String rawLockType) throws IOException { if (null == rawLockType) { // we default to "simple" for backwards compatibility log.warn("No lockType configured for " + dir + " assuming 'simple'"); rawLockType = "simple"; } final String lockType = rawLockType.toLowerCase(Locale.ROOT).trim(); if ("simple".equals(lockType)) { // multiple SimpleFSLockFactory instances should be OK dir.setLockFactory(new SimpleFSLockFactory(lockPath)); } else if ("native".equals(lockType)) { dir.setLockFactory(new NativeFSLockFactory(lockPath)); } else if ("single".equals(lockType)) { if (!(dir.getLockFactory() instanceof SingleInstanceLockFactory)) dir .setLockFactory(new SingleInstanceLockFactory()); } else if ("none".equals(lockType)) { // Recipe for disaster log.error("CONFIGURATION WARNING: locks are disabled on " + dir); dir.setLockFactory(NoLockFactory.getNoLockFactory()); } else { throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "Unrecognized lockType: " + rawLockType); } return dir; } }