/*
* Copyright Robert Newson
*
* Licensed 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 com.github.rnewson.couchdb.lucene;
import com.github.rnewson.couchdb.lucene.couchdb.Couch;
import com.github.rnewson.couchdb.lucene.couchdb.Database;
import com.github.rnewson.couchdb.lucene.couchdb.DesignDocument;
import com.github.rnewson.couchdb.lucene.couchdb.View;
import com.github.rnewson.couchdb.lucene.util.ServletUtils;
import org.apache.commons.configuration.Configuration;
import org.apache.commons.configuration.ConfigurationException;
import org.apache.commons.configuration.HierarchicalINIConfiguration;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.http.client.HttpClient;
import org.apache.log4j.Logger;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.*;
public final class LuceneServlet extends HttpServlet {
private static final Logger LOG = Logger.getLogger(LuceneServlet.class);
private static final long serialVersionUID = 1L;
private final HttpClient client;
private final Map<Database, DatabaseIndexer> indexers = new HashMap<>();
private final HierarchicalINIConfiguration ini;
private final File root;
private final Map<Database, Thread> threads = new HashMap<>();
public LuceneServlet() throws ConfigurationException, IOException {
final Config config = new Config();
this.client = config.getClient();
this.root = config.getDir();
this.ini = config.getConfiguration();
}
public LuceneServlet(final HttpClient client, final File root,
final HierarchicalINIConfiguration ini) {
this.client = client;
this.root = root;
this.ini = ini;
}
private void cleanup(final HttpServletRequest req,
final HttpServletResponse resp) throws IOException, JSONException {
final Couch couch = getCouch(req);
final Set<String> dbKeep = new HashSet<>();
final JSONArray databases = couch.getAllDatabases();
for (int i = 0; i < databases.length(); i++) {
final Database db = couch.getDatabase(databases.getString(i));
final UUID uuid = db.getUuid();
if (uuid == null) {
continue;
}
dbKeep.add(uuid.toString());
final Set<String> viewKeep = new HashSet<>();
for (final DesignDocument ddoc : db.getAllDesignDocuments()) {
for (final View view : ddoc.getAllViews().values()) {
viewKeep.add(view.getDigest());
}
}
// Delete all indexes except the keepers.
final File[] dirs = DatabaseIndexer.uuidDir(root, db.getUuid()).listFiles();
if (dirs != null) {
for (final File dir : dirs) {
if (!viewKeep.contains(dir.getName())) {
LOG.info("Cleaning old index at " + dir);
FileUtils.deleteDirectory(dir);
}
}
}
}
// Delete all directories except the keepers.
for (final File dir : root.listFiles()) {
if (!dbKeep.contains(dir.getName())) {
LOG.info("Cleaning old index at " + dir);
FileUtils.deleteDirectory(dir);
}
}
resp.setStatus(202);
ServletUtils.sendJsonSuccess(req, resp);
}
private Couch getCouch(final HttpServletRequest req) throws IOException {
final String sectionName = new PathParts(req).getKey();
final Configuration section = ini.getSection(sectionName);
if (!section.containsKey("url")) {
throw new FileNotFoundException(sectionName + " is missing or has no url parameter.");
}
return new Couch(client, section.getString("url"));
}
private synchronized DatabaseIndexer getIndexer(final Database database)
throws IOException, JSONException {
DatabaseIndexer result = indexers.get(database);
Thread thread = threads.get(database);
if (result == null || thread == null || !thread.isAlive()) {
result = new DatabaseIndexer(client, root, database, ini);
thread = new Thread(result);
thread.start();
result.awaitInitialization();
if (result.isClosed()) {
return null;
} else {
indexers.put(database, result);
threads.put(database, thread);
}
}
return result;
}
private DatabaseIndexer getIndexer(final HttpServletRequest req)
throws IOException, JSONException {
final Couch couch = getCouch(req);
final Database database = couch.getDatabase(new PathParts(req)
.getDatabaseName());
return getIndexer(database);
}
private void handleWelcomeReq(final HttpServletRequest req,
final HttpServletResponse resp) throws ServletException,
IOException, JSONException {
final Package p = this.getClass().getPackage();
final JSONObject welcome = new JSONObject();
welcome.put("couchdb-lucene", "Welcome");
welcome.put("version", p.getImplementationVersion());
ServletUtils.sendJson(req, resp, welcome);
}
@Override
protected void doGet(final HttpServletRequest req,
final HttpServletResponse resp) throws ServletException,
IOException {
try {
doGetInternal(req, resp);
} catch (final JSONException e) {
resp.sendError(500);
}
}
private void doGetInternal(final HttpServletRequest req, final HttpServletResponse resp)
throws ServletException, IOException, JSONException {
switch (StringUtils.countMatches(req.getRequestURI(), "/")) {
case 1:
handleWelcomeReq(req, resp);
return;
case 5:
final DatabaseIndexer indexer = getIndexer(req);
if (indexer == null) {
ServletUtils.sendJsonError(req, resp, 500, "error_creating_index");
return;
}
if (req.getParameter("q") == null) {
indexer.info(req, resp);
} else {
indexer.search(req, resp);
}
return;
}
ServletUtils.sendJsonError(req, resp, 400, "bad_request");
}
@Override
protected void doPost(final HttpServletRequest req,
final HttpServletResponse resp) throws ServletException,
IOException {
try {
doPostInternal(req, resp);
} catch (final JSONException e) {
resp.sendError(500);
}
}
private void doPostInternal(final HttpServletRequest req, final HttpServletResponse resp)
throws IOException, JSONException {
switch (StringUtils.countMatches(req.getRequestURI(), "/")) {
case 3:
if (req.getPathInfo().endsWith("/_cleanup")) {
cleanup(req, resp);
return;
}
break;
case 5: {
final DatabaseIndexer indexer = getIndexer(req);
if (indexer == null) {
ServletUtils.sendJsonError(req, resp, 500, "error_creating_index");
return;
}
indexer.search(req, resp);
break;
}
case 6:
final DatabaseIndexer indexer = getIndexer(req);
indexer.admin(req, resp);
return;
}
ServletUtils.sendJsonError(req, resp, 400, "bad_request");
}
}