/** * MirrorSolrConnector * Copyright 2013 by Michael Peter Christen * First released 18.02.2012 at http://yacy.net * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program in the file lgpl21.txt * If not, see <http://www.gnu.org/licenses/>. */ package net.yacy.cora.federate.solr.connector; import java.io.IOException; import java.util.Collection; import java.util.LinkedHashMap; import java.util.Map; import java.util.concurrent.BlockingQueue; import java.util.concurrent.atomic.AtomicLong; import net.yacy.cora.sorting.ReversibleScoreMap; import net.yacy.kelondro.data.word.Word; import org.apache.solr.client.solrj.response.QueryResponse; import org.apache.solr.common.SolrDocument; import org.apache.solr.common.SolrDocumentList; import org.apache.solr.common.SolrException; import org.apache.solr.common.SolrInputDocument; import org.apache.solr.common.params.CommonParams; import org.apache.solr.common.params.ModifiableSolrParams; public class MirrorSolrConnector extends AbstractSolrConnector implements SolrConnector { // the twin solrs private SolrConnector solr0, solr1; public MirrorSolrConnector() { this.solr0 = null; this.solr1 = null; } public MirrorSolrConnector(SolrConnector solr0, SolrConnector solr1) { this.solr0 = solr0; this.solr1 = solr1; } @Override public int hashCode() { return (this.solr0 == null ? 0 : this.solr0.hashCode()) + (this.solr1 == null ? 0 : this.solr1.hashCode()); } @Override public boolean equals(Object o) { return o instanceof MirrorSolrConnector && ((this.solr0 == null && ((MirrorSolrConnector) o).solr0 == null) || (((this.solr0 != null && ((MirrorSolrConnector) o).solr0 != null)) && this.solr0.equals(((MirrorSolrConnector) o).solr0))) && ((this.solr1 == null && ((MirrorSolrConnector) o).solr1 == null) || (((this.solr1 != null && ((MirrorSolrConnector) o).solr1 != null)) && this.solr1.equals(((MirrorSolrConnector) o).solr1))); } @Override public int bufferSize() { int b = 0; if (this.solr0 != null) b += this.solr0.bufferSize(); if (this.solr1 != null) b += this.solr1.bufferSize(); return b; } @Override public void clearCaches() { if (this.solr0 != null) this.solr0.clearCaches(); if (this.solr1 != null) this.solr1.clearCaches(); } public boolean isConnected0() { return this.solr0 != null; } public void connect0(SolrConnector c) { this.solr0 = c; } public SolrConnector getSolr0() { return this.solr0; } public void disconnect0() { if (this.solr0 == null) return; this.solr0.close(); this.solr0 = null; } public boolean isConnected1() { return this.solr1 != null; } public void connect1(SolrConnector c) { this.solr1 = c; } public SolrConnector getSolr1() { return this.solr1; } public void disconnect1() { if (this.solr1 == null) return; this.solr1.close(); this.solr1 = null; } @Override public void commit(final boolean softCommit) { if (this.solr0 != null) this.solr0.commit(softCommit); if (this.solr1 != null) this.solr1.commit(softCommit); } @Override public void optimize(int maxSegments) { if (this.solr0 != null) this.solr0.optimize(maxSegments); if (this.solr1 != null) this.solr1.optimize(maxSegments); } @Override public int getSegmentCount() { int s0 = this.solr0 == null ? 0 : this.solr0.getSegmentCount(); int s1 = this.solr1 == null ? 0 : this.solr1.getSegmentCount(); return Math.max(s0, s1); } @Override public boolean isClosed() { return (this.solr0 == null || this.solr0.isClosed()) && (this.solr1 == null || this.solr1.isClosed()); } @Override public synchronized void close() { if (this.solr0 != null) {this.solr0.close(); this.solr0 = null;} if (this.solr1 != null) {this.solr1.close(); this.solr1 = null;} } /** * delete everything in the solr index * @throws IOException */ @Override public void clear() throws IOException { if (this.solr0 != null) this.solr0.clear(); if (this.solr1 != null) this.solr1.clear(); } /** * delete an entry from solr * @param id the url hash of the entry * @throws IOException */ @Override public void deleteById(final String id) throws IOException { if (this.solr0 != null) this.solr0.deleteById(id); if (this.solr1 != null) this.solr1.deleteById(id); } /** * delete a set of entries from solr; entries are identified by their url hash * @param ids a list of url hashes * @throws IOException */ @Override public void deleteByIds(final Collection<String> ids) throws IOException { if (this.solr0 != null) this.solr0.deleteByIds(ids); if (this.solr1 != null) this.solr1.deleteByIds(ids); } @Override public void deleteByQuery(final String querystring) throws IOException { if (this.solr0 != null) this.solr0.deleteByQuery(querystring); if (this.solr1 != null) this.solr1.deleteByQuery(querystring); } @Override public SolrDocument getDocumentById(final String key, final String ... fields) throws IOException { assert key.length() == Word.commonHashLength : "wrong id: " + key; SolrDocument doc; if ((solr0 != null && ((doc = solr0.getDocumentById(key, fields)) != null)) || (solr1 != null && ((doc = solr1.getDocumentById(key, fields)) != null))) { return doc; } return null; } /** * add a Solr document * @param solrdoc * @throws IOException */ @Override public void add(final SolrInputDocument solrdoc) throws IOException { if (this.solr0 != null) this.solr0.add(solrdoc); if (this.solr1 != null) this.solr1.add(solrdoc); } @Override public void add(final Collection<SolrInputDocument> solrdocs) throws IOException, SolrException { if (this.solr0 != null) this.solr0.add(solrdocs); if (this.solr1 != null) this.solr1.add(solrdocs); } /** * get a query result from solr * to get all results set the query String to "*:*" * @param querystring * @throws IOException */ @Override public SolrDocumentList getDocumentListByQuery(final String querystring, final String sort, final int offset, final int count, final String ... fields) throws IOException { if (this.solr0 == null && this.solr1 == null) return new SolrDocumentList(); if (offset == 0 && count == 1 && querystring.startsWith("id:") && ((querystring.length() == 17 && querystring.charAt(3) == '"' && querystring.charAt(16) == '"') || querystring.length() == 15)) { final SolrDocumentList list = new SolrDocumentList(); SolrDocument doc = getDocumentById(querystring.charAt(3) == '"' ? querystring.substring(4, querystring.length() - 1) : querystring.substring(3), fields); list.add(doc); // no addToCache(list) here because that was already handlet in get(); return list; } if (this.solr0 != null && this.solr1 == null) { SolrDocumentList list = this.solr0.getDocumentListByQuery(querystring, sort, offset, count, fields); return list; } if (this.solr1 != null && this.solr0 == null) { SolrDocumentList list = this.solr1.getDocumentListByQuery(querystring, sort, offset, count, fields); return list; } // combine both lists SolrDocumentList l; l = this.solr0.getDocumentListByQuery(querystring, sort, offset, count, fields); if (l.size() >= count) return l; // at this point we need to know how many results are in solr0 // compute this with a very bad hack; replace with better method later int size0 = 0; { //bad hack - TODO: replace SolrDocumentList lHack = this.solr0.getDocumentListByQuery(querystring, sort, 0, Integer.MAX_VALUE, fields); size0 = lHack.size(); } // now use the size of the first query to do a second query final SolrDocumentList list = new SolrDocumentList(); for (final SolrDocument d: l) list.add(d); l = this.solr1.getDocumentListByQuery(querystring, sort, offset + l.size() - size0, count - l.size(), fields); for (final SolrDocument d: l) list.add(d); return list; } @Override public QueryResponse getResponseByParams(ModifiableSolrParams query) throws IOException, SolrException { Integer count0 = query.getInt(CommonParams.ROWS); int count = count0 == null ? 10 : count0.intValue(); Integer start0 = query.getInt(CommonParams.START); int start = start0 == null ? 0 : start0.intValue(); if (this.solr0 == null && this.solr1 == null) return new QueryResponse(); if (this.solr0 != null && this.solr1 == null) { QueryResponse list = this.solr0.getResponseByParams(query); return list; } if (this.solr1 != null && this.solr0 == null) { QueryResponse list = this.solr1.getResponseByParams(query); return list; } // combine both lists QueryResponse rsp = this.solr0.getResponseByParams(query); final SolrDocumentList l = rsp.getResults(); if (l.size() >= count) return rsp; // at this point we need to know how many results are in solr0 // compute this with a very bad hack; replace with better method later int size0 = 0; { //bad hack - TODO: replace query.set(CommonParams.START, 0); query.set(CommonParams.ROWS, Integer.MAX_VALUE); QueryResponse lHack = this.solr0.getResponseByParams(query); query.set(CommonParams.START, start); query.set(CommonParams.ROWS, count); size0 = lHack.getResults().size(); } // now use the size of the first query to do a second query query.set(CommonParams.START, start + l.size() - size0); query.set(CommonParams.ROWS, count - l.size()); QueryResponse rsp1 = this.solr1.getResponseByParams(query); query.set(CommonParams.START, start); query.set(CommonParams.ROWS, count); // TODO: combine both return rsp1; } @Override public SolrDocumentList getDocumentListByParams(ModifiableSolrParams query) throws IOException, SolrException { Integer count0 = query.getInt(CommonParams.ROWS); int count = count0 == null ? 10 : count0.intValue(); Integer start0 = query.getInt(CommonParams.START); int start = start0 == null ? 0 : start0.intValue(); if (this.solr0 == null && this.solr1 == null) return new SolrDocumentList(); if (this.solr0 != null && this.solr1 == null) { SolrDocumentList list = this.solr0.getDocumentListByParams(query); return list; } if (this.solr1 != null && this.solr0 == null) { SolrDocumentList list = this.solr1.getDocumentListByParams(query); return list; } // combine both lists final SolrDocumentList l = this.solr0.getDocumentListByParams(query); if (l.size() >= count) return l; // at this point we need to know how many results are in solr0 // compute this with a very bad hack; replace with better method later int size0 = 0; { //bad hack - TODO: replace query.set(CommonParams.START, 0); query.set(CommonParams.ROWS, Integer.MAX_VALUE); final SolrDocumentList lHack = this.solr0.getDocumentListByParams(query); query.set(CommonParams.START, start); query.set(CommonParams.ROWS, count); size0 = lHack.size(); } // now use the size of the first query to do a second query query.set(CommonParams.START, start + l.size() - size0); query.set(CommonParams.ROWS, count - l.size()); final SolrDocumentList l1 = this.solr1.getDocumentListByParams(query); query.set(CommonParams.START, start); query.set(CommonParams.ROWS, count); // TODO: combine both return l1; } @Override public long getCountByQuery(final String querystring) throws IOException { if (this.solr0 == null && this.solr1 == null) return 0; if (this.solr0 != null && this.solr1 == null) { return this.solr0.getCountByQuery(querystring); } if (this.solr1 != null && this.solr0 == null) { return this.solr1.getCountByQuery(querystring); } final AtomicLong count = new AtomicLong(0); Thread t0 = new Thread() { @Override public void run() { this.setName("MirrorSolrConnector.getCountByQuery/t0"); try { count.addAndGet(MirrorSolrConnector.this.solr0.getCountByQuery(querystring)); } catch (final IOException e) {} } }; t0.start(); Thread t1 = new Thread() { @Override public void run() { this.setName("MirrorSolrConnector.getCountByQuery/t1"); try { count.addAndGet(MirrorSolrConnector.this.solr1.getCountByQuery(querystring)); } catch (final IOException e) {} } }; t1.start(); try {t0.join();} catch (final InterruptedException e) {} try {t1.join();} catch (final InterruptedException e) {} return count.get(); } @Override public LinkedHashMap<String, ReversibleScoreMap<String>> getFacets(final String query, final int maxresults, final String ... fields) throws IOException { if (this.solr0 == null && this.solr1 == null) return new LinkedHashMap<String, ReversibleScoreMap<String>>(0); if (this.solr0 != null && this.solr1 == null) { return this.solr0.getFacets(query, maxresults, fields); } if (this.solr1 != null && this.solr0 == null) { return this.solr1.getFacets(query, maxresults, fields); } LinkedHashMap<String, ReversibleScoreMap<String>> facets0 = this.solr0.getFacets(query, maxresults, fields); LinkedHashMap<String, ReversibleScoreMap<String>> facets1 = this.solr1.getFacets(query, maxresults, fields); for (Map.Entry<String, ReversibleScoreMap<String>> facet0: facets0.entrySet()) { ReversibleScoreMap<String> facet1 = facets1.remove(facet0.getKey()); if (facet1 == null) continue; for (String key: facet1) facet0.getValue().inc(key, facet1.get(key)); } for (Map.Entry<String, ReversibleScoreMap<String>> facet1: facets1.entrySet()) { facets0.put(facet1.getKey(), facet1.getValue()); } return facets0; } @Override public long getSize() { long s = 0; if (this.solr0 != null) s += this.solr0.getSize(); if (this.solr1 != null) s += this.solr1.getSize(); return s; } @Override public LoadTimeURL getLoadTimeURL(String id) throws IOException { if (this.solr0 != null && this.solr1 == null) return this.solr0.getLoadTimeURL(id); if (this.solr0 == null && this.solr1 != null) return this.solr1.getLoadTimeURL(id); if (this.solr0 == null && this.solr1 == null) return null; LoadTimeURL md0 = this.solr0.getLoadTimeURL(id); LoadTimeURL md1 = this.solr1.getLoadTimeURL(id); if (md0 == null) return md1; if (md1 == null) return md0; long date = Math.max(md0.date, md1.date); assert md0.url.equals(md1.url); return new LoadTimeURL(md0.url, date); } @Override public BlockingQueue<String> concurrentIDsByQuery(final String querystring, final String sort, final int offset, final int maxcount, final long maxtime, final int buffersize, final int concurrency) { if (this.solr0 != null && this.solr1 == null) return this.solr0.concurrentIDsByQuery(querystring, sort, offset, maxcount, maxtime, buffersize, concurrency); if (this.solr0 == null && this.solr1 != null) return this.solr1.concurrentIDsByQuery(querystring, sort, offset, maxcount, maxtime, buffersize, concurrency); return super.concurrentIDsByQuery(querystring, sort, offset, maxcount, maxtime, buffersize, concurrency); } }