/**
* License Agreement for OpenSearchServer
* <p>
* Copyright (C) 2008-2016 Emmanuel Keller / Jaeksoft
* <p>
* http://www.open-search-server.com
* <p>
* This file is part of OpenSearchServer.
* <p>
* OpenSearchServer is free software: you can redistribute it and/or
* modify it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* <p>
* OpenSearchServer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* <p>
* You should have received a copy of the GNU General Public License
* along with OpenSearchServer.
* If not, see <http://www.gnu.org/licenses/>.
**/
package com.jaeksoft.searchlib;
import com.jaeksoft.searchlib.cluster.ClusterManager;
import com.jaeksoft.searchlib.config.ConfigFileRotation;
import com.jaeksoft.searchlib.config.ConfigFiles;
import com.jaeksoft.searchlib.index.IndexConfig;
import com.jaeksoft.searchlib.ocr.OcrManager;
import com.jaeksoft.searchlib.renderer.RendererResults;
import com.jaeksoft.searchlib.replication.ReplicationMerge;
import com.jaeksoft.searchlib.template.TemplateAbstract;
import com.jaeksoft.searchlib.template.TemplateList;
import com.jaeksoft.searchlib.user.Role;
import com.jaeksoft.searchlib.user.User;
import com.jaeksoft.searchlib.user.UserList;
import com.jaeksoft.searchlib.util.*;
import com.jaeksoft.searchlib.web.StartStopListener;
import com.jaeksoft.searchlib.web.controller.PushEvent;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.filefilter.DirectoryFileFilter;
import org.apache.lucene.search.BooleanQuery;
import org.xml.sax.SAXException;
import org.zkoss.zk.ui.WebApp;
import javax.naming.NamingException;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.xpath.XPathExpressionException;
import java.io.*;
import java.net.URI;
import java.util.*;
/**
* This class handles a list of indexes stored in a given directory.
*/
public class ClientCatalog {
private static transient volatile TreeMap<File, Client> CLIENTS = new TreeMap<File, Client>();
private static transient volatile TreeSet<File> OLD_CLIENTS = new TreeSet<File>();
private static final ReadWriteLock clientsLock = new ReadWriteLock();
private static final ReadWriteLock usersLock = new ReadWriteLock();
private static UserList userList = null;
private static final ConfigFiles configFiles = new ConfigFiles();
private static final RendererResults rendererResults = new RendererResults();
private static final ThreadGroup threadGroup = new ThreadGroup("Catalog");
/**
* This method should be called first before using any other function of
* OpenSearchServer. It will initialize all internal resources. The
* data_directory is the folder which will contain all the indexes (data and
* configuration).
*
* @param data_directory The directory which contain the indexes
*/
public static final void init(File data_directory) {
StartStopListener.start(data_directory);
}
/**
* Close OpenSearchServer. This method closes all indexes and stops any
* running task..
*/
public static final void close() {
StartStopListener.shutdown();
}
private static final boolean isOldClient(File indexDirectory) {
clientsLock.r.lock();
try {
return OLD_CLIENTS.contains(indexDirectory);
} finally {
clientsLock.r.unlock();
}
}
private static final Client getClient(File indexDirectory) throws SearchLibException {
clientsLock.r.lock();
try {
Client client = CLIENTS.get(indexDirectory);
if (client != null)
return client;
} finally {
clientsLock.r.unlock();
}
int i = 60;
while (isOldClient(indexDirectory) && i > 0) {
ThreadUtils.sleepMs(500);
i--;
}
if (i == 0)
throw new SearchLibException("Time out while getting " + indexDirectory);
clientsLock.w.lock();
try {
Client client = CLIENTS.get(indexDirectory);
if (client != null)
return client;
client = ClientFactory.INSTANCE.newClient(indexDirectory, true, false,
ClientFactory.INSTANCE.properties.getSilentBackupUrl());
CLIENTS.put(indexDirectory, client);
return client;
} finally {
clientsLock.w.unlock();
}
}
private static List<Client> findDepends(String indexName) {
clientsLock.r.lock();
try {
ArrayList<Client> list = new ArrayList<Client>();
for (Client client : CLIENTS.values()) {
IndexConfig indexConfig = client.getIndex().getIndexConfig();
if (indexConfig.isMulti())
if (indexConfig.isIndexMulti(indexName)) {
list.add(client);
}
}
return list;
} finally {
clientsLock.r.unlock();
}
}
public static final void openAll() {
try {
synchronized (ClientCatalog.class) {
for (ClientCatalogItem catalogItem : getClientCatalog(null)) {
Logging.info("OSS loads index " + catalogItem.getIndexName());
getClient(catalogItem.getIndexName());
}
}
} catch (SearchLibException e) {
Logging.error(e);
}
}
public static final void closeAll() {
synchronized (ClientCatalog.class) {
clientsLock.r.lock();
try {
for (Client client : CLIENTS.values()) {
if (client == null)
continue;
Logging.info("OSS unloads index " + client.getIndexName());
client.close();
}
} finally {
clientsLock.r.unlock();
}
rendererResults.release();
}
}
public static final long countAllDocuments() throws IOException, SearchLibException {
long count = 0;
clientsLock.r.lock();
try {
for (Client client : CLIENTS.values()) {
if (client.isTrueReplicate())
continue;
count += client.getStatistics().getNumDocs();
}
} finally {
clientsLock.r.unlock();
}
return count;
}
private static volatile long lastInstanceSize = 0;
public static final long calculateInstanceSize() throws SearchLibException {
if (StartStopListener.OPENSEARCHSERVER_DATA_FILE == null)
return 0;
lastInstanceSize = new LastModifiedAndSize(StartStopListener.OPENSEARCHSERVER_DATA_FILE, false).getSize();
return lastInstanceSize;
}
public static long getInstanceSize() throws SearchLibException {
if (lastInstanceSize != 0)
return lastInstanceSize;
return calculateInstanceSize();
}
public static final LastModifiedAndSize getLastModifiedAndSize(String indexName) throws SearchLibException {
File file = getIndexDirectory(indexName);
if (!file.exists())
return null;
return new LastModifiedAndSize(file, false);
}
public static final Client getClient(String indexName) throws SearchLibException {
return getClient(getIndexDirectory(indexName));
}
public static final void closeIndex(String indexName) throws SearchLibException {
Client client = null;
clientsLock.w.lock();
try {
File indexDirectory = getIndexDirectory(indexName);
client = CLIENTS.get(indexDirectory);
if (client == null)
return;
Logging.info("Closing client " + indexName);
client.close();
CLIENTS.remove(indexDirectory);
} finally {
clientsLock.w.unlock();
}
if (client != null)
PushEvent.eventClientSwitch.publish(client);
}
private static final File getIndexDirectory(String indexName) throws SearchLibException {
if (!isValidIndexName(indexName))
throw new SearchLibException("The name '" + indexName + "' is not allowed");
return new File(StartStopListener.OPENSEARCHSERVER_DATA_FILE, indexName);
}
public static final Set<ClientCatalogItem> getClientCatalog(User user) throws SearchLibException {
File[] files =
StartStopListener.OPENSEARCHSERVER_DATA_FILE.listFiles((FileFilter) DirectoryFileFilter.DIRECTORY);
Set<ClientCatalogItem> set = new TreeSet<ClientCatalogItem>();
if (files == null)
return null;
for (File file : files) {
if (!file.isDirectory())
continue;
String indexName = file.getName();
if (!isValidIndexName(indexName))
continue;
if (user == null || user.hasAnyRole(indexName, Role.GROUP_INDEX))
set.add(new ClientCatalogItem(indexName));
}
return set;
}
/**
* Tests if an index exists
*
* @param indexName The name of an index
* @return true if the index exist
* @throws SearchLibException inherited error
*/
public static final boolean exists(String indexName) throws SearchLibException {
return exists(null, indexName);
}
public static final boolean exists(User user, String indexName) throws SearchLibException {
if (user != null && !user.isAdmin())
throw new SearchLibException("Operation not permitted");
if (!isValidIndexName(indexName))
throw new SearchLibException("The name '" + indexName + "' is not allowed");
return getClientCatalog(null).contains(new ClientCatalogItem(indexName));
}
public static synchronized final OcrManager getOcrManager() throws SearchLibException {
return OcrManager.getInstance();
}
public static synchronized final ClusterManager getClusterManager() throws SearchLibException {
return ClusterManager.getInstance();
}
final private static boolean isValidIndexName(String name) {
if (name.startsWith("."))
return false;
if ("logs".equals(name))
return false;
return true;
}
/**
* Create a new index.
*
* @param indexName The name of the index.
* @param templateName The name of the template (EMPTY_INDEX, WEB_CRAWLER,
* FILE_CRAWLER)
* @param remoteURI the remote URI
* @throws IOException inherited error
* @throws SearchLibException inherited error
*/
public static void createIndex(String indexName, String templateName, URI remoteURI)
throws SearchLibException, IOException {
TemplateAbstract template = TemplateList.findTemplate(templateName);
if (template == null)
throw new SearchLibException("Template not found: " + templateName);
createIndex(null, indexName, template, remoteURI);
}
public static void createIndex(User user, String indexName, TemplateAbstract template, URI remoteURI)
throws SearchLibException, IOException {
if (user != null && !user.isAdmin())
throw new SearchLibException("Operation not permitted");
ClientFactory.INSTANCE.properties.checkMaxIndexNumber();
if (!isValidIndexName(indexName))
throw new SearchLibException("The name '" + indexName + "' is not allowed");
synchronized (ClientCatalog.class) {
File indexDir = new File(StartStopListener.OPENSEARCHSERVER_DATA_FILE, indexName);
if (indexDir.exists())
throw new SearchLibException("directory " + indexName + " already exists");
template.createIndex(indexDir, remoteURI);
}
}
/**
* Delete an index.
*
* @param indexName The name of the index
* @throws SearchLibException inherited error
* @throws NamingException inherited error
* @throws IOException inherited error
*/
public static void eraseIndex(String indexName) throws SearchLibException, NamingException, IOException {
eraseIndex(null, indexName);
}
public static void eraseIndex(User user, String indexName) throws SearchLibException, NamingException, IOException {
if (user != null && !user.isAdmin())
throw new SearchLibException("Operation not permitted");
File indexDir = getIndexDirectory(indexName);
Client client = null;
synchronized (ClientCatalog.class) {
clientsLock.r.lock();
try {
client = CLIENTS.get(indexDir);
} finally {
clientsLock.r.unlock();
}
if (client != null) {
client.close();
client.delete();
} else
FileUtils.deleteDirectory(indexDir);
if (client != null) {
clientsLock.w.lock();
try {
CLIENTS.remove(client.getDirectory());
} finally {
clientsLock.w.unlock();
}
PushEvent.eventClientSwitch.publish(client);
}
}
}
public static UserList getUserList() throws SearchLibException {
usersLock.r.lock();
try {
if (userList == null) {
File userFile = new File(StartStopListener.OPENSEARCHSERVER_DATA_FILE, "users.xml");
if (userFile.exists()) {
XPathParser xpp = new XPathParser(userFile);
userList = UserList.fromXml(xpp, xpp.getNode("/" + UserList.usersElement));
} else
userList = new UserList();
}
return userList;
} catch (ParserConfigurationException e) {
throw new SearchLibException(e);
} catch (SAXException e) {
throw new SearchLibException(e);
} catch (IOException e) {
throw new SearchLibException(e);
} catch (XPathExpressionException e) {
throw new SearchLibException(e);
} finally {
usersLock.r.unlock();
}
}
public static void flushPrivileges() {
usersLock.w.lock();
try {
userList = null;
} finally {
usersLock.w.unlock();
}
}
private static void saveUserListWithoutLock()
throws TransformerConfigurationException, SAXException, IOException, SearchLibException {
ConfigFileRotation cfr = configFiles.get(StartStopListener.OPENSEARCHSERVER_DATA_FILE, "users.xml");
try {
XmlWriter xmlWriter = new XmlWriter(cfr.getTempPrintWriter("UTF-8"), "UTF-8");
getUserList().writeXml(xmlWriter);
xmlWriter.endDocument();
cfr.rotate();
} finally {
cfr.abort();
}
}
public static void saveUserList() throws SearchLibException {
usersLock.w.lock();
try {
saveUserListWithoutLock();
} catch (IOException e) {
throw new SearchLibException(e);
} catch (TransformerConfigurationException e) {
throw new SearchLibException(e);
} catch (SAXException e) {
throw new SearchLibException(e);
} finally {
usersLock.w.unlock();
}
}
public static User authenticate(String login, String password) throws SearchLibException {
usersLock.r.lock();
try {
User user = getUserList().get(login);
if (user == null)
return null;
if (!user.authenticate(password))
return null;
return user;
} finally {
usersLock.r.unlock();
}
}
public static User authenticateKey(String login, String key) throws SearchLibException {
usersLock.r.lock();
try {
User user = getUserList().get(login);
if (user == null)
return null;
if (!user.authenticateKey(key))
return null;
return user;
} finally {
usersLock.r.unlock();
}
}
private static final File getTempReceiveDir(Client client) {
File clientDir = client.getDirectory();
return new File(clientDir.getParentFile(), '.' + clientDir.getName());
}
private static final File getTrashReceiveDir(Client client) {
File clientDir = client.getDirectory();
return new File(clientDir.getParentFile(), "._" + clientDir.getName());
}
public static final void receive_init(Client client) throws IOException, SearchLibException {
ClientFactory.INSTANCE.properties.checkMaxStorageLimit();
File rootDir = getTempReceiveDir(client);
FileUtils.deleteDirectory(rootDir);
rootDir.mkdir();
}
private static void lockClientDir(File clientDir) {
clientsLock.w.lock();
try {
CLIENTS.remove(clientDir);
OLD_CLIENTS.add(clientDir);
} finally {
clientsLock.w.unlock();
}
}
private static void lockClients(List<Client> clients) {
if (clients == null)
return;
for (Client client : clients)
lockClientDir(client.getDirectory());
}
private static void closeClients(List<Client> clients) {
if (clients == null)
return;
for (Client client : clients)
client.close();
}
private static void unlockClientDir(File clientDir, Client newClient) {
clientsLock.w.lock();
try {
if (newClient != null)
CLIENTS.put(clientDir, newClient);
OLD_CLIENTS.remove(clientDir);
} finally {
clientsLock.w.unlock();
}
}
private static void unlockClients(List<Client> clients) {
if (clients == null)
return;
for (Client client : clients)
unlockClientDir(client.getDirectory(), null);
}
public static void receive_switch(WebApp webapp, Client client)
throws SearchLibException, NamingException, IOException {
File trashDir = getTrashReceiveDir(client);
File clientDir = client.getDirectory();
if (trashDir.exists())
FileUtils.deleteDirectory(trashDir);
Client newClient = null;
List<Client> clientDepends = findDepends(client.getIndexName());
lockClientDir(clientDir);
try {
lockClients(clientDepends);
closeClients(clientDepends);
try {
client.trash(trashDir);
getTempReceiveDir(client).renameTo(clientDir);
File pathToMoveFile = new File(clientDir, PATH_TO_MOVE);
if (pathToMoveFile.exists()) {
for (String pathToMove : FileUtils.readLines(pathToMoveFile, "UTF-8")) {
File from = new File(trashDir, pathToMove);
File to = new File(clientDir, pathToMove);
FileUtils.moveFile(from, to);
}
if (!pathToMoveFile.delete())
throw new IOException("Unable to delete the file: " + pathToMoveFile.getAbsolutePath());
}
newClient = ClientFactory.INSTANCE.newClient(clientDir, true, true,
ClientFactory.INSTANCE.properties.getSilentBackupUrl());
newClient.writeReplCheck();
} finally {
unlockClients(clientDepends);
}
} finally {
unlockClientDir(clientDir, newClient);
}
PushEvent.eventClientSwitch.publish(client);
FileUtils.deleteDirectory(trashDir);
}
public static void receive_merge(WebApp webapp, Client client) throws SearchLibException, IOException {
File tempDir = getTempReceiveDir(client);
File clientDir = client.getDirectory();
Client newClient = null;
lockClientDir(clientDir);
try {
client.close();
new ReplicationMerge(tempDir, clientDir);
newClient = ClientFactory.INSTANCE.newClient(clientDir, true, true,
ClientFactory.INSTANCE.properties.getSilentBackupUrl());
newClient.writeReplCheck();
} finally {
unlockClientDir(clientDir, newClient);
}
PushEvent.eventClientSwitch.publish(client);
FileUtils.deleteDirectory(tempDir);
}
public static void receive_abort(Client client) throws IOException {
File tempDir = getTempReceiveDir(client);
File trashDir = getTrashReceiveDir(client);
synchronized (ClientCatalog.class) {
if (tempDir.exists())
FileUtils.deleteDirectory(tempDir);
if (trashDir.exists())
FileUtils.deleteDirectory(trashDir);
}
}
public static final void receive_dir(Client client, String filePath) throws IOException {
File rootDir = getTempReceiveDir(client);
File targetFile = new File(rootDir, filePath);
targetFile.mkdir();
}
private final static String PATH_TO_MOVE = ".path-to-move";
public static final boolean receive_file_exists(Client client, String filePath, long lastModified, long length)
throws IOException {
File existsFile = new File(client.getDirectory(), filePath);
if (!existsFile.exists())
return false;
if (existsFile.lastModified() != lastModified)
return false;
if (existsFile.length() != length)
return false;
File rootDir = getTempReceiveDir(client);
IOUtils.appendLines(new File(rootDir, PATH_TO_MOVE), filePath);
return true;
}
public static final void receive_file(Client client, String filePath, long lastModified, InputStream is)
throws IOException {
File rootDir = getTempReceiveDir(client);
File targetFile = new File(rootDir, filePath);
targetFile.createNewFile();
FileOutputStream fos = null;
try {
fos = new FileOutputStream(targetFile);
int len;
byte[] buffer = new byte[131072];
while ((len = is.read(buffer)) != -1)
fos.write(buffer, 0, len);
} catch (IOException e) {
throw e;
} finally {
IOUtils.close(fos);
}
targetFile.setLastModified(lastModified);
}
public static int getMaxClauseCount() {
return BooleanQuery.getMaxClauseCount();
}
public static void setMaxClauseCount(int value) {
BooleanQuery.setMaxClauseCount(value);
}
public final static RendererResults getRendererResults() {
return rendererResults;
}
public static ThreadGroup getThreadGroup() {
return threadGroup;
}
}