Merge branch 'master' of https://git.alfresco.com/search_discovery/insightengine into feature/Search-1824

This commit is contained in:
mbhave
2019-11-14 13:07:25 +00:00
122 changed files with 12347 additions and 2152 deletions
+1 -1
View File
@@ -13,7 +13,7 @@
<properties>
<tas.rest.api.version>6.0.1.2</tas.rest.api.version>
<tas.cmis.api.version>6.0.0.4</tas.cmis.api.version>
<tas.utility.version>3.0.13</tas.utility.version>
<tas.utility.version>3.0.14</tas.utility.version>
<rm.version>3.2.0</rm.version>
<suiteXmlFile>src/test/resources/SearchSuite.xml</suiteXmlFile>
<test.exclude></test.exclude>
+14 -1
View File
@@ -29,7 +29,7 @@
<properties>
<java.version>11</java.version>
<solr.base.version>6.6.5</solr.base.version>
<solr.version>${solr.base.version}-patched</solr.version>
<solr.version>${solr.base.version}-patched.1</solr.version>
<!-- The location to download the solr zip file from. -->
<!-- <solr.zip>https://archive.apache.org/dist/lucene/solr/${solr.version}/solr-${solr.version}.zip</solr.zip> -->
<!-- Solr startup scripts do not work with any Java version higher than 9 so the scripts have been patched -->
@@ -54,6 +54,19 @@
<showDeprecation>true</showDeprecation>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-failsafe-plugin</artifactId>
<version>2.22.2</version>
<executions>
<execution>
<goals>
<goal>integration-test</goal>
<goal>verify</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
<dependencies>
+40 -1
View File
@@ -179,6 +179,45 @@
<directory>src/main/resources/solr/instance/templates/rerank/conf</directory>
<excludes>
<exclude>solrconfig.xml</exclude>
<exclude>solrcore.properties</exclude>
</excludes>
</resource>
</resources>
</configuration>
</execution>
<execution>
<id>copy-production-solr-configuration-for-master</id>
<phase>generate-test-resources</phase>
<goals>
<goal>copy-resources</goal>
</goals>
<configuration>
<outputDirectory>${project.build.testOutputDirectory}/test-files/master/conf</outputDirectory>
<resources>
<resource>
<directory>src/main/resources/solr/instance/templates/rerank/conf</directory>
<excludes>
<exclude>solrconfig.xml</exclude>
<exclude>solrcore.properties</exclude>
</excludes>
</resource>
</resources>
</configuration>
</execution>
<execution>
<id>copy-production-solr-configuration-for-slave</id>
<phase>generate-test-resources</phase>
<goals>
<goal>copy-resources</goal>
</goals>
<configuration>
<outputDirectory>${project.build.testOutputDirectory}/test-files/slave/conf</outputDirectory>
<resources>
<resource>
<directory>src/main/resources/solr/instance/templates/rerank/conf</directory>
<excludes>
<exclude>solrconfig.xml</exclude>
<exclude>solrcore.properties</exclude>
</excludes>
</resource>
</resources>
@@ -292,7 +331,7 @@
</goals>
<configuration>
<excludes>
<exclude>**/AnnotationWriter.*</exclude>
<exclude>**/libs/*</exclude>
</excludes>
<!-- Sets the path to the file which contains the execution data. -->
<dataFile>${project.build.directory}/coverage-reports/jacoco-ut.exec</dataFile>
@@ -1,484 +0,0 @@
/*
* Copyright (C) 2005 - 2016 Alfresco Software Limited
*
* This file is part of the Alfresco software.
* If the software was purchased under a paid Alfresco license, the terms of
* the paid license agreement will prevail. Otherwise, the software is
* provided under the following open source license terms:
*
* Alfresco 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 3 of the License, or
* (at your option) any later version.
*
* Alfresco 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 Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.solr;
import org.alfresco.httpclient.AuthenticationException;
import org.alfresco.service.cmr.repository.datatype.Duration;
import org.alfresco.solr.client.Node;
import org.alfresco.solr.tracker.*;
import org.alfresco.util.CachingDateFormat;
import org.apache.commons.codec.EncoderException;
import org.apache.solr.common.util.NamedList;
import org.apache.solr.common.util.SimpleOrderedMap;
import org.json.JSONException;
import java.io.IOException;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* Methods taken from AlfrescoCoreAdminHandler that deal with building reports
*/
public class HandlerReportBuilder {
/**
* Builds AclReport
* @param tracker
* @param aclid
* @return
* @throws IOException
* @throws JSONException
*/
public static NamedList<Object> buildAclReport(AclTracker tracker, Long aclid) throws IOException, JSONException
{
AclReport aclReport = tracker.checkAcl(aclid);
NamedList<Object> nr = new SimpleOrderedMap<Object>();
nr.add("Acl Id", aclReport.getAclId());
nr.add("Acl doc in index", aclReport.getIndexAclDoc());
if (aclReport.getIndexAclDoc() != null)
{
nr.add("Acl tx in Index", aclReport.getIndexAclTx());
}
return nr;
}
/**
* Builds TxReport
* @param trackerRegistry
* @param srv
* @param coreName
* @param tracker
* @param txid
* @return
* @throws AuthenticationException
* @throws IOException
* @throws JSONException
* @throws EncoderException
*/
public static NamedList<Object> buildTxReport(TrackerRegistry trackerRegistry, InformationServer srv, String coreName, MetadataTracker tracker, Long txid)
throws AuthenticationException, IOException, JSONException, EncoderException
{
NamedList<Object> nr = new SimpleOrderedMap<Object>();
nr.add("TXID", txid);
nr.add("transaction", buildTrackerReport(trackerRegistry, srv, coreName, txid, txid, 0l, 0l, null, null));
NamedList<Object> nodes = new SimpleOrderedMap<Object>();
// add node reports ....
List<Node> dbNodes = tracker.getFullNodesForDbTransaction(txid);
for (Node node : dbNodes)
{
nodes.add("DBID " + node.getId(), buildNodeReport(tracker, node));
}
nr.add("txDbNodeCount", dbNodes.size());
nr.add("nodes", nodes);
return nr;
}
/**
* Builds AclTxReport
* @param trackerRegistry
* @param srv
* @param coreName
* @param tracker
* @param acltxid
* @return
* @throws AuthenticationException
* @throws IOException
* @throws JSONException
* @throws EncoderException
*/
public static NamedList<Object> buildAclTxReport(TrackerRegistry trackerRegistry, InformationServer srv, String coreName, AclTracker tracker, Long acltxid)
throws AuthenticationException, IOException, JSONException, EncoderException
{
NamedList<Object> nr = new SimpleOrderedMap<Object>();
nr.add("TXID", acltxid);
nr.add("transaction", buildTrackerReport(trackerRegistry, srv, coreName, 0l, 0l, acltxid, acltxid, null, null));
NamedList<Object> nodes = new SimpleOrderedMap<Object>();
// add node reports ....
List<Long> dbAclIds = tracker.getAclsForDbAclTransaction(acltxid);
for (Long aclid : dbAclIds)
{
nodes.add("ACLID " + aclid, buildAclReport(tracker, aclid));
}
nr.add("aclTxDbAclCount", dbAclIds.size());
nr.add("nodes", nodes);
return nr;
}
/**
* Builds Node report
* @param tracker
* @param node
* @return
* @throws IOException
* @throws JSONException
*/
public static NamedList<Object> buildNodeReport(MetadataTracker tracker, Node node) throws IOException, JSONException
{
NodeReport nodeReport = tracker.checkNode(node);
NamedList<Object> nr = new SimpleOrderedMap<Object>();
nr.add("Node DBID", nodeReport.getDbid());
nr.add("DB TX", nodeReport.getDbTx());
nr.add("DB TX status", nodeReport.getDbNodeStatus().toString());
if (nodeReport.getIndexLeafDoc() != null)
{
nr.add("Leaf tx in Index", nodeReport.getIndexLeafTx());
}
if (nodeReport.getIndexAuxDoc() != null)
{
nr.add("Aux tx in Index", nodeReport.getIndexAuxTx());
}
nr.add("Indexed Node Doc Count", nodeReport.getIndexedNodeDocCount());
return nr;
}
/**
* Builds Node Report
* @param tracker
* @param dbid
* @return
* @throws IOException
* @throws JSONException
*/
public static NamedList<Object> buildNodeReport(MetadataTracker tracker, Long dbid) throws IOException, JSONException
{
NodeReport nodeReport = tracker.checkNode(dbid);
NamedList<Object> nr = new SimpleOrderedMap<Object>();
nr.add("Node DBID", nodeReport.getDbid());
nr.add("DB TX", nodeReport.getDbTx());
nr.add("DB TX status", nodeReport.getDbNodeStatus().toString());
if (nodeReport.getIndexLeafDoc() != null)
{
nr.add("Leaf tx in Index", nodeReport.getIndexLeafTx());
}
if (nodeReport.getIndexAuxDoc() != null)
{
nr.add("Aux tx in Index", nodeReport.getIndexAuxTx());
}
nr.add("Indexed Node Doc Count", nodeReport.getIndexedNodeDocCount());
return nr;
}
/**
* Builds Tracker report
* @param trackerRegistry
* @param srv
* @param coreName
* @param fromTx
* @param toTx
* @param fromAclTx
* @param toAclTx
* @param fromTime
* @param toTime
* @return
* @throws IOException
* @throws JSONException
* @throws AuthenticationException
* @throws EncoderException
*/
public static NamedList<Object> buildTrackerReport(TrackerRegistry trackerRegistry, InformationServer srv, String coreName, Long fromTx, Long toTx, Long fromAclTx, Long toAclTx,
Long fromTime, Long toTime) throws IOException, JSONException, AuthenticationException, EncoderException
{
// ACL
AclTracker aclTracker = trackerRegistry.getTrackerForCore(coreName, AclTracker.class);
IndexHealthReport aclReport = aclTracker.checkIndex(toTx, toAclTx, fromTime, toTime);
NamedList<Object> ihr = new SimpleOrderedMap<Object>();
ihr.add("Alfresco version", aclTracker.getAlfrescoVersion());
ihr.add("DB acl transaction count", aclReport.getDbAclTransactionCount());
ihr.add("Count of duplicated acl transactions in the index", aclReport.getDuplicatedAclTxInIndex()
.cardinality());
if (aclReport.getDuplicatedAclTxInIndex().cardinality() > 0)
{
ihr.add("First duplicate acl tx", aclReport.getDuplicatedAclTxInIndex().nextSetBit(0L));
}
ihr.add("Count of acl transactions in the index but not the DB", aclReport.getAclTxInIndexButNotInDb()
.cardinality());
if (aclReport.getAclTxInIndexButNotInDb().cardinality() > 0)
{
ihr.add("First acl transaction in the index but not the DB", aclReport.getAclTxInIndexButNotInDb()
.nextSetBit(0L));
}
ihr.add("Count of missing acl transactions from the Index", aclReport.getMissingAclTxFromIndex()
.cardinality());
if (aclReport.getMissingAclTxFromIndex().cardinality() > 0)
{
ihr.add("First acl transaction missing from the Index", aclReport.getMissingAclTxFromIndex()
.nextSetBit(0L));
}
ihr.add("Index acl transaction count", aclReport.getAclTransactionDocsInIndex());
ihr.add("Index unique acl transaction count", aclReport.getAclTransactionDocsInIndex());
TrackerState aclState = aclTracker.getTrackerState();
ihr.add("Last indexed change set commit time", aclState.getLastIndexedChangeSetCommitTime());
Date lastChangeSetDate = new Date(aclState.getLastIndexedChangeSetCommitTime());
ihr.add("Last indexed change set commit date", CachingDateFormat.getDateFormat().format(lastChangeSetDate));
ihr.add("Last changeset id before holes", aclState.getLastIndexedChangeSetIdBeforeHoles());
// Metadata
MetadataTracker metadataTracker = trackerRegistry.getTrackerForCore(coreName, MetadataTracker.class);
IndexHealthReport metaReport = metadataTracker.checkIndex(toTx, toAclTx, fromTime, toTime);
ihr.add("DB transaction count", metaReport.getDbTransactionCount());
ihr.add("Count of duplicated transactions in the index", metaReport.getDuplicatedTxInIndex()
.cardinality());
if (metaReport.getDuplicatedTxInIndex().cardinality() > 0)
{
ihr.add("First duplicate", metaReport.getDuplicatedTxInIndex().nextSetBit(0L));
}
ihr.add("Count of transactions in the index but not the DB", metaReport.getTxInIndexButNotInDb()
.cardinality());
if (metaReport.getTxInIndexButNotInDb().cardinality() > 0)
{
ihr.add("First transaction in the index but not the DB", metaReport.getTxInIndexButNotInDb()
.nextSetBit(0L));
}
ihr.add("Count of missing transactions from the Index", metaReport.getMissingTxFromIndex().cardinality());
if (metaReport.getMissingTxFromIndex().cardinality() > 0)
{
ihr.add("First transaction missing from the Index", metaReport.getMissingTxFromIndex()
.nextSetBit(0L));
}
ihr.add("Index transaction count", metaReport.getTransactionDocsInIndex());
ihr.add("Index unique transaction count", metaReport.getTransactionDocsInIndex());
ihr.add("Index node count", metaReport.getLeafDocCountInIndex());
ihr.add("Count of duplicate nodes in the index", metaReport.getDuplicatedLeafInIndex().cardinality());
if (metaReport.getDuplicatedLeafInIndex().cardinality() > 0)
{
ihr.add("First duplicate node id in the index", metaReport.getDuplicatedLeafInIndex().nextSetBit(0L));
}
ihr.add("Index error count", metaReport.getErrorDocCountInIndex());
ihr.add("Count of duplicate error docs in the index", metaReport.getDuplicatedErrorInIndex()
.cardinality());
if (metaReport.getDuplicatedErrorInIndex().cardinality() > 0)
{
ihr.add("First duplicate error in the index", SolrInformationServer.PREFIX_ERROR
+ metaReport.getDuplicatedErrorInIndex().nextSetBit(0L));
}
ihr.add("Index unindexed count", metaReport.getUnindexedDocCountInIndex());
ihr.add("Count of duplicate unindexed docs in the index", metaReport.getDuplicatedUnindexedInIndex()
.cardinality());
if (metaReport.getDuplicatedUnindexedInIndex().cardinality() > 0)
{
ihr.add("First duplicate unindexed in the index",
metaReport.getDuplicatedUnindexedInIndex().nextSetBit(0L));
}
TrackerState metaState = metadataTracker.getTrackerState();
ihr.add("Last indexed transaction commit time", metaState.getLastIndexedTxCommitTime());
Date lastTxDate = new Date(metaState.getLastIndexedTxCommitTime());
ihr.add("Last indexed transaction commit date", CachingDateFormat.getDateFormat().format(lastTxDate));
ihr.add("Last TX id before holes", metaState.getLastIndexedTxIdBeforeHoles());
srv.addFTSStatusCounts(ihr);
return ihr;
}
/**
* Adds a core summary
* @param cname
* @param detail
* @param hist
* @param values
* @param srv
* @param report
* @throws IOException
*/
public static void addCoreSummary(TrackerRegistry trackerRegistry, String cname, boolean detail, boolean hist, boolean values,
InformationServer srv, NamedList<Object> report) throws IOException
{
NamedList<Object> coreSummary = new SimpleOrderedMap<Object>();
coreSummary.addAll((SimpleOrderedMap<Object>) srv.getCoreStats());
MetadataTracker metaTrkr = trackerRegistry.getTrackerForCore(cname, MetadataTracker.class);
TrackerState metadataTrkrState = metaTrkr.getTrackerState();
long lastIndexTxCommitTime = metadataTrkrState.getLastIndexedTxCommitTime();
long lastIndexedTxId = metadataTrkrState.getLastIndexedTxId();
long lastTxCommitTimeOnServer = metadataTrkrState.getLastTxCommitTimeOnServer();
long lastTxIdOnServer = metadataTrkrState.getLastTxIdOnServer();
Date lastIndexTxCommitDate = new Date(lastIndexTxCommitTime);
Date lastTxOnServerDate = new Date(lastTxCommitTimeOnServer);
long transactionsToDo = lastTxIdOnServer - lastIndexedTxId;
if (transactionsToDo < 0)
{
transactionsToDo = 0;
}
AclTracker aclTrkr = trackerRegistry.getTrackerForCore(cname, AclTracker.class);
TrackerState aclTrkrState = aclTrkr.getTrackerState();
long lastIndexChangeSetCommitTime = aclTrkrState.getLastIndexedChangeSetCommitTime();
long lastIndexedChangeSetId = aclTrkrState.getLastIndexedChangeSetId();
long lastChangeSetCommitTimeOnServer = aclTrkrState.getLastChangeSetCommitTimeOnServer();
long lastChangeSetIdOnServer = aclTrkrState.getLastChangeSetIdOnServer();
Date lastIndexChangeSetCommitDate = new Date(lastIndexChangeSetCommitTime);
Date lastChangeSetOnServerDate = new Date(lastChangeSetCommitTimeOnServer);
long changeSetsToDo = lastChangeSetIdOnServer - lastIndexedChangeSetId;
if (changeSetsToDo < 0)
{
changeSetsToDo = 0;
}
long nodesToDo = 0;
long remainingTxTimeMillis = 0;
if (transactionsToDo > 0)
{
// We now use the elapsed time as seen by the single thread farming out metadata indexing
double meanDocsPerTx = srv.getTrackerStats().getMeanDocsPerTx();
double meanNodeElaspedIndexTime = srv.getTrackerStats().getMeanNodeElapsedIndexTime();
nodesToDo = (long)(transactionsToDo * meanDocsPerTx);
remainingTxTimeMillis = (long) (nodesToDo * meanNodeElaspedIndexTime);
}
Date now = new Date();
Date end = new Date(now.getTime() + remainingTxTimeMillis);
Duration remainingTx = new Duration(now, end);
long remainingChangeSetTimeMillis = 0;
if (changeSetsToDo > 0)
{
// We now use the elapsed time as seen by the single thread farming out alc indexing
double meanAclsPerChangeSet = srv.getTrackerStats().getMeanAclsPerChangeSet();
double meanAclElapsedIndexTime = srv.getTrackerStats().getMeanAclElapsedIndexTime();
remainingChangeSetTimeMillis = (long) (changeSetsToDo * meanAclsPerChangeSet * meanAclElapsedIndexTime);
}
now = new Date();
end = new Date(now.getTime() + remainingChangeSetTimeMillis);
Duration remainingChangeSet = new Duration(now, end);
NamedList<Object> ftsSummary = new SimpleOrderedMap<Object>();
long remainingContentTimeMillis = 0;
srv.addFTSStatusCounts(ftsSummary);
long cleanCount = ((Long)ftsSummary.get("Node count with FTSStatus Clean")).longValue();
long dirtyCount = ((Long)ftsSummary.get("Node count with FTSStatus Dirty")).longValue();
long newCount = ((Long)ftsSummary.get("Node count with FTSStatus New")).longValue();
long nodesInIndex = ((Long)coreSummary.get("Alfresco Nodes in Index"));
long contentYetToSee = nodesInIndex > 0 ? nodesToDo * (cleanCount + dirtyCount + newCount)/nodesInIndex : 0;;
if (dirtyCount + newCount + contentYetToSee > 0)
{
// We now use the elapsed time as seen by the single thread farming out alc indexing
double meanContentElapsedIndexTime = srv.getTrackerStats().getMeanContentElapsedIndexTime();
remainingContentTimeMillis = (long) ((dirtyCount + newCount + contentYetToSee) * meanContentElapsedIndexTime);
}
now = new Date();
end = new Date(now.getTime() + remainingContentTimeMillis);
Duration remainingContent = new Duration(now, end);
coreSummary.add("FTS",ftsSummary);
Duration txLag = new Duration(lastIndexTxCommitDate, lastTxOnServerDate);
if (lastIndexTxCommitDate.compareTo(lastTxOnServerDate) > 0)
{
txLag = new Duration();
}
long txLagSeconds = (lastTxCommitTimeOnServer - lastIndexTxCommitTime) / 1000;
if (txLagSeconds < 0)
{
txLagSeconds = 0;
}
Duration changeSetLag = new Duration(lastIndexChangeSetCommitDate, lastChangeSetOnServerDate);
if (lastIndexChangeSetCommitDate.compareTo(lastChangeSetOnServerDate) > 0)
{
changeSetLag = new Duration();
}
long changeSetLagSeconds = (lastChangeSetCommitTimeOnServer - lastIndexChangeSetCommitTime) / 1000;
if (txLagSeconds < 0)
{
txLagSeconds = 0;
}
ContentTracker contentTrkr = trackerRegistry.getTrackerForCore(cname, ContentTracker.class);
TrackerState contentTrkrState = contentTrkr.getTrackerState();
// Leave ModelTracker out of this check, because it is common
boolean aTrackerIsRunning = aclTrkrState.isRunning() || metadataTrkrState.isRunning()
|| contentTrkrState.isRunning();
coreSummary.add("Active", aTrackerIsRunning);
ModelTracker modelTrkr = trackerRegistry.getModelTracker();
TrackerState modelTrkrState = modelTrkr.getTrackerState();
coreSummary.add("ModelTracker Active", modelTrkrState.isRunning());
coreSummary.add("ContentTracker Active", contentTrkrState.isRunning());
coreSummary.add("MetadataTracker Active", metadataTrkrState.isRunning());
coreSummary.add("AclTracker Active", aclTrkrState.isRunning());
// TX
coreSummary.add("Last Index TX Commit Time", lastIndexTxCommitTime);
coreSummary.add("Last Index TX Commit Date", lastIndexTxCommitDate);
coreSummary.add("TX Lag", txLagSeconds + " s");
coreSummary.add("TX Duration", txLag.toString());
coreSummary.add("Timestamp for last TX on server", lastTxCommitTimeOnServer);
coreSummary.add("Date for last TX on server", lastTxOnServerDate);
coreSummary.add("Id for last TX on server", lastTxIdOnServer);
coreSummary.add("Id for last TX in index", lastIndexedTxId);
coreSummary.add("Approx transactions remaining", transactionsToDo);
coreSummary.add("Approx transaction indexing time remaining", remainingTx.largestComponentformattedString());
// Change set
coreSummary.add("Last Index Change Set Commit Time", lastIndexChangeSetCommitTime);
coreSummary.add("Last Index Change Set Commit Date", lastIndexChangeSetCommitDate);
coreSummary.add("Change Set Lag", changeSetLagSeconds + " s");
coreSummary.add("Change Set Duration", changeSetLag.toString());
coreSummary.add("Timestamp for last Change Set on server", lastChangeSetCommitTimeOnServer);
coreSummary.add("Date for last Change Set on server", lastChangeSetOnServerDate);
coreSummary.add("Id for last Change Set on server", lastChangeSetIdOnServer);
coreSummary.add("Id for last Change Set in index", lastIndexedChangeSetId);
coreSummary.add("Approx change sets remaining", changeSetsToDo);
coreSummary.add("Approx change set indexing time remaining",
remainingChangeSet.largestComponentformattedString());
coreSummary.add("Approx content indexing time remaining",
remainingContent.largestComponentformattedString());
// Stats
coreSummary.add("Model sync times (ms)",
srv.getTrackerStats().getModelTimes().getNamedList(detail, hist, values));
coreSummary.add("Acl index time (ms)",
srv.getTrackerStats().getAclTimes().getNamedList(detail, hist, values));
coreSummary.add("Node index time (ms)",
srv.getTrackerStats().getNodeTimes().getNamedList(detail, hist, values));
coreSummary.add("Docs/Tx", srv.getTrackerStats().getTxDocs().getNamedList(detail, hist, values));
coreSummary.add("Doc Transformation time (ms)", srv.getTrackerStats().getDocTransformationTimes()
.getNamedList(detail, hist, values));
// Model
Map<String, Set<String>> modelErrors = srv.getModelErrors();
if (modelErrors.size() > 0)
{
NamedList<Object> errorList = new SimpleOrderedMap<Object>();
for (Map.Entry<String, Set<String>> modelNameToErrors : modelErrors.entrySet())
{
errorList.add(modelNameToErrors.getKey(), modelNameToErrors.getValue());
}
coreSummary.add("Model changes are not compatible with the existing data model and have not been applied",
errorList);
}
report.add(cname, coreSummary);
}
}
@@ -0,0 +1,564 @@
/*
* Copyright (C) 2005 - 2016 Alfresco Software Limited
*
* This file is part of the Alfresco software.
* If the software was purchased under a paid Alfresco license, the terms of
* the paid license agreement will prevail. Otherwise, the software is
* provided under the following open source license terms:
*
* Alfresco 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 3 of the License, or
* (at your option) any later version.
*
* Alfresco 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 Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.solr;
import org.alfresco.error.AlfrescoRuntimeException;
import org.alfresco.service.cmr.repository.datatype.Duration;
import org.alfresco.solr.client.Node;
import org.alfresco.solr.tracker.*;
import org.alfresco.util.CachingDateFormat;
import org.apache.solr.common.util.NamedList;
import org.apache.solr.common.util.SimpleOrderedMap;
import org.json.JSONException;
import java.io.IOException;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Set;
import static java.util.Optional.ofNullable;
/**
* Methods taken from AlfrescoCoreAdminHandler that deal with building reports
*/
class HandlerReportHelper
{
static NamedList<Object> buildAclReport(AclTracker tracker, Long aclid) throws JSONException
{
AclReport aclReport = tracker.checkAcl(aclid);
NamedList<Object> nr = new SimpleOrderedMap<>();
nr.add("Acl Id", aclReport.getAclId());
nr.add("Acl doc in index", aclReport.getIndexAclDoc());
if (aclReport.getIndexAclDoc() != null)
{
nr.add("Acl tx in Index", aclReport.getIndexAclTx());
}
return nr;
}
static NamedList<Object> buildTxReport(TrackerRegistry trackerRegistry, InformationServer srv, String coreName, MetadataTracker tracker, Long txid) throws JSONException
{
NamedList<Object> nr = new SimpleOrderedMap<>();
nr.add("TXID", txid);
nr.add("transaction", buildTrackerReport(trackerRegistry, srv, coreName, txid, txid, 0L, 0L, null, null));
NamedList<Object> nodes = new SimpleOrderedMap<>();
// add node reports ....
List<Node> dbNodes = tracker.getFullNodesForDbTransaction(txid);
for (Node node : dbNodes)
{
nodes.add("DBID " + node.getId(), buildNodeReport(tracker, node));
}
nr.add("txDbNodeCount", dbNodes.size());
nr.add("nodes", nodes);
return nr;
}
static NamedList<Object> buildAclTxReport(TrackerRegistry trackerRegistry, InformationServer srv, String coreName, AclTracker tracker, Long acltxid) throws JSONException
{
try {
NamedList<Object> nr = new SimpleOrderedMap<>();
nr.add("TXID", acltxid);
nr.add("transaction", buildTrackerReport(trackerRegistry, srv, coreName, 0L, 0L, acltxid, acltxid, null, null));
NamedList<Object> nodes = new SimpleOrderedMap<>();
// add node reports ....
List<Long> dbAclIds = tracker.getAclsForDbAclTransaction(acltxid);
for (Long aclid : dbAclIds) {
nodes.add("ACLID " + aclid, buildAclReport(tracker, aclid));
}
nr.add("aclTxDbAclCount", dbAclIds.size());
nr.add("nodes", nodes);
return nr;
}
catch (Exception exception)
{
throw new AlfrescoRuntimeException("", exception);
}
}
static NamedList<Object> buildNodeReport(MetadataTracker tracker, Node node) throws JSONException
{
NodeReport nodeReport = tracker.checkNode(node);
NamedList<Object> nr = new SimpleOrderedMap<>();
nr.add("Node DBID", nodeReport.getDbid());
nr.add("DB TX", nodeReport.getDbTx());
nr.add("DB TX status", nodeReport.getDbNodeStatus().toString());
if (nodeReport.getIndexLeafDoc() != null)
{
nr.add("Leaf tx in Index", nodeReport.getIndexLeafTx());
}
if (nodeReport.getIndexAuxDoc() != null)
{
nr.add("Aux tx in Index", nodeReport.getIndexAuxTx());
}
nr.add("Indexed Node Doc Count", nodeReport.getIndexedNodeDocCount());
return nr;
}
static NamedList<Object> buildNodeReport(CoreStatePublisher publisher, Long dbid) throws JSONException
{
NodeReport nodeReport = publisher.checkNode(dbid);
NamedList<Object> payload = new SimpleOrderedMap<>();
payload.add("Node DBID", nodeReport.getDbid());
if (publisher.isOnMasterOrStandalone())
{
ofNullable(nodeReport.getDbTx()).ifPresent(value -> payload.add("DB TX", value));
ofNullable(nodeReport.getDbNodeStatus()).map(Object::toString).ifPresent(value -> payload.add("DB TX Status", value));
ofNullable(nodeReport.getIndexLeafTx()).ifPresent(value -> payload.add("Leaf tx in Index", value));
ofNullable(nodeReport.getIndexAuxDoc()).ifPresent(value -> payload.add("Aux tx in Index", value));
}
else
{
payload.add("WARNING", "This response comes from a slave core and it contains minimal information about the node. " +
"Please consider to re-submit the same request to the corresponding Master, in order to get more information.");
}
ofNullable(nodeReport.getIndexedNodeDocCount()).ifPresent(value -> payload.add("Indexed Node Doc Count", value));
return payload;
}
/**
* Builds Tracker report
*/
static NamedList<Object> buildTrackerReport(TrackerRegistry trackerRegistry, InformationServer srv, String coreName, Long fromTx, Long toTx, Long fromAclTx, Long toAclTx,
Long fromTime, Long toTime) throws JSONException
{
try
{
// ACL
AclTracker aclTracker = trackerRegistry.getTrackerForCore(coreName, AclTracker.class);
IndexHealthReport aclReport = aclTracker.checkIndex(toTx, toAclTx, fromTime, toTime);
NamedList<Object> ihr = new SimpleOrderedMap<>();
ihr.add("Alfresco version", aclTracker.getAlfrescoVersion());
ihr.add("DB acl transaction count", aclReport.getDbAclTransactionCount());
ihr.add("Count of duplicated acl transactions in the index", aclReport.getDuplicatedAclTxInIndex()
.cardinality());
if (aclReport.getDuplicatedAclTxInIndex().cardinality() > 0) {
ihr.add("First duplicate acl tx", aclReport.getDuplicatedAclTxInIndex().nextSetBit(0L));
}
ihr.add("Count of acl transactions in the index but not the DB", aclReport.getAclTxInIndexButNotInDb()
.cardinality());
if (aclReport.getAclTxInIndexButNotInDb().cardinality() > 0) {
ihr.add("First acl transaction in the index but not the DB", aclReport.getAclTxInIndexButNotInDb()
.nextSetBit(0L));
}
ihr.add("Count of missing acl transactions from the Index", aclReport.getMissingAclTxFromIndex()
.cardinality());
if (aclReport.getMissingAclTxFromIndex().cardinality() > 0) {
ihr.add("First acl transaction missing from the Index", aclReport.getMissingAclTxFromIndex()
.nextSetBit(0L));
}
ihr.add("Index acl transaction count", aclReport.getAclTransactionDocsInIndex());
ihr.add("Index unique acl transaction count", aclReport.getAclTransactionDocsInIndex());
TrackerState aclState = aclTracker.getTrackerState();
ihr.add("Last indexed change set commit time", aclState.getLastIndexedChangeSetCommitTime());
Date lastChangeSetDate = new Date(aclState.getLastIndexedChangeSetCommitTime());
ihr.add("Last indexed change set commit date", CachingDateFormat.getDateFormat().format(lastChangeSetDate));
ihr.add("Last changeset id before holes", aclState.getLastIndexedChangeSetIdBeforeHoles());
// Metadata
MetadataTracker metadataTracker = trackerRegistry.getTrackerForCore(coreName, MetadataTracker.class);
IndexHealthReport metaReport = metadataTracker.checkIndex(toTx, toAclTx, fromTime, toTime);
ihr.add("DB transaction count", metaReport.getDbTransactionCount());
ihr.add("Count of duplicated transactions in the index", metaReport.getDuplicatedTxInIndex()
.cardinality());
if (metaReport.getDuplicatedTxInIndex().cardinality() > 0) {
ihr.add("First duplicate", metaReport.getDuplicatedTxInIndex().nextSetBit(0L));
}
ihr.add("Count of transactions in the index but not the DB", metaReport.getTxInIndexButNotInDb()
.cardinality());
if (metaReport.getTxInIndexButNotInDb().cardinality() > 0) {
ihr.add("First transaction in the index but not the DB", metaReport.getTxInIndexButNotInDb()
.nextSetBit(0L));
}
ihr.add("Count of missing transactions from the Index", metaReport.getMissingTxFromIndex().cardinality());
if (metaReport.getMissingTxFromIndex().cardinality() > 0) {
ihr.add("First transaction missing from the Index", metaReport.getMissingTxFromIndex()
.nextSetBit(0L));
}
ihr.add("Index transaction count", metaReport.getTransactionDocsInIndex());
ihr.add("Index unique transaction count", metaReport.getTransactionDocsInIndex());
ihr.add("Index node count", metaReport.getLeafDocCountInIndex());
ihr.add("Count of duplicate nodes in the index", metaReport.getDuplicatedLeafInIndex().cardinality());
if (metaReport.getDuplicatedLeafInIndex().cardinality() > 0) {
ihr.add("First duplicate node id in the index", metaReport.getDuplicatedLeafInIndex().nextSetBit(0L));
}
ihr.add("Index error count", metaReport.getErrorDocCountInIndex());
ihr.add("Count of duplicate error docs in the index", metaReport.getDuplicatedErrorInIndex()
.cardinality());
if (metaReport.getDuplicatedErrorInIndex().cardinality() > 0) {
ihr.add("First duplicate error in the index", SolrInformationServer.PREFIX_ERROR
+ metaReport.getDuplicatedErrorInIndex().nextSetBit(0L));
}
ihr.add("Index unindexed count", metaReport.getUnindexedDocCountInIndex());
ihr.add("Count of duplicate unindexed docs in the index", metaReport.getDuplicatedUnindexedInIndex()
.cardinality());
if (metaReport.getDuplicatedUnindexedInIndex().cardinality() > 0) {
ihr.add("First duplicate unindexed in the index",
metaReport.getDuplicatedUnindexedInIndex().nextSetBit(0L));
}
TrackerState metaState = metadataTracker.getTrackerState();
ihr.add("Last indexed transaction commit time", metaState.getLastIndexedTxCommitTime());
Date lastTxDate = new Date(metaState.getLastIndexedTxCommitTime());
ihr.add("Last indexed transaction commit date", CachingDateFormat.getDateFormat().format(lastTxDate));
ihr.add("Last TX id before holes", metaState.getLastIndexedTxIdBeforeHoles());
srv.addFTSStatusCounts(ihr);
return ihr;
}
catch (Exception exception)
{
throw new AlfrescoRuntimeException("", exception);
}
}
static void addSlaveCoreSummary(TrackerRegistry trackerRegistry, String cname, boolean detail, boolean hist, boolean values,
InformationServer srv, NamedList<Object> report) throws IOException
{
NamedList<Object> coreSummary = new SimpleOrderedMap<>();
coreSummary.addAll((SimpleOrderedMap<Object>) srv.getCoreStats());
SlaveCoreStatePublisher statePublisher = trackerRegistry.getTrackerForCore(cname, SlaveCoreStatePublisher.class);
TrackerState trackerState = statePublisher.getTrackerState();
long lastIndexTxCommitTime = trackerState.getLastIndexedTxCommitTime();
long lastIndexedTxId = trackerState.getLastIndexedTxId();
long lastTxCommitTimeOnServer = trackerState.getLastTxCommitTimeOnServer();
long lastTxIdOnServer = trackerState.getLastTxIdOnServer();
Date lastIndexTxCommitDate = new Date(lastIndexTxCommitTime);
Date lastTxOnServerDate = new Date(lastTxCommitTimeOnServer);
long transactionsToDo = lastTxIdOnServer - lastIndexedTxId;
if (transactionsToDo < 0)
{
transactionsToDo = 0;
}
long nodesToDo = 0;
long remainingTxTimeMillis = 0;
if (transactionsToDo > 0)
{
// We now use the elapsed time as seen by the single thread farming out metadata indexing
double meanDocsPerTx = srv.getTrackerStats().getMeanDocsPerTx();
double meanNodeElaspedIndexTime = srv.getTrackerStats().getMeanNodeElapsedIndexTime();
nodesToDo = (long)(transactionsToDo * meanDocsPerTx);
remainingTxTimeMillis = (long) (nodesToDo * meanNodeElaspedIndexTime);
}
Date now = new Date();
Date end = new Date(now.getTime() + remainingTxTimeMillis);
Duration remainingTx = new Duration(now, end);
long remainingChangeSetTimeMillis = 0;
now = new Date();
end = new Date(now.getTime() + remainingChangeSetTimeMillis);
Duration remainingChangeSet = new Duration(now, end);
NamedList<Object> ftsSummary = new SimpleOrderedMap<>();
long remainingContentTimeMillis = 0;
srv.addFTSStatusCounts(ftsSummary);
long cleanCount =
ofNullable(ftsSummary.get("Node count with FTSStatus Clean"))
.map(Number.class::cast)
.map(Number::longValue)
.orElse(0L);
long dirtyCount =
ofNullable(ftsSummary.get("Node count with FTSStatus Dirty"))
.map(Number.class::cast)
.map(Number::longValue)
.orElse(0L);
long newCount =
ofNullable(ftsSummary.get("Node count with FTSStatus New"))
.map(Number.class::cast)
.map(Number::longValue)
.orElse(0L);
long nodesInIndex =
ofNullable(coreSummary.get("Alfresco Nodes in Index"))
.map(Number.class::cast)
.map(Number::longValue)
.orElse(0L);
long contentYetToSee = nodesInIndex > 0 ? nodesToDo * (cleanCount + dirtyCount + newCount)/nodesInIndex : 0;
if (dirtyCount + newCount + contentYetToSee > 0)
{
// We now use the elapsed time as seen by the single thread farming out alc indexing
double meanContentElapsedIndexTime = srv.getTrackerStats().getMeanContentElapsedIndexTime();
remainingContentTimeMillis = (long) ((dirtyCount + newCount + contentYetToSee) * meanContentElapsedIndexTime);
}
now = new Date();
end = new Date(now.getTime() + remainingContentTimeMillis);
Duration remainingContent = new Duration(now, end);
coreSummary.add("FTS",ftsSummary);
Duration txLag = new Duration(lastIndexTxCommitDate, lastTxOnServerDate);
if (lastIndexTxCommitDate.compareTo(lastTxOnServerDate) > 0)
{
txLag = new Duration();
}
long txLagSeconds = (lastTxCommitTimeOnServer - lastIndexTxCommitTime) / 1000;
if (txLagSeconds < 0)
{
txLagSeconds = 0;
}
ModelTracker modelTrkr = trackerRegistry.getModelTracker();
TrackerState modelTrkrState = modelTrkr.getTrackerState();
coreSummary.add("ModelTracker Active", modelTrkrState.isRunning());
coreSummary.add("NodeState Publisher Active", trackerState.isRunning());
// TX
coreSummary.add("Last Index TX Commit Time", lastIndexTxCommitTime);
coreSummary.add("Last Index TX Commit Date", lastIndexTxCommitDate);
coreSummary.add("TX Lag", txLagSeconds + " s");
coreSummary.add("TX Duration", txLag.toString());
coreSummary.add("Timestamp for last TX on server", lastTxCommitTimeOnServer);
coreSummary.add("Date for last TX on server", lastTxOnServerDate);
coreSummary.add("Id for last TX on server", lastTxIdOnServer);
coreSummary.add("Id for last TX in index", lastIndexedTxId);
coreSummary.add("Approx transactions remaining", transactionsToDo);
coreSummary.add("Approx transaction indexing time remaining", remainingTx.largestComponentformattedString());
// Stats
coreSummary.add("Model sync times (ms)", srv.getTrackerStats().getModelTimes().getNamedList(detail, hist, values));
coreSummary.add("Docs/Tx", srv.getTrackerStats().getTxDocs().getNamedList(detail, hist, values));
// Model
Map<String, Set<String>> modelErrors = srv.getModelErrors();
if (modelErrors.size() > 0)
{
NamedList<Object> errorList = new SimpleOrderedMap<>();
for (Map.Entry<String, Set<String>> modelNameToErrors : modelErrors.entrySet())
{
errorList.add(modelNameToErrors.getKey(), modelNameToErrors.getValue());
}
coreSummary.add("Model changes are not compatible with the existing data model and have not been applied", errorList);
}
report.add(cname, coreSummary);
}
static void addMasterOrStandaloneCoreSummary(TrackerRegistry trackerRegistry, String cname, boolean detail, boolean hist, boolean values,
InformationServer srv, NamedList<Object> report) throws IOException
{
NamedList<Object> coreSummary = new SimpleOrderedMap<>();
coreSummary.addAll((SimpleOrderedMap<Object>) srv.getCoreStats());
MetadataTracker metaTrkr = trackerRegistry.getTrackerForCore(cname, MetadataTracker.class);
TrackerState metadataTrkrState = metaTrkr.getTrackerState();
long lastIndexTxCommitTime = metadataTrkrState.getLastIndexedTxCommitTime();
long lastIndexedTxId = metadataTrkrState.getLastIndexedTxId();
long lastTxCommitTimeOnServer = metadataTrkrState.getLastTxCommitTimeOnServer();
long lastTxIdOnServer = metadataTrkrState.getLastTxIdOnServer();
Date lastIndexTxCommitDate = new Date(lastIndexTxCommitTime);
Date lastTxOnServerDate = new Date(lastTxCommitTimeOnServer);
long transactionsToDo = lastTxIdOnServer - lastIndexedTxId;
if (transactionsToDo < 0)
{
transactionsToDo = 0;
}
AclTracker aclTrkr = trackerRegistry.getTrackerForCore(cname, AclTracker.class);
TrackerState aclTrkrState = aclTrkr.getTrackerState();
long lastIndexChangeSetCommitTime = aclTrkrState.getLastIndexedChangeSetCommitTime();
long lastIndexedChangeSetId = aclTrkrState.getLastIndexedChangeSetId();
long lastChangeSetCommitTimeOnServer = aclTrkrState.getLastChangeSetCommitTimeOnServer();
long lastChangeSetIdOnServer = aclTrkrState.getLastChangeSetIdOnServer();
Date lastIndexChangeSetCommitDate = new Date(lastIndexChangeSetCommitTime);
Date lastChangeSetOnServerDate = new Date(lastChangeSetCommitTimeOnServer);
long changeSetsToDo = lastChangeSetIdOnServer - lastIndexedChangeSetId;
if (changeSetsToDo < 0)
{
changeSetsToDo = 0;
}
long nodesToDo = 0;
long remainingTxTimeMillis = 0;
if (transactionsToDo > 0)
{
// We now use the elapsed time as seen by the single thread farming out metadata indexing
double meanDocsPerTx = srv.getTrackerStats().getMeanDocsPerTx();
double meanNodeElaspedIndexTime = srv.getTrackerStats().getMeanNodeElapsedIndexTime();
nodesToDo = (long)(transactionsToDo * meanDocsPerTx);
remainingTxTimeMillis = (long) (nodesToDo * meanNodeElaspedIndexTime);
}
Date now = new Date();
Date end = new Date(now.getTime() + remainingTxTimeMillis);
Duration remainingTx = new Duration(now, end);
long remainingChangeSetTimeMillis = 0;
if (changeSetsToDo > 0)
{
// We now use the elapsed time as seen by the single thread farming out alc indexing
double meanAclsPerChangeSet = srv.getTrackerStats().getMeanAclsPerChangeSet();
double meanAclElapsedIndexTime = srv.getTrackerStats().getMeanAclElapsedIndexTime();
remainingChangeSetTimeMillis = (long) (changeSetsToDo * meanAclsPerChangeSet * meanAclElapsedIndexTime);
}
now = new Date();
end = new Date(now.getTime() + remainingChangeSetTimeMillis);
Duration remainingChangeSet = new Duration(now, end);
NamedList<Object> ftsSummary = new SimpleOrderedMap<>();
long remainingContentTimeMillis = 0;
srv.addFTSStatusCounts(ftsSummary);
long cleanCount =
ofNullable(ftsSummary.get("Node count with FTSStatus Clean"))
.map(Number.class::cast)
.map(Number::longValue)
.orElse(0L);
long dirtyCount =
ofNullable(ftsSummary.get("Node count with FTSStatus Dirty"))
.map(Number.class::cast)
.map(Number::longValue)
.orElse(0L);
long newCount =
ofNullable(ftsSummary.get("Node count with FTSStatus New"))
.map(Number.class::cast)
.map(Number::longValue)
.orElse(0L);
long nodesInIndex =
ofNullable(coreSummary.get("Alfresco Nodes in Index"))
.map(Number.class::cast)
.map(Number::longValue)
.orElse(0L);
long contentYetToSee = nodesInIndex > 0 ? nodesToDo * (cleanCount + dirtyCount + newCount)/nodesInIndex : 0;
if (dirtyCount + newCount + contentYetToSee > 0)
{
// We now use the elapsed time as seen by the single thread farming out alc indexing
double meanContentElapsedIndexTime = srv.getTrackerStats().getMeanContentElapsedIndexTime();
remainingContentTimeMillis = (long) ((dirtyCount + newCount + contentYetToSee) * meanContentElapsedIndexTime);
}
now = new Date();
end = new Date(now.getTime() + remainingContentTimeMillis);
Duration remainingContent = new Duration(now, end);
coreSummary.add("FTS",ftsSummary);
Duration txLag = new Duration(lastIndexTxCommitDate, lastTxOnServerDate);
if (lastIndexTxCommitDate.compareTo(lastTxOnServerDate) > 0)
{
txLag = new Duration();
}
long txLagSeconds = (lastTxCommitTimeOnServer - lastIndexTxCommitTime) / 1000;
if (txLagSeconds < 0)
{
txLagSeconds = 0;
}
Duration changeSetLag = new Duration(lastIndexChangeSetCommitDate, lastChangeSetOnServerDate);
if (lastIndexChangeSetCommitDate.compareTo(lastChangeSetOnServerDate) > 0)
{
changeSetLag = new Duration();
}
long changeSetLagSeconds = (lastChangeSetCommitTimeOnServer - lastIndexChangeSetCommitTime) / 1000;
if (txLagSeconds < 0)
{
txLagSeconds = 0;
}
ContentTracker contentTrkr = trackerRegistry.getTrackerForCore(cname, ContentTracker.class);
TrackerState contentTrkrState = contentTrkr.getTrackerState();
// Leave ModelTracker out of this check, because it is common
boolean aTrackerIsRunning = aclTrkrState.isRunning() || metadataTrkrState.isRunning()
|| contentTrkrState.isRunning();
coreSummary.add("Active", aTrackerIsRunning);
ModelTracker modelTrkr = trackerRegistry.getModelTracker();
TrackerState modelTrkrState = modelTrkr.getTrackerState();
coreSummary.add("ModelTracker Active", modelTrkrState.isRunning());
coreSummary.add("ContentTracker Active", contentTrkrState.isRunning());
coreSummary.add("MetadataTracker Active", metadataTrkrState.isRunning());
coreSummary.add("AclTracker Active", aclTrkrState.isRunning());
// TX
coreSummary.add("Last Index TX Commit Time", lastIndexTxCommitTime);
coreSummary.add("Last Index TX Commit Date", lastIndexTxCommitDate);
coreSummary.add("TX Lag", txLagSeconds + " s");
coreSummary.add("TX Duration", txLag.toString());
coreSummary.add("Timestamp for last TX on server", lastTxCommitTimeOnServer);
coreSummary.add("Date for last TX on server", lastTxOnServerDate);
coreSummary.add("Id for last TX on server", lastTxIdOnServer);
coreSummary.add("Id for last TX in index", lastIndexedTxId);
coreSummary.add("Approx transactions remaining", transactionsToDo);
coreSummary.add("Approx transaction indexing time remaining", remainingTx.largestComponentformattedString());
// Change set
coreSummary.add("Last Index Change Set Commit Time", lastIndexChangeSetCommitTime);
coreSummary.add("Last Index Change Set Commit Date", lastIndexChangeSetCommitDate);
coreSummary.add("Change Set Lag", changeSetLagSeconds + " s");
coreSummary.add("Change Set Duration", changeSetLag.toString());
coreSummary.add("Timestamp for last Change Set on server", lastChangeSetCommitTimeOnServer);
coreSummary.add("Date for last Change Set on server", lastChangeSetOnServerDate);
coreSummary.add("Id for last Change Set on server", lastChangeSetIdOnServer);
coreSummary.add("Id for last Change Set in index", lastIndexedChangeSetId);
coreSummary.add("Approx change sets remaining", changeSetsToDo);
coreSummary.add("Approx change set indexing time remaining",
remainingChangeSet.largestComponentformattedString());
coreSummary.add("Approx content indexing time remaining",
remainingContent.largestComponentformattedString());
// Stats
coreSummary.add("Model sync times (ms)",
srv.getTrackerStats().getModelTimes().getNamedList(detail, hist, values));
coreSummary.add("Acl index time (ms)",
srv.getTrackerStats().getAclTimes().getNamedList(detail, hist, values));
coreSummary.add("Node index time (ms)",
srv.getTrackerStats().getNodeTimes().getNamedList(detail, hist, values));
coreSummary.add("Docs/Tx", srv.getTrackerStats().getTxDocs().getNamedList(detail, hist, values));
coreSummary.add("Doc Transformation time (ms)", srv.getTrackerStats().getDocTransformationTimes()
.getNamedList(detail, hist, values));
// Model
Map<String, Set<String>> modelErrors = srv.getModelErrors();
if (modelErrors.size() > 0)
{
NamedList<Object> errorList = new SimpleOrderedMap<>();
for (Map.Entry<String, Set<String>> modelNameToErrors : modelErrors.entrySet())
{
errorList.add(modelNameToErrors.getKey(), modelNameToErrors.getValue());
}
coreSummary.add("Model changes are not compatible with the existing data model and have not been applied",
errorList);
}
report.add(cname, coreSummary);
}
}
@@ -179,4 +179,6 @@ public interface InformationServer extends InformationServerCollectionProvider
String getHostName();
String getBaseUrl();
void flushContentStore() throws IOException;
}
@@ -3892,4 +3892,10 @@ public class SolrInformationServer implements InformationServer
}
return searchers;
}
@Override
public void flushContentStore() throws IOException
{
solrContentStore.flushChangeSet();
}
}
@@ -0,0 +1,106 @@
/*
* Copyright (C) 2005-2019 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco 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 3 of the License, or
* (at your option) any later version.
*
* Alfresco 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 Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.solr.content;
import org.alfresco.solr.client.NodeMetaData;
import org.apache.solr.common.SolrInputDocument;
import java.io.Closeable;
import java.io.IOException;
import java.util.List;
import java.util.Map;
/**
* Behavioural interface which indicates the type of content store access mode for the owning node.
* A replication interaction is composed at least by 2 roles: a master and a slave.
*
* Despite the same interface, the behaviour of the content store management changes depending on the node kind:
*
* <ul>
* <li>Master Node: READ + WRITE + Changes tracking. Writes and changes tracking are a direct consequence of the "indexing" nature of the master node.</li>
* <li>Slave Node: READ ONLY (i.e. never write: changes are applied on the master and replicated on slaves)</li>
* </ul>
*
* Important: the owning entity *is not* the SolrCore instance: the Content store is shared across all cores of
* A node can transit from Read to Write mode
*
* @author Andrea Gazzarini
* @since 1.5
*/
interface AccessMode extends Closeable
{
/**
* Returns the last persisted content store version.
*
* @return the last persisted content store version, SolrContentStore#NO_VERSION_AVAILABLE in case the version isn't available.
*/
long getLastCommittedVersion();
/**
* Persists the last committed version on the hosting node.
* Note that this is tipically valid only on slave node, because the master already manages the content store version on
* a persistent storage, so it doesn't need to call this method.
*
* @param version the last committed content store version.
*/
void setLastCommittedVersion(long version);
Map<String, List<Map<String, Object>>> getChanges(long version);
/**
* Stores a {@link SolrInputDocument} into Alfresco solr content store.
*
* @param tenant the owning tenant.
* @param dbId the document DBID
* @param doc the document itself.
*/
void storeDocOnSolrContentStore(String tenant, long dbId, SolrInputDocument doc);
/**
* Stores a {@link SolrInputDocument} into Alfresco solr content store.
*
* @param nodeMetaData the node metadata.
* @param doc the document itself.
*/
void storeDocOnSolrContentStore(NodeMetaData nodeMetaData, SolrInputDocument doc);
/**
* Removes a node from the content store.
*
* @param nodeMetaData the node metadata.
*/
void removeDocFromContentStore(NodeMetaData nodeMetaData);
/**
* Flushes pending changesets.
*
* @throws IOException in case of I/O failure.
*/
void flushChangeSet() throws IOException;
/**
* Tries to transition from this mode to the readOnlyMode.
*/
void switchOnReadOnlyMode();
/**
* Tries to transition from this mode to the read/write mode.
*/
void switchOnReadWriteMode();
}
@@ -0,0 +1,460 @@
/*
* Copyright (C) 2005-2019 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco 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 3 of the License, or
* (at your option) any later version.
*
* Alfresco 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 Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.solr.content;
import static java.util.Arrays.asList;
import static java.util.Arrays.stream;
import static java.util.Collections.emptySet;
import static java.util.Optional.ofNullable;
import org.alfresco.util.Pair;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.LongPoint;
import org.apache.lucene.document.NumericDocValuesField;
import org.apache.lucene.document.StoredField;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.IndexWriterConfig;
import org.apache.lucene.search.FieldDoc;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.MatchAllDocsQuery;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.ScoreDoc;
import org.apache.lucene.search.SearcherManager;
import org.apache.lucene.search.Sort;
import org.apache.lucene.search.SortField;
import org.apache.lucene.search.SortedNumericSortField;
import org.apache.lucene.search.TopDocs;
import org.apache.lucene.store.FSDirectory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.Closeable;
import java.io.File;
import java.io.IOException;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.function.BiFunction;
import java.util.function.BinaryOperator;
/**
* Stores and manages changes occurred in a content store.
*
* Note this entity is part of the content store only when it is set on writable mode (i.e. only when the hosting node
* is a standalone instance or it is a master node).
*
* That means if the hosting node has at least one standalone core or one master node, then the managed content store
* will be set in write mode and therefore a {@link ChangeSet} instance will track all changes applied to it.
*
* In that mode, each change is associated to a given (incremental) version; changes consist of adds/updates and deletes.
* Each time the {@link org.alfresco.solr.tracker.CommitTracker} executes a commit in the main index, all content store
* changes accumulated in the meantime in the current {@link ChangeSet} instance are flushed in an auxiliary Lucene index
* and therefore persisted.
*
* On the other side, when all cores on a given node are configured as slaves, the content store is in read-only mode,
* and there's no need to track any change (i.e. changes are coming from master through the replication procedure).
*
* As a side note: we can have a persistent or transient {@link ChangeSet} instance. The first one is used to manage, as
* the name suggests, in a persistent way the content store changes accumulated during the indexing operations (see the
* description above about its behaviour in master nodes).
*
* The second one is used instead for computing/reducing the "merged" list of changes that we need to communicate to slave nodes:
* as part of the replication mechanism, a slave communicates its last synched content store version; on the master side,
* we need to compute and communicate back all changes that the slave needs to apply since that version in order to be in
* synch with master.
*
* @author Andrea Gazzarini
* @since 1.5
*/
public class ChangeSet implements AutoCloseable
{
private final static Logger LOGGER = LoggerFactory.getLogger(ChangeSet.class);
private final static ChangeSet EMPTY_CHANGESET = new ChangeSet.Builder().empty().build();
/**
* Builds class for creating {@link ChangeSet} instances.
* The builder is here for creating three kind of {@link ChangeSet} instances:
*
* <ul>
* <li>Persistent: used for tracking and persisting content store changes.</li>
* <li>Transient: used for computing the merged list of changes a slave should apply for being in synch with master.</li>
* <li>Empty: an immutable "NullObject" used for denoting an empty {@link ChangeSet} instance.</li>
* </ul>
*
* @see <a href="https://en.wikipedia.org/wiki/Null_object_pattern">Null Object Design Pattern</a>
*/
public static class Builder
{
private String root;
private boolean immutable;
/**
* Adds a content store root folder to this builder.
* This automatically implies we are creating a persistent {@link ChangeSet} instance.
*
* @param root the absolute path of the content store root folder.
* @return this builder, for being used in a fluent mode.
*/
Builder withContentStoreRoot(String root)
{
if (root == null) throw new IllegalArgumentException("Unable to build the Changeset structures with a null content store root folder.");
if (!new File(root).canWrite()) throw new IllegalArgumentException("Unable to build the Changeset structures with a non-writeable content store root folder.");
this.root = root;
return this;
}
/**
* We are interested in building an empty, immutable {@link ChangeSet} instance.
*
* @return this builder, for being used in a fluent mode.
*/
Builder empty()
{
this.root = null;
this.immutable = true;
return this;
}
/**
* Builds the product of this builder (i.e. the {@link ChangeSet} instance).
*
* @return the product of this builder (i.e. the {@link ChangeSet} instance).
*/
ChangeSet build()
{
if (root == null)
{
// Creates a transient changeset (no persistence); mainly used for reducing the changes during replication.
return new ChangeSet(
immutable ? emptySet() : new HashSet<>(),
immutable ? emptySet() : new HashSet<>());
}
IndexWriter writer = null;
try
{
File indexDirectory = new File(root, CHANGESETS_ROOT_FOLDER_NAME);
writer = new IndexWriter(FSDirectory.open(indexDirectory.toPath()), new IndexWriterConfig());
writer.commit();
final SearcherManager searcher = new SearcherManager(writer, null);
LOGGER.info("ContentStore Changeset index has been correctly mounted on {}", indexDirectory.getAbsolutePath());
return new ChangeSet(
searcher,
writer,
immutable ? emptySet() : new HashSet<>(),
immutable ? emptySet() : new HashSet<>());
}
catch (Exception exception)
{
ofNullable(writer).ifPresent(ChangeSet::silentyClose);
throw new IllegalArgumentException("Unable to create a ContentStore ChangeSet data structure. See further details in the stacktrack below.", exception);
}
}
}
private final static String VERSION_FIELD_NAME = "version";
private final static String RVERSION_FIELD_NAME = "rversion";
private final static String ADDS_FIELD_NAME = "adds";
private final static String DELETES_FIELD_NAME = "deletes";
final static String CHANGESETS_ROOT_FOLDER_NAME = "changeSets";
Set<String> deletes;
Set<String> adds;
SearcherManager searcher;
private final IndexWriter writer;
Query selectEverything = new MatchAllDocsQuery();
/**
* Builds a new transient {@link ChangeSet} with the given Lucene facades.
*
* @param deletesContainer the container which will hold the deletes.
* @param addsContainer the container which will hold the adds/updates.
*/
private ChangeSet(
final Set<String> deletesContainer,
final Set<String> addsContainer)
{
this(null, null, deletesContainer, addsContainer);
}
/**
* Builds a new {@link ChangeSet} with the given Lucene facades.
*
* @param searcher the searcher reference (actually a {@link SearcherManager} instance instead of dealing with {@link IndexSearcher} directly.
* @param writer the {@link IndexWriter} instance used for persisting the content store changes.
* @param deletesContainer the container which will hold the deletes.
* @param addsContainer the container which will hold the adds/updates.
*/
private ChangeSet(
final SearcherManager searcher,
final IndexWriter writer,
final Set<String> deletesContainer,
final Set<String> addsContainer)
{
this.searcher = searcher;
this.writer = writer;
this.deletes = deletesContainer;
this.adds = addsContainer;
}
/**
* Records a delete change.
*
* @param path the relative path of the file which has been deleted.
*/
synchronized void delete(String path)
{
adds.remove(path);
deletes.add(path);
LOGGER.debug("ContentStore change recorded: item {} has been deleted.", path);
debugPendingChanges();
}
/**
* Records an add or update change.
*
* @param path the relative path of the file which has been updated or added.
*/
synchronized void addOrReplace(String path)
{
deletes.remove(path);
adds.add(path);
LOGGER.debug("ContentStore change recorded: item {} has been added/updated.", path);
debugPendingChanges();
}
/**
* Flushes all pending collected content store changes.
*
* @throws IOException in case of I/O failure.
*/
void flush() throws IOException
{
// No ops if this is a transient changeset
if (searcher == null || writer == null) {
return;
}
if (adds.isEmpty() && deletes.isEmpty())
{
LOGGER.debug("No changes in contentstore to flush");
return;
}
final long version = System.currentTimeMillis();
LOGGER.debug("About to add a new Changeset entry (version = {}, deletes = {}, adds = {})", version, deletes.size(), adds.size());
final Document document = new Document();
document.add(new NumericDocValuesField(VERSION_FIELD_NAME, version));
document.add(new LongPoint(RVERSION_FIELD_NAME, version));
Set<String> tmpDel;
Set<String> tmpAdd;
synchronized (this) {
tmpDel = deletes;
deletes = new HashSet<>();
tmpAdd = adds;
adds = new HashSet<>();
}
tmpDel.stream()
.map(item -> new StoredField(DELETES_FIELD_NAME, item))
.forEach(document::add);
tmpAdd.stream()
.map(item -> new StoredField(ADDS_FIELD_NAME, item))
.forEach(document::add);
writer.addDocument(document);
writer.commit();
searcher.maybeRefresh();
LOGGER.debug("New Changeset entry have been added (version = {}, deletes = {}, adds = {})", version, deletes.size(), adds.size());
}
/**
* Sanity check for making sure the requestor sent a valid and known content store version.
*
* @param version the content store version (sent by a requestor)
* @return true if the given version is unknown, that is, is not part of the content store version history.
*/
boolean isUnknownVersion(long version)
{
try
{
TopDocs hits = searcher().search(LongPoint.newExactQuery(RVERSION_FIELD_NAME, version), 1);
return hits.totalHits != 1;
}
catch(Exception exception)
{
LOGGER.error("Unable to check the requested version ({}) in the local versioning store. See further details in the stacktrace below.", version, exception);
return true;
}
}
@Override
public void close()
{
ofNullable(writer).ifPresent(ChangeSet::silentyClose);
ofNullable(searcher).ifPresent(ChangeSet::silentyClose);
}
/**
* Returns the last persisted content store version.
*
* @return the last persisted content store version, SolrContentStore#NO_VERSION_AVAILABLE in case the version isn't available.
*/
long getLastCommittedVersion()
{
try
{
TopDocs hits = searcher().search(
selectEverything,
1,
new Sort(new SortedNumericSortField(VERSION_FIELD_NAME, SortField.Type.LONG, true)),
false,
false);
return ofNullable(hits.scoreDocs)
.filter(docs -> docs.length > 0)
.map(docs -> ((FieldDoc) docs[0]).fields)
.filter(fields -> fields.length > 0)
.map(fields -> (Long)fields[0])
.orElse(SolrContentStore.NO_VERSION_AVAILABLE);
}
catch(Exception exception)
{
LOGGER.error("Unable to retrieve the last committed content store changeset version. " +
"As consequence of that a dummy value of " + SolrContentStore.NO_VERSION_AVAILABLE +
" will be returned. See further details in the stacktrack below.",
exception);
return SolrContentStore.NO_VERSION_AVAILABLE;
}
}
/**
* Returns the content store changes (adds / deletes) since the given version (exclusive).
*
* @param version the start offset version (exclusive).
* @return the content store changes (adds / deletes) since the given version (exclusive).
*/
public ChangeSet since(long version)
{
try
{
Query query = LongPoint.newRangeQuery(RVERSION_FIELD_NAME, Math.addExact(version, 1), Long.MAX_VALUE);
TopDocs hits = searcher().search(
query,
100,
new Sort(new SortedNumericSortField(VERSION_FIELD_NAME, SortField.Type.LONG)),
false,
false);
final BiFunction<ChangeSet, ? super Pair<List<String>,List<String>>, ChangeSet> accumulator =
(partial, nth) -> {
final List<String> nthDeletes = nth.getFirst();
final List<String> nthAdds = nth.getSecond();
nthDeletes.forEach(partial::delete);
nthAdds.forEach(partial::addOrReplace);
return partial;
};
final BinaryOperator<ChangeSet> combiner = (c1, c2) -> {
c1.deletes.forEach(c2::delete);
c1.adds.forEach(c2::addOrReplace);
return c1;
};
return stream(hits.scoreDocs)
.map(this::toDoc)
.map(doc ->
new Pair<>(
asList(doc.getValues(DELETES_FIELD_NAME)),
asList(doc.getValues(ADDS_FIELD_NAME))))
.reduce(new ChangeSet.Builder().build(), accumulator, combiner);
}
catch(Exception exception)
{
LOGGER.error("Unable to retrieve the changeset since version {}. " +
"As consequence of that an empty result will be returned. " +
"See further details in the stacktrack below.",
version,
exception);
return EMPTY_CHANGESET;
}
}
private Document toDoc(ScoreDoc hit)
{
try
{
return searcher().doc(hit.doc);
}
catch (IOException e)
{
throw new RuntimeException(e);
}
}
private void debugPendingChanges()
{
if (LOGGER.isDebugEnabled())
{
LOGGER.debug("ContentStore pending deletes: " + deletes);
LOGGER.debug("ContentStore pending adds/updates: " + adds);
}
}
/**
* Silently close (i.e. without any exception re-throwing) the incoming resource.
*
* @param resource the closeable resource.
*/
private static void silentyClose(Closeable resource)
{
try
{
resource.close();
}
catch (Exception exception)
{
LOGGER.error("Unable to properly close the resource instance {}. See further details in the stacktrace below.", resource, exception);
}
}
private IndexSearcher searcher() throws IOException
{
return searcher.acquire();
}
}
@@ -0,0 +1,33 @@
/*
* Copyright (C) 2005-2019 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco 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 3 of the License, or
* (at your option) any later version.
*
* Alfresco 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 Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.solr.content;
/**
* {@link AccessMode} specialisation for those modes that need some kind of initialisation.
*
* @author Andrea Gazzarini
* @since 1.5
*/
public interface InitialisableAccessMode extends AccessMode
{
/**
* Initialises this access mode instance.
*/
void init();
}
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2005-2014 Alfresco Software Limited.
* Copyright (C) 2005-2019 Alfresco Software Limited.
*
* This file is part of Alfresco
*
@@ -18,52 +18,475 @@
*/
package org.alfresco.solr.content;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;
import org.alfresco.repo.content.ContentContext;
import org.alfresco.repo.content.ContentStore;
import org.alfresco.service.cmr.repository.ContentReader;
import org.alfresco.service.cmr.repository.ContentWriter;
import org.alfresco.solr.AlfrescoSolrDataModel;
import org.alfresco.solr.client.NodeMetaData;
import org.alfresco.solr.config.ConfigUtil;
import org.alfresco.solr.handler.AlfrescoReplicationHandler;
import org.apache.commons.io.FileUtils;
import org.apache.lucene.util.BytesRef;
import org.apache.solr.common.SolrInputDocument;
import org.apache.solr.common.util.JavaBinCodec;
import org.apache.solr.core.SolrResourceLoader;
import org.apache.solr.handler.SnapShooter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.Closeable;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.function.Predicate;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;
import static java.util.Collections.emptyList;
import static java.util.Collections.emptyMap;
import static java.util.Collections.singletonList;
import static java.util.Optional.of;
import static java.util.Optional.ofNullable;
import static java.util.stream.Collectors.toList;
import static org.alfresco.solr.content.SolrContentUrlBuilder.FILE_EXTENSION;
import static org.alfresco.solr.content.SolrContentUrlBuilder.logger;
/**
* A content store specific to SOLR's requirements: The URL is generated from a
* set of properties such as:
*
* <ul>
* <li>ACL ID</li>
* <li>DB ID</li>
* <li>Other metadata</li>
* <li>ACL ID</li>
* <li>DB ID</li>
* <li>Other metadata</li>
* </ul>
* The URL, if not known, can be reliably regenerated using the
* {@link SolrContentUrlBuilder}.
*
*
* The URL, if not known, can be reliably regenerated using the {@link SolrContentUrlBuilder}.
* <br/>
*
* Since version 1.5 this class acts as a logical singleton: there will be only one instance per node.
* That reflects exactly what we physically have in the filesystem (i.e. there's only one content store per node).
*
* That unique instance is created at startup in {@link org.alfresco.solr.AlfrescoCoreAdminHandler} and then passed to
* each registered core (see {@link org.alfresco.solr.lifecycle.SolrCoreLoadListener})
*
* The State pattern implemented by means of the {@link AccessMode} interface allows for a given {@link SolrContentStore} instance to act:
*
* <ul>
* <li>in READ/WRITE mode: when <b>at least one core</b> of the hosting node is a master or it is a standalone shard/instance.</li>
* <li>in READ ONLY mode: when <b>all cores</b> of the hosting node are slaves.</li>
* </ul>
*
* The Finite State Machine (FST) provides three possible states: Initial, Read Only, Read/Write.
* The allowed transitions are:
*
* <ul>
* <li>Initial -> ReadOnly: a slave core has been registered, the content store hasn't been yet initialised.</li>
* <li>
* ReadOnly -> Read/Write: this scenario is unusual because we should have on the same instance a mixed set of cores
* (some master/standalone, some slaves). It happens when a first slave core registers and causes the transition
* described in the first point (initial -> read only). Then a master or standalone core registers so we need to
* move from a read only to a complete readable/writable managed content store.
* </li>
* <li> Initial -> Read/Write: a master or standalone core has been registered, and the content store hasn't been yet initialised.</li>
* </ul>
*
* Note the following transitions are not allowed:
*
* <ul>
* <li>Coming back to Initial state: once it has been initialised the Content Store cannot return back to the "Initial" state.</li>
* <li>
* Read/Write -> ReadOnly: if at least one master or standalone core registers, the content store is permanently moved in Read/Write mode.
* So even a further slave node registers (not very usually as described above) the content store remains in RW mode.
* </li>
* </ul>
*
* @author Derek Hulley
* @author Michael Suzuki
* @since 5.0
* @author Andrea Gazzarini
* @since 1.5
* @see org.alfresco.solr.lifecycle.SolrCoreLoadListener
* @see <a href="https://it.wikipedia.org/wiki/State_pattern">State Pattern</a>
*/
public class SolrContentStore implements ContentStore
public final class SolrContentStore implements Closeable, AccessMode
{
protected final static Logger log = LoggerFactory.getLogger(SolrContentStore.class);
private final static Logger LOGGER = LoggerFactory.getLogger(SolrContentStore.class);
public static final long NO_VERSION_AVAILABLE = -1L;
public static final long NO_CONTENT_STORE_REPLICATION_REQUIRED = -2L;
static final String CONTENT_STORE = "contentstore";
static final String SOLR_CONTENT_DIR = "solr.content.dir";
public static final String INFO = "info";
public static final String FULL_REPLICATION = "full-replication";
public static final String DELETES = "deletes";
public static final String ADDS = "adds";
private final Predicate<File> onlyDatafiles = file -> file.isFile() && file.getName().endsWith(FILE_EXTENSION);
private final String root;
/**
* Constructor.
* @param solrHome
* Used for denoting the very beginning state, when the {@link SolrContentStore} has been created
* but we don't know (yet) which is the role played by the cores that will register on the hosting Solr node.
*/
AccessMode notYetSet = new AccessMode()
{
@Override
public long getLastCommittedVersion()
{
throw new IllegalStateException("ContentStore hasn't been properly initialised.");
}
@Override
public void setLastCommittedVersion(long version)
{
throw new IllegalStateException("ContentStore hasn't been properly initialised.");
}
@Override
public Map<String, List<Map<String, Object>>> getChanges(long version)
{
throw new IllegalStateException("ContentStore hasn't been properly initialised.");
}
@Override
public void storeDocOnSolrContentStore(String tenant, long dbId, SolrInputDocument doc)
{
throw new IllegalStateException("ContentStore hasn't been properly initialised.");
}
@Override
public void storeDocOnSolrContentStore(NodeMetaData nodeMetaData, SolrInputDocument doc)
{
throw new IllegalStateException("ContentStore hasn't been properly initialised.");
}
@Override
public void removeDocFromContentStore(NodeMetaData nodeMetaData)
{
throw new IllegalStateException("ContentStore hasn't been properly initialised.");
}
@Override
public void flushChangeSet()
{
throw new IllegalStateException("ContentStore hasn't been properly initialised.");
}
@Override
public void switchOnReadOnlyMode()
{
logger.info("Switching the content store to ReadOnly mode.");
currentAccessMode = readOnly;
}
@Override
public void switchOnReadWriteMode()
{
logger.info("Switching the content store to Read/Write mode.");
readWrite.init();
currentAccessMode = readWrite;
}
@Override
public void close()
{
// Nothing to close here
}
};
final InitialisableAccessMode readOnly = new InitialisableAccessMode()
{
@Override
public void init()
{
// Nothing to be done here...
}
@Override
public void switchOnReadWriteMode()
{
logger.info("Switching from ReadOnly to Read/Write Content Store.");
readWrite.init();
currentAccessMode = readWrite;
logger.info("Switching from ReadOnly to Read/Write Content Store.");
}
@Override
public void switchOnReadOnlyMode()
{
logger.info("The content store is already in ReadOnly mode so this call won't have any effect.");
}
@Override
public long getLastCommittedVersion()
{
try
{
return Files.lines(Paths.get(root, ".version"))
.map(Long::parseLong)
.findFirst()
.orElse(NO_VERSION_AVAILABLE);
}
catch (Exception e)
{
return NO_VERSION_AVAILABLE;
}
}
@Override
public void setLastCommittedVersion(long version)
{
try
{
File tmpFile = new File(root, ".version-" + new SimpleDateFormat(SnapShooter.DATE_FMT, Locale.ROOT).format(new Date()));
FileWriter wr = new FileWriter(tmpFile);
wr.write(Long.toString(version));
wr.close();
tmpFile.renameTo(new File(root, ".version"));
}
catch (IOException exception)
{
logger.error("Unable to persist the last committed content store version {}. See the stacktrace below for furtger details.", version, exception);
}
}
@Override
public Map<String, List<Map<String, Object>>> getChanges(long version)
{
logger.warn("NoOp SolrContentStore changes call on slave side: this shouldn't happen because the ContentStore is in read-only mode when the hosting node is a slave.");
return emptyMap();
}
@Override
public void storeDocOnSolrContentStore(String tenant, long dbId, SolrInputDocument doc)
{
logger.warn("NoOp SolrContentStore write call on slave side: this shouldn't happen because the ContentStore is in read-only mode when the hosting node is a slave.");
}
@Override
public void storeDocOnSolrContentStore(NodeMetaData nodeMetaData, SolrInputDocument doc)
{
logger.warn("NoOp SolrContentStore write call on slave side: this shouldn't happen because the ContentStore is in read-only mode when the hosting node is a slave.");
}
@Override
public void removeDocFromContentStore(NodeMetaData nodeMetaData)
{
logger.warn("NoOp SolrContentStore write call on slave side: this shouldn't happen because the ContentStore is in read-only mode when the hosting node is a slave.");
}
@Override
public void flushChangeSet()
{
logger.warn("NoOp ChangeSet tracking call on slave side: this shouldn't happen because the ContentStore is in read-only mode when the hosting node is a slave.");
}
@Override
public void close()
{
// There's nothing to close on slave side
}
};
final InitialisableAccessMode readWrite = new InitialisableAccessMode()
{
private ChangeSet changeSet;
@Override
public void init()
{
changeSet = ofNullable(changeSet).orElseGet(() -> new ChangeSet.Builder().withContentStoreRoot(root).build());
}
@Override
public long getLastCommittedVersion()
{
return changeSet.getLastCommittedVersion();
}
@Override
public void setLastCommittedVersion(long version)
{
// Do nothing here, as we are on master-side
}
@Override
public Map<String, List<Map<String, Object>>> getChanges(long version)
{
// The slave doesn't have a version, we are listing the whole content store
if (version <= NO_VERSION_AVAILABLE || changeSet.isUnknownVersion(version))
{
String message = "A slave requested the content store synchronization " +
((version <= NO_VERSION_AVAILABLE)
? " without providing any local version (actually {})."
: " with an invalid/unknown local version number ({}).") +
"As consequence of that this master will list the whole content store because a full replication is needed.";
logger.info(message, version);
return Map.of(
INFO, singletonList(Map.of(FULL_REPLICATION, true)),
ADDS, fullContentStore(),
DELETES, emptyList());
}
ChangeSet changes = changeSet.since(version);
return Map.of(
INFO, singletonList(Map.of(FULL_REPLICATION, false)),
DELETES,
changes.deletes.stream()
.map(path -> Map.<String, Object>of("name", path))
.collect(toList()),
ADDS,
changes.adds.stream()
.map(relativePath -> root + relativePath)
.map(File::new)
.map(file -> new AlfrescoReplicationHandler.FileInfo(file, file.getAbsolutePath().replace(root, "")))
.map(AlfrescoReplicationHandler.FileInfo::getAsMap)
.collect(toList()));
}
@Override
public void storeDocOnSolrContentStore(String tenant, long dbId, SolrInputDocument doc)
{
ContentContext contentContext =
of(SolrContentUrlBuilder
.start()
.add(SolrContentUrlBuilder.KEY_TENANT, tenant)
.add(SolrContentUrlBuilder.KEY_DB_ID, String.valueOf(dbId)))
.map(SolrContentUrlBuilder::getContentContext)
.orElseThrow(() -> new IllegalArgumentException("Unable to build a Content Context from tenant " + tenant + " and DBID " + dbId));
this.delete(contentContext.getContentUrl());
ContentWriter writer = this.getWriter(contentContext);
LOGGER.debug("Writing {}/{} to {}", tenant, dbId, contentContext.getContentUrl());
try (OutputStream contentOutputStream = writer.getContentOutputStream();
GZIPOutputStream gzip = new GZIPOutputStream(contentOutputStream))
{
JavaBinCodec codec = new JavaBinCodec(resolver);
codec.marshal(doc, gzip);
File file = getFileFromUrl(contentContext.getContentUrl());
changeSet.addOrReplace(relativePath(file));
}
catch (Exception exception)
{
LOGGER.warn("Unable to write to Content Store using URL: {}", contentContext.getContentUrl(), exception);
}
}
@Override
public void storeDocOnSolrContentStore(NodeMetaData nodeMetaData, SolrInputDocument doc) {
String fixedTenantDomain = AlfrescoSolrDataModel.getTenantId(nodeMetaData.getTenantDomain());
storeDocOnSolrContentStore(fixedTenantDomain, nodeMetaData.getId(), doc);
}
@Override
public void removeDocFromContentStore(NodeMetaData nodeMetaData)
{
String fixedTenantDomain = AlfrescoSolrDataModel.getTenantId(nodeMetaData.getTenantDomain());
String contentUrl = SolrContentUrlBuilder
.start()
.add(SolrContentUrlBuilder.KEY_TENANT, fixedTenantDomain)
.add(SolrContentUrlBuilder.KEY_DB_ID, String.valueOf(nodeMetaData.getId()))
.getContentContext()
.getContentUrl();
delete(contentUrl);
}
@Override
public void flushChangeSet() throws IOException
{
changeSet.flush();
}
@Override
public void switchOnReadWriteMode()
{
logger.debug("The content store is already in ReadWrite mode; as consequence of that, the incoming \"SET-TO-RW-MODE\" call won't have any effect.");
}
@Override
public void switchOnReadOnlyMode()
{
logger.debug("A writable content store cannot switch in ReadOnly mode. This could happen in an edge case where" +
" on the same Solr node we have masters and slaves nodes");
}
@Override
public void close()
{
changeSet.close();
}
private List<Map<String, Object>> fullContentStore()
{
try
{
return Files.walk(Paths.get(root))
.map(Path::toFile)
.filter(onlyDatafiles)
.map(file -> new AlfrescoReplicationHandler.FileInfo(file, file.getAbsolutePath().replace(root, "")))
.map(AlfrescoReplicationHandler.FileInfo::getAsMap)
.collect(toList());
}
catch (Exception e)
{
LOGGER.error("An exception occurred while retrieving the whole ContentStore filelist. " +
"As consequence of that an empty list will be returned (i.e. no ContentStore synch will happen).");
return emptyList();
}
}
private void delete(String contentUrl)
{
File file = getFileFromUrl(contentUrl);
if (file.delete()) changeSet.delete(relativePath(file));
}
private ContentWriter getWriter(ContentContext context)
{
String url = context.getContentUrl();
File file = getFileFromUrl(url);
return new SolrFileContentWriter(file, url);
}
};
AccessMode currentAccessMode = notYetSet;
private final JavaBinCodec.ObjectResolver resolver = (o, codec) -> {
if(o instanceof BytesRef)
{
BytesRef br = (BytesRef)o;
codec.writeByteArray(br.bytes,br.offset,br.length);
return null;
}
return o;
};
/**
* Builds a new {@link SolrContentStore} instance with the given SOLR HOME.
*
* @param solrHome the Solr HOME.
*/
public SolrContentStore(String solrHome)
{
@@ -77,122 +500,92 @@ public class SolrContentStore implements ContentStore
{
//Its very unlikely that solrHome would not exist so we will log an error
//but continue because solr.content.dir may be specified, so it keeps working
log.error(solrHomeFile.getAbsolutePath() + " does not exist.");
LOGGER.error(solrHomeFile.getAbsolutePath() + " does not exist.");
}
String path = solrHomeFile.getParent()+"/"+CONTENT_STORE;
log.warn(path + " will be used as a default path if " + SOLR_CONTENT_DIR + " property is not defined");
String path = solrHomeFile.getParent() + "/" + CONTENT_STORE;
LOGGER.warn(path + " will be used as a default path if " + SOLR_CONTENT_DIR + " property is not defined");
File rootFile = new File(ConfigUtil.locateProperty(SOLR_CONTENT_DIR, path));
try
{
FileUtils.forceMkdir(rootFile);
}
catch (Exception e)
{
}
catch (Exception e) {
throw new RuntimeException("Failed to create directory for content store: " + rootFile, e);
}
this.root = rootFile.getAbsolutePath();
}
// write a BytesRef as a byte array
private JavaBinCodec.ObjectResolver resolver = new JavaBinCodec.ObjectResolver()
/**
* Returns the content store changes since the given (requestor) version.
*
* @param version the requestor version.
* @return the content store changes since the given (requestor) version.
*/
@Override
public Map<String, List<Map<String, Object>>> getChanges(long version)
{
@Override public Object resolve(Object o,JavaBinCodec codec)throws IOException
{
if(o instanceof BytesRef)
{
BytesRef br=(BytesRef)o;
codec.writeByteArray(br.bytes,br.offset,br.length);
return null;
}
return o;
}
};
return currentAccessMode.getChanges(version);
}
/**
* Retrieve document from SolrContentStore.
*
* @param tenant identifier
* @param dbId identifier
* @return {@link SolrInputDocument} searched document
* @throws IOException if error
*/
public SolrInputDocument retrieveDocFromSolrContentStore(String tenant, long dbId) throws IOException
public SolrInputDocument retrieveDocFromSolrContentStore(String tenant, long dbId)
{
String contentUrl = SolrContentUrlBuilder.start().add(SolrContentUrlBuilder.KEY_TENANT, tenant)
.add(SolrContentUrlBuilder.KEY_DB_ID, String.valueOf(dbId)).get();
String contentUrl =
SolrContentUrlBuilder.start()
.add(SolrContentUrlBuilder.KEY_TENANT, tenant)
.add(SolrContentUrlBuilder.KEY_DB_ID, String.valueOf(dbId))
.get();
ContentReader reader = this.getReader(contentUrl);
SolrInputDocument cachedDoc = null;
if (reader.exists())
if (!reader.exists())
{
// try-with-resources statement closes all these InputStreams
try (InputStream contentInputStream = reader.getContentInputStream();
// Uncompresses the document
GZIPInputStream gzip = new GZIPInputStream(contentInputStream);)
{
cachedDoc = (SolrInputDocument) new JavaBinCodec(resolver).unmarshal(gzip);
} catch (Exception e)
{
// Don't fail for this
log.warn("Failed to get doc from store using URL: " + contentUrl, e);
return null;
}
return null;
}
try (InputStream contentInputStream = reader.getContentInputStream();
InputStream gzip = new GZIPInputStream(contentInputStream))
{
return (SolrInputDocument) new JavaBinCodec(resolver).unmarshal(gzip);
}
catch (Exception exception)
{
// Don't fail for this
LOGGER.warn("Failed to get doc from store using URL: " + contentUrl, exception);
return null;
}
return cachedDoc;
}
private final String root;
@Override
public boolean isContentUrlSupported(String contentUrl)
public long getLastCommittedVersion()
{
return (contentUrl != null && contentUrl.startsWith(SolrContentUrlBuilder.SOLR_PROTOCOL_PREFIX));
return currentAccessMode.getLastCommittedVersion();
}
/**
* @return <tt>true</tt> always
*/
@Override
public boolean isWriteSupported()
public void setLastCommittedVersion(long version)
{
return true;
currentAccessMode.setLastCommittedVersion(version);
}
/**
* @return -1 always
* Returns the absolute path of the content store root folder.
*
* @return the absolute path of the content store root folder.
*/
@Override
public long getSpaceFree()
{
return -1L;
}
/**
* @return -1 always
*/
@Override
public long getSpaceTotal()
{
return -1L;
}
@Override
public String getRootLocation()
{
return root;
}
/**
* Convert a content URL into a File, whether it exists or not
*/
private File getFileFromUrl(String contentUrl)
{
String path = contentUrl.replace(SolrContentUrlBuilder.SOLR_PROTOCOL_PREFIX, root + "/");
return new File(path);
}
@Override
public boolean exists(String contentUrl)
{
File file = getFileFromUrl(contentUrl);
@@ -200,93 +593,101 @@ public class SolrContentStore implements ContentStore
}
@Override
public ContentReader getReader(String contentUrl)
public void storeDocOnSolrContentStore(String tenant, long dbId, SolrInputDocument doc)
{
currentAccessMode.storeDocOnSolrContentStore(tenant, dbId, doc);
}
/**
* Store {@link SolrInputDocument} in to Alfresco solr content store.
*
* @param nodeMetaData the incoming node metadata.
* @param doc the document itself.
*/
@Override
public void storeDocOnSolrContentStore(NodeMetaData nodeMetaData, SolrInputDocument doc)
{
currentAccessMode.storeDocOnSolrContentStore(nodeMetaData, doc);
}
/**
* Removes {@link SolrInputDocument} from Alfresco solr content store.
*
* @param nodeMetaData the incoming node metadata.
*/
@Override
public void removeDocFromContentStore(NodeMetaData nodeMetaData)
{
currentAccessMode.removeDocFromContentStore(nodeMetaData);
}
@Override
public void flushChangeSet() throws IOException
{
currentAccessMode.flushChangeSet();
}
@Override
public void close() throws IOException
{
currentAccessMode.close();
}
@Override
public void switchOnReadWriteMode()
{
currentAccessMode = readWrite;
}
@Override
public void switchOnReadOnlyMode()
{
currentAccessMode = readOnly;
}
/**
* Assuming the input file belongs to the content store, it returns the corresponding relative path.
*
* @param file the content store file.
* @return the relative file path.
*/
private String relativePath(File file)
{
return file.getAbsolutePath().replace(root, "");
}
/**
* Convert a content URL into a File, whether it exists or not
*/
private File getFileFromUrl(String contentUrl)
{
return new File(contentUrl.replace(SolrContentUrlBuilder.SOLR_PROTOCOL_PREFIX, root + "/"));
}
private ContentReader getReader(String contentUrl)
{
File file = getFileFromUrl(contentUrl);
return new SolrFileContentReader(file, contentUrl);
}
@Override
public ContentWriter getWriter(ContentContext context)
{
// Ensure that there is a context and that it has a URL
if (context == null || context.getContentUrl() == null)
{
throw new IllegalArgumentException("Retrieve a writer with a URL-providing ContentContext.");
}
String url = context.getContentUrl();
File file = getFileFromUrl(url);
SolrFileContentWriter writer = new SolrFileContentWriter(file, url);
// Done
return writer;
}
@Override
public boolean delete(String contentUrl)
{
File file = getFileFromUrl(contentUrl);
return file.delete();
}
/**
* Stores a {@link SolrInputDocument} into Alfresco solr content store.
* @param tenant
* @param dbId
* @param doc
* @throws IOException
* Enables/disables the content store access mode.
* The term "toggles" is just for indicating that this method will be called several times (one for each registered
* core). The underlying FSM makes sure this call will be idempotent so in case of read/write content store the
* data structure used for maintaining the content store versioning will be initialised only once, even if this
* method is called repeatedly.
*
* @param enableReadOnlyMode a flag indicating if the requesting core requires a readOnly (true) or readWrite (false) content store.
*/
public void storeDocOnSolrContentStore(String tenant, long dbId, SolrInputDocument doc) throws IOException
public synchronized void toggleReadOnlyMode(boolean enableReadOnlyMode)
{
ContentContext contentContext = SolrContentUrlBuilder
.start()
.add(SolrContentUrlBuilder.KEY_TENANT, tenant)
.add(SolrContentUrlBuilder.KEY_DB_ID, String.valueOf(dbId))
.getContentContext();
this.delete(contentContext.getContentUrl());
ContentWriter writer = this.getWriter(contentContext);
if (log.isDebugEnabled())
if (enableReadOnlyMode)
{
log.debug("Writing doc to " + contentContext.getContentUrl());
currentAccessMode.switchOnReadOnlyMode();
}
try (
OutputStream contentOutputStream = writer.getContentOutputStream();
// Compresses the document
GZIPOutputStream gzip = new GZIPOutputStream(contentOutputStream);
)
else
{
JavaBinCodec codec = new JavaBinCodec(resolver);
codec.marshal(doc, gzip);
}
catch (Exception e)
{
// A failure to write to the store is acceptable as long as it's logged
log.warn("Failed to write to store using URL: " + contentContext.getContentUrl(), e);
currentAccessMode.switchOnReadWriteMode();
}
}
/**
* Store {@link SolrInputDocument} in to Alfresco solr content store.
* @param nodeMetaData identifier
* @param doc to store
* @throws IOException if error
*/
public void storeDocOnSolrContentStore(NodeMetaData nodeMetaData, SolrInputDocument doc) throws IOException
{
String fixedTenantDomain = AlfrescoSolrDataModel.getTenantId(nodeMetaData.getTenantDomain());
storeDocOnSolrContentStore(fixedTenantDomain, nodeMetaData.getId(), doc);
}
/**
* Removes {@link SolrInputDocument} from Alfresco solr content store.
* @param nodeMetaData
*/
public void removeDocFromContentStore(NodeMetaData nodeMetaData)
{
String fixedTenantDomain = AlfrescoSolrDataModel.getTenantId(nodeMetaData.getTenantDomain());
String contentUrl = SolrContentUrlBuilder
.start()
.add(SolrContentUrlBuilder.KEY_TENANT, fixedTenantDomain)
.add(SolrContentUrlBuilder.KEY_DB_ID, String.valueOf(nodeMetaData.getId()))
.getContentContext()
.getContentUrl();
this.delete(contentUrl);
}
}
}
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2005-2014 Alfresco Software Limited.
* Copyright (C) 2005-2019 Alfresco Software Limited.
*
* This file is part of Alfresco
*
@@ -18,7 +18,7 @@
*/
package org.alfresco.solr.content;
import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.TreeMap;
import java.util.zip.CRC32;
@@ -43,20 +43,13 @@ import org.slf4j.LoggerFactory;
*/
public class SolrContentUrlBuilder
{
/**
* <b>solr</b> is the prefix for SOLR content URLs
* @see #isContentUrlSupported(String)
*/
public static final String SOLR_PROTOCOL = "solr";
public static final String SOLR_PROTOCOL_PREFIX = SOLR_PROTOCOL + ContentStore.PROTOCOL_DELIMITER;
public static final String FILE_EXTENSION = ".gz";
private static final String SOLR_PROTOCOL = "solr";
static final String SOLR_PROTOCOL_PREFIX = SOLR_PROTOCOL + ContentStore.PROTOCOL_DELIMITER;
static final String FILE_EXTENSION = ".gz";
/** The key for the tenant name */
public static final String KEY_TENANT = "tenant";
/** The key for the DB ID */
public static final String KEY_DB_ID = "dbId";
/** The key for the ACL ID */
public static final String KEY_ACL_ID = "aclId";
static final String KEY_TENANT = "tenant";
static final String KEY_DB_ID = "dbId";
static final String KEY_ACL_ID = "aclId";
protected final static Logger logger = LoggerFactory.getLogger(SolrContentUrlBuilder.class);
@@ -66,9 +59,9 @@ public class SolrContentUrlBuilder
/**
* Protected constructor used by {@link SolrContentUrlBuilder#start()}
*/
protected SolrContentUrlBuilder()
private SolrContentUrlBuilder()
{
this.metadata = new TreeMap<String, String>();
this.metadata = new TreeMap<>();
}
/**
@@ -83,32 +76,34 @@ public class SolrContentUrlBuilder
/**
* Add some metadata to the URL generator. The order in which metadata is added is irrelevant.
* <p/>
* Note that there are specific keys that are commonly used and, if provided, may not be null or empty.
*
* <ul>
* <li><b>{@link #KEY_TENANT}:</b> The name of the tenant or 'default' if missing.</li>
* <li><b>{@link #KEY_DB_ID}:</b> The database ID.</li>
* <li><b>{@link #KEY_ACL_ID}:</b> The ACL ID.</li>
* </ul>
*
* @param key an arbitrary metadata key (never <tt>null</tt>>
* @param value some metadata value (<tt>null</tt> is supported)
* @return this builder for more building
* @param key an arbitrary metadata key (never <tt>null</tt>>
* @param value some metadata value (<tt>null</tt> is supported)
* @return this builder for more building
*
* @throws IllegalArgumentException if the key is null
* @throws IllegalStateException if the key has been used already
* @throws IllegalArgumentException if the key is null
* @throws IllegalStateException if the key has been used already
*/
public synchronized SolrContentUrlBuilder add(String key, String value)
public SolrContentUrlBuilder add(String key, String value)
{
if (key == null)
{
throw new IllegalArgumentException("The metadata 'key' may not be null.");
}
String previous = metadata.put(key, value);
if (previous != null)
{
throw new IllegalStateException("The metadata key, '" + key + "', has already been used.");
}
// Check well-known keys
if (key.equals(KEY_TENANT) || key.equals(KEY_DB_ID) || key.equals(KEY_ACL_ID))
{
@@ -131,8 +126,8 @@ public class SolrContentUrlBuilder
/**
* Get the final content URL using the {@link #add(String, String) supplied metadata}.
*
* @return the SOLR content URL
* @throws IllegalStateException if no metadata has been added
* @return the SOLR content URL
* @throws IllegalStateException if no metadata has been added
*/
public synchronized String get()
{
@@ -169,21 +164,13 @@ public class SolrContentUrlBuilder
sb.append("misc/");
// Calculate the CRC
CRC32 crc = new CRC32();
try
for (Map.Entry<String, String> entry : metadata.entrySet())
{
for (Map.Entry<String, String> entry : metadata.entrySet())
{
// This is ordered, so just add each entry as "key = value".
// DO NOT USE entry.toString() because the format is not a contract
// and we have to have the same string for the same metadata
String entryStr = entry.getKey() + "=" + entry.getValue() + "; ";
crc.update(entryStr.getBytes("UTF-8"));
}
}
catch (UnsupportedEncodingException e)
{
// Yeah, right.
throw new RuntimeException("UTF-8 is not supported.", e);
// This is ordered, so just add each entry as "key = value".
// DO NOT USE entry.toString() because the format is not a contract
// and we have to have the same string for the same metadata
String entryStr = entry.getKey() + "=" + entry.getValue() + "; ";
crc.update(entryStr.getBytes(StandardCharsets.UTF_8));
}
numSb.append(crc.getValue());
}
@@ -218,7 +205,7 @@ public class SolrContentUrlBuilder
/**
* Helper method to retrieve a {@link ContentContext} constructed using the final {@link #get()} url.
*/
public ContentContext getContentContext()
ContentContext getContentContext()
{
String url = get();
return new ContentContext(null, url);
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2005-2014 Alfresco Software Limited.
* Copyright (C) 2005-2019 Alfresco Software Limited.
*
* This file is part of Alfresco
*
@@ -42,7 +42,7 @@ import org.springframework.util.FileCopyUtils;
* @author Derek Hulley
* @since 5.0
*/
public class SolrFileContentReader implements ContentReader
class SolrFileContentReader implements ContentReader
{
private final File file;
private final String contentUrl;
@@ -51,7 +51,7 @@ public class SolrFileContentReader implements ContentReader
* @param file the file to write to
* @param contentUrl the content URL for information purposes
*/
protected SolrFileContentReader(File file, String contentUrl)
SolrFileContentReader(File file, String contentUrl)
{
this.file = file;
this.contentUrl = contentUrl;
@@ -120,9 +120,8 @@ public class SolrFileContentReader implements ContentReader
}
try
{
InputStream is = new BufferedInputStream(new FileInputStream(file));
// done
return is;
return new BufferedInputStream(new FileInputStream(file));
}
catch (Throwable e)
{
@@ -192,12 +191,9 @@ public class SolrFileContentReader implements ContentReader
ByteArrayOutputStream os = new ByteArrayOutputStream();
FileCopyUtils.copy(is, os); // both streams are closed
byte[] bytes = os.toByteArray();
// get the encoding for the string
String encoding = "UTF-8";
// create the string from the byte[] using encoding if necessary
String content = (encoding == null) ? new String(bytes) : new String(bytes, encoding);
// done
return content;
return new String(bytes, encoding);
}
catch (IOException e)
{
@@ -266,4 +262,4 @@ public class SolrFileContentReader implements ContentReader
{
throw new UnsupportedOperationException("Auto-created method not implemented.");
}
}
}
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2005-2014 Alfresco Software Limited.
* Copyright (C) 2005-2019 Alfresco Software Limited.
*
* This file is part of Alfresco
*
@@ -41,7 +41,7 @@ import org.apache.commons.io.FileUtils;
* @author Derek Hulley
* @since 5.0
*/
public class SolrFileContentWriter implements ContentWriter
class SolrFileContentWriter implements ContentWriter
{
private final File file;
private final String contentUrl;
@@ -51,11 +51,10 @@ public class SolrFileContentWriter implements ContentWriter
* @param file the file to write to
* @param contentUrl the content URL for information purposes
*/
protected SolrFileContentWriter(File file, String contentUrl)
SolrFileContentWriter(File file, String contentUrl)
{
this.file = file;
this.contentUrl = contentUrl;
this.written = false;
}
@Override
@@ -109,7 +108,7 @@ public class SolrFileContentWriter implements ContentWriter
@Override
public synchronized OutputStream getContentOutputStream() throws ContentIOException
{
if (written == true)
if (written)
{
throw new IllegalStateException("The writer has already been used: " + file);
}
@@ -117,6 +116,7 @@ public class SolrFileContentWriter implements ContentWriter
{
throw new IllegalStateException("The file already exists: " + file);
}
try
{
OutputStream is = new BufferedOutputStream(FileUtils.openOutputStream(file));
@@ -139,7 +139,7 @@ public class SolrFileContentWriter implements ContentWriter
@Override
public synchronized void putContent(InputStream is) throws ContentIOException
{
if (written == true)
if (written)
{
throw new IllegalStateException("The writer has already been used: " + file);
}
@@ -147,6 +147,7 @@ public class SolrFileContentWriter implements ContentWriter
{
throw new IllegalStateException("The file already exists: " + file);
}
try
{
FileUtils.copyInputStreamToFile(is, file);
@@ -161,7 +162,7 @@ public class SolrFileContentWriter implements ContentWriter
@Override
public synchronized void putContent(File sourceFile) throws ContentIOException
{
if (written == true)
if (written)
{
throw new IllegalStateException("The writer has already been used: " + this.file);
}
@@ -173,6 +174,7 @@ public class SolrFileContentWriter implements ContentWriter
{
throw new IllegalStateException("The source file does not exist: " + sourceFile);
}
try
{
FileUtils.copyFile(sourceFile, this.file, false);
@@ -0,0 +1,24 @@
/*
* Copyright (C) 2005-2019 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco 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 3 of the License, or
* (at your option) any later version.
*
* Alfresco 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 Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* The package contains all components that manage the Solr ContentStore.
* Note that in SearchServices 2.0 the whole package will be deprecated/removed because the content store will be
* replaced by the built-in Solr storage capability (i.e. stored fields).
*/
package org.alfresco.solr.content;
@@ -0,0 +1,102 @@
/*
* 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.
*
*
* Modification copyright (C) 2005-2019 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco 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 3 of the License, or
* (at your option) any later version.
*
* Alfresco 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 Alfresco. If not, see <http://www.gnu.org/licenses/>
*
*/
package org.alfresco.solr.handler;
import org.apache.solr.handler.SnapShooter;
import java.net.URI;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import java.util.Objects;
import java.util.Optional;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
class OldBackupDirectory implements Comparable<OldBackupDirectory>
{
private static final Pattern dirNamePattern = Pattern.compile("^snapshot[.](.*)$");
private URI basePath;
private String dirName;
private Optional<Date> timestamp = Optional.empty();
OldBackupDirectory(URI basePath, String dirName)
{
this.dirName = Objects.requireNonNull(dirName);
this.basePath = Objects.requireNonNull(basePath);
Matcher m = dirNamePattern.matcher(dirName);
if (m.find())
{
try
{
this.timestamp = Optional.of(new SimpleDateFormat(SnapShooter.DATE_FMT, Locale.ROOT).parse(m.group(1)));
}
catch (ParseException e)
{
this.timestamp = Optional.empty();
}
}
}
public URI getPath()
{
return this.basePath.resolve(dirName);
}
String getDirName()
{
return dirName;
}
public Optional<Date> getTimestamp()
{
return timestamp;
}
@Override
public int compareTo(OldBackupDirectory that)
{
if (this.timestamp.isPresent() && that.timestamp.isPresent())
{
return that.timestamp.get().compareTo(this.timestamp.get());
}
// Use absolute value of path in case the time-stamp is missing on either side.
return that.getPath().compareTo(this.getPath());
}
}
@@ -0,0 +1,377 @@
/*
* 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.
*
*
* Modification copyright (C) 2005-2019 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco 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 3 of the License, or
* (at your option) any later version.
*
* Alfresco 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 Alfresco. If not, see <http://www.gnu.org/licenses/>
*
*/
package org.alfresco.solr.handler;
import org.apache.lucene.index.IndexCommit;
import org.apache.lucene.store.Directory;
import org.apache.solr.common.SolrException;
import org.apache.solr.common.SolrException.ErrorCode;
import org.apache.solr.common.util.NamedList;
import org.apache.solr.core.DirectoryFactory.DirContext;
import org.apache.solr.core.IndexDeletionPolicyWrapper;
import org.apache.solr.core.SolrCore;
import org.apache.solr.core.backup.repository.BackupRepository;
import org.apache.solr.core.backup.repository.BackupRepository.PathType;
import org.apache.solr.core.backup.repository.LocalFileSystemRepository;
import org.apache.solr.core.snapshots.SolrSnapshotMetaDataManager;
import org.apache.solr.search.SolrIndexSearcher;
import org.apache.solr.util.RefCounted;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.lang.invoke.MethodHandles;
import java.net.URI;
import java.nio.file.Paths;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Consumer;
/**
* <p> Provides functionality equivalent to the snapshooter script </p>
* This is no longer used in standard replication.
*
* @since solr 1.4
*/
public class SnapShooter
{
private static final Logger LOG = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
private SolrCore solrCore;
private String snapshotName = null;
private String directoryName = null;
private URI baseSnapDirPath = null;
private URI snapshotDirPath = null;
private BackupRepository backupRepo = null;
private String commitName; // can be null
@Deprecated
public SnapShooter(SolrCore core, String location, String snapshotName)
{
String snapDirStr;
// Note - This logic is only applicable to the usecase where a shared file-system is exposed via
// local file-system interface (primarily for backwards compatibility). For other use-cases, users
// will be required to specify "location" where the backup should be stored.
if (location == null)
{
snapDirStr = core.getDataDir();
}
else
{
snapDirStr = core.getCoreDescriptor().getInstanceDir().resolve(location).normalize().toString();
}
initialize(new LocalFileSystemRepository(), core, Paths.get(snapDirStr).toUri(), snapshotName, null);
}
SnapShooter(BackupRepository backupRepo, SolrCore core, URI location, String snapshotName, String commitName)
{
initialize(backupRepo, core, location, snapshotName, commitName);
}
private void initialize(BackupRepository backupRepo, SolrCore core, URI location, String snapshotName, String commitName)
{
this.solrCore = Objects.requireNonNull(core);
this.backupRepo = Objects.requireNonNull(backupRepo);
this.baseSnapDirPath = location;
this.snapshotName = snapshotName;
if (snapshotName != null)
{
directoryName = "snapshot." + snapshotName;
}
else
{
SimpleDateFormat fmt = new SimpleDateFormat(DATE_FMT, Locale.ROOT);
directoryName = "snapshot." + fmt.format(new Date());
}
this.snapshotDirPath = backupRepo.resolve(location, directoryName);
this.commitName = commitName;
}
public BackupRepository getBackupRepository()
{
return backupRepo;
}
/**
* Gets the parent directory of the snapshots. This is the {@code location}
* given in the constructor.
*/
public URI getLocation()
{
return this.baseSnapDirPath;
}
void validateDeleteSnapshot()
{
Objects.requireNonNull(this.snapshotName);
boolean dirFound = false;
String[] paths;
try
{
paths = backupRepo.listAll(baseSnapDirPath);
for (String path : paths)
{
if (path.equals(this.directoryName)
&& backupRepo.getPathType(baseSnapDirPath.resolve(path)) == PathType.DIRECTORY)
{
dirFound = true;
break;
}
}
if (!dirFound)
{
throw new SolrException(SolrException.ErrorCode.BAD_REQUEST,
"Snapshot " + snapshotName + " cannot be found in directory: " + baseSnapDirPath);
}
}
catch (IOException e)
{
throw new SolrException(SolrException.ErrorCode.SERVER_ERROR,
"Unable to find snapshot " + snapshotName + " in directory: " + baseSnapDirPath, e);
}
}
void deleteSnapAsync(final AlfrescoReplicationHandler alfrescoReplicationHandler)
{
new Thread(() -> deleteNamedSnapshot(alfrescoReplicationHandler)).start();
}
void validateCreateSnapshot() throws IOException
{
// Note - Removed the current behavior of creating the directory hierarchy.
// Do we really need to provide this support?
if (!backupRepo.exists(baseSnapDirPath))
{
throw new SolrException(SolrException.ErrorCode.BAD_REQUEST,
" Directory does not exist: " + snapshotDirPath);
}
if (backupRepo.exists(snapshotDirPath))
{
throw new SolrException(SolrException.ErrorCode.BAD_REQUEST,
"Snapshot directory already exists: " + snapshotDirPath);
}
}
public NamedList createSnapshot() throws Exception
{
RefCounted<SolrIndexSearcher> searcher = solrCore.getSearcher();
try
{
if (commitName != null)
{
SolrSnapshotMetaDataManager snapshotMgr = solrCore.getSnapshotMetaDataManager();
Optional<IndexCommit> commit = snapshotMgr.getIndexCommitByName(commitName);
if (commit.isPresent())
{
return createSnapshot(commit.get());
}
throw new SolrException(ErrorCode.SERVER_ERROR,
"Unable to find an index commit with name " + commitName + " for core " + solrCore.getName());
}
else
{
//TODO should we try solrCore.getDeletionPolicy().getLatestCommit() first?
IndexDeletionPolicyWrapper deletionPolicy = solrCore.getDeletionPolicy();
IndexCommit indexCommit = searcher.get().getIndexReader().getIndexCommit();
deletionPolicy.saveCommitPoint(indexCommit.getGeneration());
try
{
return createSnapshot(indexCommit);
}
finally
{
deletionPolicy.releaseCommitPoint(indexCommit.getGeneration());
}
}
}
finally
{
searcher.decref();
}
}
void createSnapAsync(final IndexCommit indexCommit, final int numberToKeep, Consumer<NamedList> result)
{
solrCore.getDeletionPolicy().saveCommitPoint(indexCommit.getGeneration());
new Thread(() -> {
try
{
result.accept(createSnapshot(indexCommit));
}
catch (Exception e)
{
LOG.error("Exception while creating snapshot", e);
NamedList snapShootDetails = new NamedList<>();
snapShootDetails.add("snapShootException", e.getMessage());
result.accept(snapShootDetails);
}
finally
{
solrCore.getDeletionPolicy().releaseCommitPoint(indexCommit.getGeneration());
}
if (snapshotName == null)
{
try
{
deleteOldBackups(numberToKeep);
}
catch (IOException e)
{
LOG.warn("Unable to delete old snapshots ", e);
}
}
}).start();
}
// note: remember to reserve the indexCommit first so it won't get deleted concurrently
private NamedList createSnapshot(final IndexCommit indexCommit) throws Exception
{
LOG.info("Creating backup snapshot " + (snapshotName == null ? "<not named>" : snapshotName) + " at "
+ baseSnapDirPath);
boolean success = false;
try
{
NamedList<Object> details = new NamedList<>();
details.add("startTime", new Date().toString());//bad; should be Instant.now().toString()
Collection<String> files = indexCommit.getFileNames();
Directory dir = solrCore.getDirectoryFactory()
.get(solrCore.getIndexDir(), DirContext.DEFAULT, solrCore.getSolrConfig().indexConfig.lockType);
try
{
for (String fileName : files)
{
backupRepo.copyFileFrom(dir, fileName, snapshotDirPath);
}
}
finally
{
solrCore.getDirectoryFactory().release(dir);
}
details.add("fileCount", files.size());
details.add("status", "success");
details.add("snapshotCompletedAt", new Date().toString());//bad; should be Instant.now().toString()
details.add("snapshotName", snapshotName);
LOG.info("Done creating backup snapshot: " + (snapshotName == null ? "<not named>" : snapshotName) + " at "
+ baseSnapDirPath);
success = true;
return details;
}
finally
{
if (!success)
{
try
{
backupRepo.deleteDirectory(snapshotDirPath);
}
catch (Exception excDuringDelete)
{
LOG.warn("Failed to delete " + snapshotDirPath + " after snapshot creation failed due to: "
+ excDuringDelete);
}
}
}
}
private void deleteOldBackups(int numberToKeep) throws IOException
{
String[] paths = backupRepo.listAll(baseSnapDirPath);
List<OldBackupDirectory> dirs = new ArrayList<>();
for (String f : paths)
{
if (backupRepo.getPathType(baseSnapDirPath.resolve(f)) == PathType.DIRECTORY)
{
OldBackupDirectory obd = new OldBackupDirectory(baseSnapDirPath, f);
if (obd.getTimestamp().isPresent())
{
dirs.add(obd);
}
}
}
if (numberToKeep > dirs.size() - 1)
{
return;
}
Collections.sort(dirs);
int i = 1;
for (OldBackupDirectory dir : dirs)
{
if (i++ > numberToKeep)
{
backupRepo.deleteDirectory(dir.getPath());
}
}
}
protected void deleteNamedSnapshot(AlfrescoReplicationHandler alfrescoReplicationHandler)
{
LOG.info("Deleting snapshot: " + snapshotName);
NamedList<Object> details = new NamedList<>();
try
{
URI path = baseSnapDirPath.resolve("snapshot." + snapshotName);
backupRepo.deleteDirectory(path);
details.add("status", "success");
details.add("snapshotDeletedAt", new Date().toString());
}
catch (IOException e)
{
details.add("status", "Unable to delete snapshot: " + snapshotName);
LOG.warn("Unable to delete snapshot: " + snapshotName, e);
}
alfrescoReplicationHandler.snapShootDetails = details;
}
private static final String DATE_FMT = "yyyyMMddHHmmssSSS";
}
@@ -35,7 +35,7 @@ import org.alfresco.solr.tracker.CommitTracker;
import org.alfresco.solr.tracker.ContentTracker;
import org.alfresco.solr.tracker.MetadataTracker;
import org.alfresco.solr.tracker.ModelTracker;
import org.alfresco.solr.tracker.SlaveNodeStatePublisher;
import org.alfresco.solr.tracker.SlaveCoreStatePublisher;
import org.alfresco.solr.tracker.SolrTrackerScheduler;
import org.alfresco.solr.tracker.Tracker;
import org.alfresco.solr.tracker.TrackerRegistry;
@@ -113,7 +113,7 @@ public class SolrCoreLoadListener extends AbstractSolrEventListener
AlfrescoSolrDataModel.getInstance().getDictionaryService(CMISStrictDictionaryService.DEFAULT),
AlfrescoSolrDataModel.getInstance().getNamespaceDAO());
SolrContentStore contentStore = new SolrContentStore(coreContainer.getSolrHome());
SolrContentStore contentStore = admin.getSolrContentStore();
SolrInformationServer informationServer = new SolrInformationServer(admin, core, repositoryClient, contentStore);
coreProperties.putAll(informationServer.getProps());
admin.getInformationServers().put(core.getName(), informationServer);
@@ -162,6 +162,7 @@ public class SolrCoreLoadListener extends AbstractSolrEventListener
boolean trackersHaveBeenEnabled = Boolean.parseBoolean(coreProperties.getProperty("enable.alfresco.tracking", "true"));
boolean owningCoreIsSlave = isSlaveModeEnabledFor(core);
contentStore.toggleReadOnlyMode(owningCoreIsSlave);
if (trackerRegistry.hasTrackersForCore(core.getName()))
{
@@ -182,7 +183,7 @@ public class SolrCoreLoadListener extends AbstractSolrEventListener
{
LOGGER.info("SearchServices Core Trackers have been explicitly disabled on core \"{}\" through \"enable.alfresco.tracking\" configuration property.", core.getName());
SlaveNodeStatePublisher statePublisher = new SlaveNodeStatePublisher(false, coreProperties, repositoryClient, core.getName(), informationServer);
SlaveCoreStatePublisher statePublisher = new SlaveCoreStatePublisher(false, coreProperties, repositoryClient, core.getName(), informationServer);
trackerRegistry.register(core.getName(), statePublisher);
scheduler.schedule(statePublisher, core.getName(), coreProperties);
trackers.add(statePublisher);
@@ -197,7 +198,7 @@ public class SolrCoreLoadListener extends AbstractSolrEventListener
{
LOGGER.info("SearchServices Core Trackers have been disabled on core \"{}\" because it is a slave core.", core.getName());
SlaveNodeStatePublisher statePublisher = new SlaveNodeStatePublisher(false, coreProperties, repositoryClient, core.getName(), informationServer);
SlaveCoreStatePublisher statePublisher = new SlaveCoreStatePublisher(false, coreProperties, repositoryClient, core.getName(), informationServer);
trackerRegistry.register(core.getName(), statePublisher);
scheduler.schedule(statePublisher, core.getName(), coreProperties);
trackers.add(statePublisher);
@@ -151,6 +151,8 @@ public class CommitTracker extends AbstractTracker
maintenance();
}
infoSrv.flushContentStore();
//Do the commit opening the searcher if needed. This will commit all the work done by indexing trackers.
//This will return immediately and not wait for searchers to warm
//System.out.println("################### Commit:"+openSearcherNeeded);
@@ -41,6 +41,7 @@ import org.alfresco.service.namespace.QName;
import org.alfresco.solr.AlfrescoCoreAdminHandler;
import org.alfresco.solr.AlfrescoSolrDataModel;
import org.alfresco.solr.InformationServer;
import org.alfresco.solr.NodeReport;
import org.alfresco.solr.TrackerState;
import org.alfresco.solr.client.SOLRAPIClient;
import org.apache.commons.lang3.StringUtils;
@@ -59,7 +60,7 @@ import java.util.Properties;
* @since 1.5
* @see <a href="https://issues.alfresco.com/jira/browse/SEARCH-1752">SEARCH-1752</a>
*/
public abstract class NodeStatePublisher extends AbstractTracker
public abstract class CoreStatePublisher extends AbstractTracker
{
DocRouter docRouter;
private final boolean isMaster;
@@ -70,7 +71,7 @@ public abstract class NodeStatePublisher extends AbstractTracker
/** The property to use for determining the shard. */
protected Optional<QName> shardProperty = Optional.empty();
NodeStatePublisher(
CoreStatePublisher(
boolean isMaster,
Properties p,
SOLRAPIClient client,
@@ -88,12 +89,28 @@ public abstract class NodeStatePublisher extends AbstractTracker
docRouter = DocRouterFactory.getRouter(p, ShardMethodEnum.getShardMethod(shardMethod));
}
NodeStatePublisher(Type type)
CoreStatePublisher(Type type)
{
super(type);
this.isMaster = false;
}
/**
* Returns information about the {@link org.alfresco.solr.client.Node} associated with the given dbid.
*
* @param dbid the node identifier.
* @return the {@link org.alfresco.solr.client.Node} associated with the given dbid.
*/
public NodeReport checkNode(Long dbid)
{
NodeReport nodeReport = new NodeReport();
nodeReport.setDbid(dbid);
this.infoSrv.addCommonNodeReportInfo(nodeReport);
return nodeReport;
}
private void firstUpdateShardProperty()
{
shardKey.ifPresent( shardKeyName -> {
@@ -222,4 +239,14 @@ public abstract class NodeStatePublisher extends AbstractTracker
{
return this.docRouter;
}
/**
* Returns true if the hosting core is master or standalone.
*
* @return true if the hosting core is master or standalone.
*/
public boolean isOnMasterOrStandalone()
{
return isMaster;
}
}
@@ -49,7 +49,7 @@ import org.slf4j.LoggerFactory;
* This tracks two things: transactions and metadata nodes
* @author Ahmed Owian
*/
public class MetadataTracker extends NodeStatePublisher implements Tracker
public class MetadataTracker extends CoreStatePublisher implements Tracker
{
protected final static Logger log = LoggerFactory.getLogger(MetadataTracker.class);
private static final int DEFAULT_TRANSACTION_DOCS_BATCH_SIZE = 100;
@@ -888,24 +888,18 @@ public class MetadataTracker extends NodeStatePublisher implements Tracker
}
}
@Override
public NodeReport checkNode(Long dbid)
{
NodeReport nodeReport = new NodeReport();
nodeReport.setDbid(dbid);
NodeReport nodeReport = super.checkNode(dbid);
// In DB
GetNodesParameters parameters = new GetNodesParameters();
parameters.setFromNodeId(dbid);
parameters.setToNodeId(dbid);
List<Node> dbnodes;
try
{
dbnodes = client.getNodes(parameters, 1);
List<Node> dbnodes = client.getNodes(parameters, 1);
if (dbnodes.size() == 1)
{
Node dbnode = dbnodes.get(0);
@@ -915,41 +909,31 @@ public class MetadataTracker extends NodeStatePublisher implements Tracker
else
{
nodeReport.setDbNodeStatus(SolrApiNodeStatus.UNKNOWN);
nodeReport.setDbTx(-1l);
nodeReport.setDbTx(-1L);
}
}
catch (IOException e)
{
nodeReport.setDbNodeStatus(SolrApiNodeStatus.UNKNOWN);
nodeReport.setDbTx(-2l);
nodeReport.setDbTx(-2L);
}
catch (JSONException e)
{
nodeReport.setDbNodeStatus(SolrApiNodeStatus.UNKNOWN);
nodeReport.setDbTx(-3l);
nodeReport.setDbTx(-3L);
}
catch (AuthenticationException e1)
{
nodeReport.setDbNodeStatus(SolrApiNodeStatus.UNKNOWN);
nodeReport.setDbTx(-4l);
nodeReport.setDbTx(-4L);
}
this.infoSrv.addCommonNodeReportInfo(nodeReport);
return nodeReport;
}
public NodeReport checkNode(Node node)
{
NodeReport nodeReport = new NodeReport();
nodeReport.setDbid(node.getId());
nodeReport.setDbNodeStatus(node.getStatus());
nodeReport.setDbTx(node.getTxnId());
this.infoSrv.addCommonNodeReportInfo(nodeReport);
return nodeReport;
return checkNode(node.getId());
}
public List<Node> getFullNodesForDbTransaction(Long txid)
@@ -14,8 +14,8 @@ import java.util.Properties;
/**
* Despite belonging to the Tracker ecosystem, this component is actually a publisher, which periodically informs
* Alfresco about the state of the hosting slave node.
* As the name suggests, this worker is scheduled only when the hosting node acts as a slave.
* Alfresco about the state of the hosting slave core.
* As the name suggests, this worker is scheduled only when the owning core acts as a slave.
* It allows Solr's master/slave setup to be used with dynamic shard registration.
*
* In this scenario the slave is polling a "tracking" Solr node. The tracker below calls
@@ -27,9 +27,9 @@ import java.util.Properties;
* @author Andrea Gazzarini
* @since 1.5
*/
public class SlaveNodeStatePublisher extends NodeStatePublisher
public class SlaveCoreStatePublisher extends CoreStatePublisher
{
public SlaveNodeStatePublisher(
public SlaveCoreStatePublisher(
boolean isMaster,
Properties coreProperties,
SOLRAPIClient repositoryClient,
@@ -61,6 +61,12 @@ public class SlaveNodeStatePublisher extends NodeStatePublisher
// Do nothing here
}
@Override
public boolean isOnMasterOrStandalone()
{
return false;
}
@Override
public boolean hasMaintenance()
{
@@ -0,0 +1,71 @@
/*
* Copyright (C) 2005-2019 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco 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 3 of the License, or
* (at your option) any later version.
*
* Alfresco 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 Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.solr.utils;
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.Map;
import java.util.stream.Collectors;
/**
* @author Elia Porciani
*/
public class AlfrescoFileUtils {
/**
* Check if two directories contains the same files
*
* @param dir
* @param dir2
* @param extensions Limits the search to the extensions provided
* @param recursive Check recursively in all subdirs
* @return
*/
public static boolean areDirectoryEquals(Path dir, Path dir2, String[] extensions, boolean recursive) {
Map<String, File> filesDir1 = FileUtils.listFiles(new File(dir.toUri()), extensions, recursive)
.stream().collect(Collectors.toMap(f -> f.getName(), f -> f));
Map<String, File> filesDir2 = FileUtils.listFiles(new File(dir2.toUri()), extensions, recursive)
.stream().collect(Collectors.toMap(f -> f.getName(), f -> f));
if (filesDir1.size() != filesDir2.size())
return false;
return filesDir1.entrySet().stream().allMatch(e -> {
File fileDir2 = filesDir2.get(e.getKey());
if (fileDir2 == null) {
return false;
}
try {
byte[] otherBytes = Files.readAllBytes(e.getValue().toPath());
byte[] thisBytes = Files.readAllBytes(fileDir2.toPath());
return (Arrays.equals(otherBytes, thisBytes));
} catch (IOException ex) {
return false;
}
});
}
}
@@ -0,0 +1,56 @@
/*
* Copyright (C) 2005-2019 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco 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 3 of the License, or
* (at your option) any later version.
*
* Alfresco 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 Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.solr.utils;
import java.util.Collection;
import java.util.Collections;
public abstract class Utils
{
/**
* Returns the same input collection if that is not null, otherwise a new empty collection.
* Provides a safe way for iterating over a returned collection (which could be null).
*
* @param values the collection.
* @param <T> the collection type.
* @return the same input collection if that is not null, otherwise a new empty collection.
*/
public static <T> Collection<T> notNullOrEmpty(Collection<T> values)
{
return values != null ? values : Collections.emptyList();
}
/**
* Converts the given input in an Integer, otherwise it returns null.
*
* @param value the numeric string.
* @return the corresponding Integer or null in case the input is NaN.
*/
public static Integer toIntOrNull(String value)
{
try
{
return Integer.valueOf(value);
}
catch(NumberFormatException nfe)
{
return null;
}
}
}
@@ -1154,7 +1154,9 @@
https://wiki.apache.org/solr/SolrCloud/
-->
<requestHandler name="/replication" class="solr.ReplicationHandler" >
<requestHandler name="/replication" class="org.alfresco.solr.handler.AlfrescoReplicationHandler">
<!--
To enable simple master/slave replication, uncomment one of the
sections below, depending on whether this solr instance should be
@@ -87,7 +87,7 @@ import static org.alfresco.repo.search.adaptor.lucene.QueryConstants.FIELD_VERSI
* @author Michael Suzuki
*/
@ThreadLeakLingering(linger = 5000)
public abstract class AbstractAlfrescoDistributedTest extends SolrTestInitializer
public abstract class AbstractAlfrescoDistributedIT extends SolrITInitializer
{
protected static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
@@ -296,6 +296,7 @@ public abstract class AbstractAlfrescoDistributedTest extends SolrTestInitialize
{
long begin = System.currentTimeMillis();
//TODO: Support multiple cores per jetty
System.out.println(getStandaloneClients().size());
SolrClient standaloneClient = getStandaloneClients().get(0); //Get the first one
waitForDocCountCore(standaloneClient, query, count, waitMillis, begin);
waitForShardsCount(query, count, waitMillis, begin);
@@ -760,8 +761,6 @@ public abstract class AbstractAlfrescoDistributedTest extends SolrTestInitialize
params.remove("distrib");
setDistributedParams(params);
QueryResponse rsp = queryRandomShard(json, params);
System.out.println("Cluster Response:"+rsp);
System.out.println("Control Response:"+controlRsp);
solrComparator.compareResponses(rsp, controlRsp);
return rsp;
}
@@ -27,13 +27,13 @@ import org.alfresco.solr.client.NodeMetaData;
import org.alfresco.solr.client.SOLRAPIQueueClient;
import org.alfresco.solr.client.Transaction;
import org.alfresco.solr.tracker.Tracker;
import org.alfresco.util.SearchLanguageConversion;
import org.apache.chemistry.opencmis.commons.impl.json.JSONArray;
import org.apache.chemistry.opencmis.commons.impl.json.JSONObject;
import org.apache.chemistry.opencmis.commons.impl.json.JSONValue;
import org.apache.commons.io.FileUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.hadoop.util.Time;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.TopDocs;
import org.apache.solr.SolrTestCaseJ4;
@@ -67,6 +67,7 @@ import org.xml.sax.SAXException;
import javax.servlet.http.HttpServletRequest;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.xpath.XPathExpressionException;
import java.io.File;
import java.io.IOException;
import java.math.BigInteger;
import java.nio.file.Paths;
@@ -84,19 +85,25 @@ import static org.junit.Assert.assertEquals;
/**
* Base class that provides the solr test harness.
* This is used to manage the embedded solr used for unit and integration testing.
* This is used to manage the embedded solr used for integration testing.
* The abstract also provides helper method that interacts with the
* embedded solr.
*
* @author Michael Suzuki
*
*/
public abstract class AbstractAlfrescoSolrTests implements SolrTestFiles, AlfrescoSolrConstants
public abstract class AbstractAlfrescoSolrIT implements SolrTestFiles, AlfrescoSolrConstants
{
static AlfrescoCoreAdminHandler admin;
private static Log LOG = LogFactory.getLog(AbstractAlfrescoSolrTests.class);
private static Log LOG = LogFactory.getLog(AbstractAlfrescoSolrIT.class);
private static boolean CORE_NOT_YET_CREATED = true;
private static String testExecutionSolrHome;
private static String testSolrCollection;
private static String testSolrConf;
private static String templateConf;
/**
* Harness initialized by initTestHarness.
* <p/>
@@ -119,6 +126,20 @@ public abstract class AbstractAlfrescoSolrTests implements SolrTestFiles, Alfres
protected static NodeRef TEST_NODEREF;
protected static Date FTS_TEST_DATE;
protected static void copyTestFiles() throws IOException {
//Add solr home conf folder with alfresco based configuration.
File testExecutionFolder = Paths.get(testExecutionSolrHome).toFile();
for (String s : List.of("/conf", "/alfrescoModels", "/templates", "/collection1")) {
FileUtils.copyDirectory(Paths.get(TEST_FILES_LOCATION + s).toFile(), Paths.get(testExecutionFolder + s).toFile());
}
}
/* Bunch of methods that wrap testHarness object usage.
* TestHarness is a class for internal solr test and should not be use outside Solr.
* Unfortunately this class is used in too many test and a complete refactor would have a huge impact
@@ -218,6 +239,7 @@ public abstract class AbstractAlfrescoSolrTests implements SolrTestFiles, Alfres
@Deprecated
public static void initAlfrescoCore(String schema) throws Exception
{
LOG.info("##################################### init Alfresco core ##############");
LOG.info("####initCore");
System.setProperty("solr.solr.home", TEST_FILES_LOCATION);
@@ -231,18 +253,29 @@ public abstract class AbstractAlfrescoSolrTests implements SolrTestFiles, Alfres
System.setProperty("alfresco.test", "true");
System.setProperty("solr.tests.mergeScheduler", "org.apache.lucene.index.ConcurrentMergeScheduler");
System.setProperty("solr.tests.mergePolicy", "org.apache.lucene.index.TieredMergePolicy");
if (CORE_NOT_YET_CREATED)
{
testExecutionSolrHome = TEST_EXECUTION_FOLDER + "/" + Time.now() + "/solrhome";
testSolrCollection = testExecutionSolrHome + "/collection1";
testSolrConf = testSolrCollection + "/conf/";
templateConf = testExecutionSolrHome + "/templates/%s/conf/";
copyTestFiles();
createAlfrescoCore(schema);
}
LOG.info("####initCore end");
admin = of(h).map(TestHarness::getCoreContainer)
.map(CoreContainer::getMultiCoreHandler)
.map(AlfrescoCoreAdminHandler.class::cast)
.orElseThrow(RuntimeException::new);
}
/**
* @deprecated as testHarness is used
*/
@@ -263,11 +296,11 @@ public abstract class AbstractAlfrescoSolrTests implements SolrTestFiles, Alfres
{
String templateName = System.getProperty("templateName", "rerank");
FileUtils.copyFile(
Paths.get(String.format(TEMPLATE_CONF, templateName) + schema).toFile(),
Paths.get(TEST_SOLR_CONF + schema).toFile());
Paths.get(String.format(templateConf, templateName) + schema).toFile(),
Paths.get(testSolrConf + schema).toFile());
}
SolrResourceLoader resourceLoader = new SolrResourceLoader(Paths.get(TEST_FILES_LOCATION), null, properties);
SolrResourceLoader resourceLoader = new SolrResourceLoader(Paths.get(testExecutionSolrHome), null, properties);
TestCoresLocator locator = new TestCoresLocator(SolrTestCaseJ4.DEFAULT_TEST_CORENAME,
"data",
"solrconfig.xml",
@@ -292,9 +325,9 @@ public abstract class AbstractAlfrescoSolrTests implements SolrTestFiles, Alfres
}
@AfterClass()
public static void tearDown()
{
public static void tearDown() throws IOException {
h.close();
FileUtils.forceDelete(Paths.get(TEST_EXECUTION_FOLDER).toFile());
CORE_NOT_YET_CREATED = true;
}
/**
@@ -309,7 +342,7 @@ public abstract class AbstractAlfrescoSolrTests implements SolrTestFiles, Alfres
/**
* Validates an update XML String is successful
*/
public void assertU(String update)
public void assertU(String update)
{
assertU(null, update);
}
@@ -325,7 +358,7 @@ public abstract class AbstractAlfrescoSolrTests implements SolrTestFiles, Alfres
/**
* Validates an update XML String failed
*/
public void assertFailedU(String update)
public void assertFailedU(String update)
{
assertFailedU(null, update);
}
@@ -434,6 +467,7 @@ public abstract class AbstractAlfrescoSolrTests implements SolrTestFiles, Alfres
}
catch (Exception e2)
{
e2.printStackTrace();
throw new RuntimeException("Exception during query", e2);
}
}
@@ -498,7 +532,7 @@ public abstract class AbstractAlfrescoSolrTests implements SolrTestFiles, Alfres
throws Exception
{
Date date = new Date();
long timeout = (long)date.getTime() + waitMillis;
long timeout = date.getTime() + waitMillis;
RefCounted<SolrIndexSearcher> ref = null;
int totalHits = 0;
@@ -738,6 +772,7 @@ public abstract class AbstractAlfrescoSolrTests implements SolrTestFiles, Alfres
}
catch(Exception exception)
{
exception.printStackTrace();
throw new RuntimeException(exception);
}
finally
@@ -785,7 +820,7 @@ public abstract class AbstractAlfrescoSolrTests implements SolrTestFiles, Alfres
{
public SolrServletRequest(SolrCore core, HttpServletRequest req)
{
super(core, new MultiMapSolrParams(Collections.<String, String[]> emptyMap()));
super(core, new MultiMapSolrParams(Collections.emptyMap()));
}
}
@@ -43,7 +43,7 @@ import static org.alfresco.solr.AlfrescoSolrUtils.*;
*/
@SolrTestCaseJ4.SuppressSSL
@LuceneTestCase.SuppressCodecs({"Appending","Lucene3x","Lucene40","Lucene41","Lucene42","Lucene43", "Lucene44", "Lucene45","Lucene46","Lucene47","Lucene48","Lucene49"})
public class AdminHandlerDistributedTest extends AbstractAlfrescoDistributedTest
public class AdminHandlerDistributedIT extends AbstractAlfrescoDistributedIT
{
private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
final String JETTY_SERVER_ID = this.getClass().getSimpleName();
@@ -52,7 +52,7 @@ public class AdminHandlerDistributedTest extends AbstractAlfrescoDistributedTest
@BeforeClass
private static void initData() throws Throwable
{
initSolrServers(2, "AdminHandlerDistributedTest", null);
initSolrServers(2, "AdminHandlerDistributedIT", null);
}
@AfterClass
@@ -11,7 +11,8 @@ import org.junit.Test;
@LuceneTestCase.SuppressCodecs({"Appending","Lucene3x","Lucene40","Lucene41","Lucene42","Lucene43", "Lucene44", "Lucene45","Lucene46","Lucene47","Lucene48","Lucene49"})
@SolrTestCaseJ4.SuppressSSL
public class AdminHandlerTest extends AbstractAlfrescoSolrTests {
public class AdminHandlerIT extends AbstractAlfrescoSolrIT
{
static CoreAdminHandler admin;
@@ -20,15 +20,23 @@ package org.alfresco.solr;
import static java.util.Arrays.asList;
import static java.util.Collections.emptyList;
import static java.util.Collections.singletonList;
import static org.alfresco.solr.AlfrescoCoreAdminHandler.ALFRESCO_CORE_NAME;
import static org.alfresco.solr.AlfrescoCoreAdminHandler.ARCHIVE_CORE_NAME;
import static org.alfresco.solr.AlfrescoCoreAdminHandler.ARG_TXID;
import static org.alfresco.solr.AlfrescoCoreAdminHandler.STORE_REF_MAP;
import static org.alfresco.solr.AlfrescoCoreAdminHandler.VERSION_CORE_NAME;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.spy;
@@ -42,13 +50,18 @@ import java.util.stream.Collectors;
import org.alfresco.service.cmr.repository.StoreRef;
import org.alfresco.solr.adapters.IOpenBitSet;
import org.alfresco.solr.tracker.AclTracker;
import org.alfresco.solr.tracker.DocRouter;
import org.alfresco.solr.tracker.IndexHealthReport;
import org.alfresco.solr.tracker.MetadataTracker;
import org.alfresco.solr.tracker.PropertyRouter;
import org.alfresco.solr.tracker.SlaveCoreStatePublisher;
import org.alfresco.solr.tracker.TrackerRegistry;
import org.apache.solr.common.SolrException;
import org.apache.solr.common.params.CoreAdminParams;
import org.apache.solr.common.params.ModifiableSolrParams;
import org.apache.solr.common.params.SolrParams;
import org.apache.solr.common.util.NamedList;
import org.apache.solr.core.SolrCore;
import org.apache.solr.request.SolrQueryRequest;
import org.apache.solr.response.SolrQueryResponse;
import org.junit.Before;
@@ -60,7 +73,7 @@ import org.mockito.junit.MockitoJUnitRunner;
/** Unit tests for {@link org.alfresco.solr.AlfrescoCoreAdminHandler}. */
@RunWith(MockitoJUnitRunner.class)
public class AlfrescoCoreAdminHandlerTest
public class AlfrescoCoreAdminHandlerIT
{
/** The string representing a transaction report. */
private static final String TXREPORT = "TXREPORT";
@@ -105,6 +118,169 @@ public class AlfrescoCoreAdminHandlerTest
when(req.getParams()).thenReturn(params);
}
@Test
public void extractShardsWithEmptyParameter_shouldReturnAnEmptyList()
{
assertTrue(alfrescoCoreAdminHandler.extractShards("", Integer.MAX_VALUE).isEmpty());
}
@Test
public void extractShardsWithNullParameter_shouldReturnAnEmptyList()
{
assertTrue(alfrescoCoreAdminHandler.extractShards(null, Integer.MAX_VALUE).isEmpty());
}
@Test
public void extractShardsWithOneInvalidShard_shouldReturnAnEmptyList()
{
assertTrue(alfrescoCoreAdminHandler.extractShards("This is an invalid shard id", Integer.MAX_VALUE).isEmpty());
}
@Test
public void extractShardsWithOneShards_shouldReturnSingletonList()
{
assertEquals(singletonList(1), alfrescoCoreAdminHandler.extractShards("1", Integer.MAX_VALUE));
}
@Test
public void extractShardsWithSeveralValidShards_shouldReturnAllOfThemInTheList()
{
assertEquals(asList(1,5,6,11,23), alfrescoCoreAdminHandler.extractShards("1,5,6,11,23", Integer.MAX_VALUE));
}
@Test
public void extractShardsWithSeveralValidShards_shouldReturnOnlyValidIdentifiers()
{
assertEquals(asList(1,5,6,11,23), alfrescoCoreAdminHandler.extractShards("1,5,A,6,xyz,11,BB,23,o01z", Integer.MAX_VALUE));
}
@Test
public void extractShardsWithSeveralValidShardsAndLimit_shouldConsiderOnlyShardsLesserThanLimit()
{
assertEquals(asList(1,5,6,11,12), alfrescoCoreAdminHandler.extractShards("1,5,6,11,23,25,99,223,12", 23));
}
@Test
public void hasAlfrescoCoreWhenInputIsNull_shouldReturnFalse()
{
assertFalse(alfrescoCoreAdminHandler.hasAlfrescoCore(null));
}
@Test
public void hasAlfrescoCoreWhenWeHaveNoCore_shouldReturnFalse()
{
assertFalse(alfrescoCoreAdminHandler.hasAlfrescoCore(emptyList()));
}
@Test
public void hasAlfrescoCoreWhenDoesntHaveAnyTracker_shouldReturnFalse()
{
assertFalse(alfrescoCoreAdminHandler.hasAlfrescoCore(emptyList()));
}
@Test
public void hasAlfrescoCoreWithRegisteredTrackers_shouldReturnTrue()
{
when(trackerRegistry.hasTrackersForCore("CoreD")).thenReturn(true);
assertTrue(alfrescoCoreAdminHandler.hasAlfrescoCore(asList(dummyCore("CoreA"), dummyCore("CoreB"), dummyCore("CoreC"), dummyCore("CoreD"))));
}
@Test
public void trackerRegistryHasNoCoreNames_itShouldReturnAnEmptyList()
{
assertTrue(alfrescoCoreAdminHandler.coreNames().isEmpty());
}
@Test
public void coreDetectedAsMasterOrStandalone()
{
MetadataTracker coreStatePublisher = mock(MetadataTracker.class);
when(trackerRegistry.getTrackerForCore(anyString(), eq(MetadataTracker.class)))
.thenReturn(coreStatePublisher);
assertTrue(alfrescoCoreAdminHandler.isMasterOrStandalone("ThisIsTheCoreName"));
}
@Test
public void coreDetectedAsSlave()
{
when(trackerRegistry.getTrackerForCore(anyString(), eq(MetadataTracker.class))).thenReturn(null);
assertFalse(alfrescoCoreAdminHandler.isMasterOrStandalone("ThisIsTheCoreName"));
}
@Test
public void coreIsMaster_thenCoreStatePublisherInstanceCorrespondsToMetadataTracker()
{
MetadataTracker coreStatePublisher = mock(MetadataTracker.class);
when(trackerRegistry.getTrackerForCore(anyString(), eq(MetadataTracker.class)))
.thenReturn(coreStatePublisher);
assertSame(coreStatePublisher, alfrescoCoreAdminHandler.coreStatePublisher("ThisIsTheCoreName"));
}
@Test
public void coreIsSlave_thenCoreStatePublisherInstanceCorrespondsToSlaveCoreStatePublisher()
{
SlaveCoreStatePublisher coreStatePublisher = mock(SlaveCoreStatePublisher.class);
when(trackerRegistry.getTrackerForCore(anyString(), eq(MetadataTracker.class))).thenReturn(null);
when(trackerRegistry.getTrackerForCore(anyString(), eq(SlaveCoreStatePublisher.class))).thenReturn(coreStatePublisher);
assertSame(coreStatePublisher, alfrescoCoreAdminHandler.coreStatePublisher("ThisIsTheCoreName"));
}
@Test
public void coreIsSlave_thenDocRouterIsNull()
{
String coreName = "aCore";
when(trackerRegistry.getTrackerForCore(eq(coreName), eq(MetadataTracker.class))).thenReturn(null);
assertNull(alfrescoCoreAdminHandler.getDocRouter("aCore"));
}
@Test
public void coreIsMaster_thenDocRouterIsProperlyReturned()
{
DocRouter expectedRouter = new PropertyRouter("someProperty_.{1,35}");
MetadataTracker coreStatePublisher = mock(MetadataTracker.class);
when(coreStatePublisher.getDocRouter()).thenReturn(expectedRouter);
when(trackerRegistry.getTrackerForCore(anyString(), eq(MetadataTracker.class))).thenReturn(coreStatePublisher);
assertSame(expectedRouter, alfrescoCoreAdminHandler.getDocRouter("aCore"));
}
@Test
public void targetCoreNameCanBeSpecifiedInSeveralWays()
{
String coreName = "ThisIsTheCoreName";
ModifiableSolrParams params = new ModifiableSolrParams();
assertNull(alfrescoCoreAdminHandler.coreName(params));
params.set(CoreAdminParams.CORE, coreName);
assertEquals(coreName, alfrescoCoreAdminHandler.coreName(params));
params.remove(CoreAdminParams.CORE);
assertNull(alfrescoCoreAdminHandler.coreName(params));
params.set("coreName", coreName);
assertEquals(coreName, alfrescoCoreAdminHandler.coreName(params));
assertEquals(coreName, alfrescoCoreAdminHandler.coreName(params));
}
private SolrCore dummyCore(String name)
{
SolrCore core = mock(SolrCore.class);
when(core.getName()).thenReturn(name);
return core;
}
/** Check that a transaction report can be generated. */
@Test
public void handleCustomActionTXReportSuccess() throws Exception
@@ -143,8 +319,6 @@ public class AlfrescoCoreAdminHandlerTest
public void handleCustomActionTXReportMissingTXId()
{
when(params.get(CoreAdminParams.ACTION)).thenReturn(TXREPORT);
when(params.get(ARG_TXID)).thenReturn(null);
alfrescoCoreAdminHandler.handleCustomAction(req, rsp);
verify(rsp, never()).add(anyString(), any());
@@ -156,11 +330,8 @@ public class AlfrescoCoreAdminHandlerTest
{
when(params.get(CoreAdminParams.ACTION)).thenReturn(TXREPORT);
when(params.get(CoreAdminParams.CORE)).thenReturn(null);
when(params.get(ARG_TXID)).thenReturn(TX_ID);
alfrescoCoreAdminHandler.handleCustomAction(req, rsp);
verify(rsp, never()).add(anyString(), any());
}
/** Check that when an unknown action is provided we don't generate a report. */
@@ -212,10 +383,9 @@ public class AlfrescoCoreAdminHandlerTest
public void coreNamesAreTrimmed_oneCoreNameAtTime() {
AlfrescoCoreAdminHandler spy = spy(new AlfrescoCoreAdminHandler() {
@Override
protected boolean newCore(String coreName, int numShards, StoreRef storeRef, String templateName, int replicationFactor, int nodeInstance, int numNodes, String shardIds, Properties extraProperties, SolrQueryResponse rsp)
protected void newCore(String coreName, int numShards, StoreRef storeRef, String templateName, int replicationFactor, int nodeInstance, int numNodes, String shardIds, Properties extraProperties, SolrQueryResponse rsp)
{
// Do nothing here otherwise we cannot spy it
return true;
}
});
@@ -238,10 +408,9 @@ public class AlfrescoCoreAdminHandlerTest
public void validAndInvalidCoreNames() {
AlfrescoCoreAdminHandler spy = spy(new AlfrescoCoreAdminHandler() {
@Override
protected boolean newCore(String coreName, int numShards, StoreRef storeRef, String templateName, int replicationFactor, int nodeInstance, int numNodes, String shardIds, Properties extraProperties, SolrQueryResponse rsp)
protected void newCore(String coreName, int numShards, StoreRef storeRef, String templateName, int replicationFactor, int nodeInstance, int numNodes, String shardIds, Properties extraProperties, SolrQueryResponse rsp)
{
// Do nothing here otherwise we cannot spy it
return true;
}
});
@@ -37,8 +37,9 @@ import org.quartz.SchedulerException;
@LuceneTestCase.SuppressCodecs({"Appending","Lucene3x","Lucene40","Lucene41","Lucene42","Lucene43", "Lucene44", "Lucene45","Lucene46","Lucene47","Lucene48","Lucene49"})
@SolrTestCaseJ4.SuppressSSL
public class AlfrescoSolrReloadTest extends AbstractAlfrescoSolrTests {
private static Log logger = LogFactory.getLog(org.alfresco.solr.tracker.AlfrescoSolrTrackerTest.class);
public class AlfrescoSolrReloadIT extends AbstractAlfrescoSolrIT
{
private static Log logger = LogFactory.getLog(org.alfresco.solr.tracker.AlfrescoSolrTrackerIT.class);
@BeforeClass
public static void beforeClass() throws Exception {
@@ -77,7 +77,7 @@ import org.alfresco.service.cmr.repository.ChildAssociationRef;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.StoreRef;
import org.alfresco.service.namespace.QName;
import org.alfresco.solr.AbstractAlfrescoSolrTests.SolrServletRequest;
import org.alfresco.solr.AbstractAlfrescoSolrIT.SolrServletRequest;
import org.alfresco.solr.client.Acl;
import org.alfresco.solr.client.AclChangeSet;
import org.alfresco.solr.client.AclReaders;
@@ -217,9 +217,9 @@ public class AlfrescoSolrUtils
nodeMetaData.setAclId(acl.getId());
nodeMetaData.setTxnId(txn.getId());
nodeMetaData.setOwner(owner);
nodeMetaData.setAspects(new HashSet<QName>());
nodeMetaData.setAspects(new HashSet<>());
nodeMetaData.setAncestors(ancestors);
Map<QName, PropertyValue> props = new HashMap<QName, PropertyValue>();
Map<QName, PropertyValue> props = new HashMap<>();
props.put(ContentModel.PROP_IS_INDEXED, new StringPropertyValue("true"));
props.put(ContentModel.PROP_CONTENT, new ContentPropertyValue(Locale.US, 0l, "UTF-8", "text/plain", null));
nodeMetaData.setProperties(props);
@@ -230,8 +230,8 @@ public class AlfrescoSolrUtils
}
nodeMetaData.setType(QName.createQName(TEST_NAMESPACE, "testSuperType"));
nodeMetaData.setAncestors(ancestors);
nodeMetaData.setPaths(new ArrayList<Pair<String, QName>>());
nodeMetaData.setNamePaths(new ArrayList<List<String>>());
nodeMetaData.setPaths(new ArrayList<>());
nodeMetaData.setNamePaths(new ArrayList<>());
return nodeMetaData;
}
/**
@@ -30,7 +30,7 @@ import org.junit.BeforeClass;
import org.junit.Test;
@SolrTestCaseJ4.SuppressSSL
public class AlfrescoTrackerRegistrationTest extends AbstractAlfrescoSolrTests
public class AlfrescoTrackerRegistrationIT extends AbstractAlfrescoSolrIT
{
@BeforeClass
public static void beforeClass() throws Exception {
@@ -51,7 +51,7 @@ import static org.alfresco.solr.AlfrescoSolrUtils.getCore;
*/
@SolrTestCaseJ4.SuppressSSL
@LuceneTestCase.SuppressCodecs({"Appending","Lucene3x","Lucene40","Lucene41","Lucene42","Lucene43", "Lucene44", "Lucene45","Lucene46","Lucene47","Lucene48","Lucene49"})
public class CoresCreateUpdateDistributedTest extends AbstractAlfrescoDistributedTest
public class CoresCreateUpdateDistributedIT extends AbstractAlfrescoDistributedIT
{
private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
final static String JETTY_SERVER_ID = "CoresCreateUpdateDistributedTest";
@@ -40,7 +40,7 @@ import static org.alfresco.solr.AlfrescoSolrUtils.getCore;
*/
@SolrTestCaseJ4.SuppressSSL
@LuceneTestCase.SuppressCodecs({"Appending","Lucene3x","Lucene40","Lucene41","Lucene42","Lucene43", "Lucene44", "Lucene45","Lucene46","Lucene47","Lucene48","Lucene49"})
public class CoresCreateViaPropertyTest extends AbstractAlfrescoDistributedTest
public class CoresCreateViaPropertyIT extends AbstractAlfrescoDistributedIT
{
private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
final static String JETTY_SERVER_ID = "CoresCreateViaPropertyTest";
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2005-2014 Alfresco Software Limited.
* Copyright (C) 2005-2019 Alfresco Software Limited.
*
* This file is part of Alfresco
*
@@ -72,79 +72,4 @@ public class SolrDataModelTest
Long actualId = AlfrescoSolrDataModel.parseTransactionId(id);
assertEquals(expectedId, actualId);
}
@Test
public void smokeTestCMISModel()
{
AlfrescoSolrDataModel dataModel = new AlfrescoSolrDataModel();
// load test model containing content properties multiple
ClassLoader cl = SolrDataModelTest.class.getClassLoader();
InputStream modelStream = cl.getResourceAsStream("alfresco/model/dictionaryModel.xml");
assertNotNull(modelStream);
M2Model model = M2Model.createModel(modelStream);
dataModel.putModel(model);
modelStream = cl.getResourceAsStream("alfresco/model/cmisModel.xml");
assertNotNull(modelStream);
model = M2Model.createModel(modelStream);
dataModel.putModel(model);
assertEquals(2, dataModel.getAlfrescoModels().size());
assertEquals(1, dataModel.getIndexedFieldNamesForProperty(OBJECT_ID).getFields().size());
assertEquals(4, dataModel.getIndexedFieldNamesForProperty(NAME).getFields().size());
assertEquals(1, dataModel.getIndexedFieldNamesForProperty(CREATION_DATE).getFields().size());
assertEquals(0, dataModel.getIndexedFieldNamesForProperty(IS_IMMUTABLE).getFields().size());
assertEquals(0, dataModel.getIndexedFieldNamesForProperty(IS_PRIVATE_WOKING_COPY).getFields().size());
assertEquals(1, dataModel.getIndexedFieldNamesForProperty(CONTENT_STREAM_LENGTH).getFields().size());
assertEquals(1, dataModel .getQueryableFields(OBJECT_ID, null, FieldUse.FACET).getFields().size());
assertEquals(1, dataModel .getQueryableFields(OBJECT_ID, null, FieldUse.COMPLETION).getFields().size());
assertEquals(1, dataModel .getQueryableFields(OBJECT_ID, null, FieldUse.FTS).getFields().size());
assertEquals(1, dataModel .getQueryableFields(OBJECT_ID, null, FieldUse.ID).getFields().size());
assertEquals(1, dataModel .getQueryableFields(OBJECT_ID, null, FieldUse.MULTI_FACET).getFields().size());
assertEquals(1, dataModel .getQueryableFields(OBJECT_ID, null, FieldUse.SORT).getFields().size());
assertEquals(1, dataModel .getQueryableFields(OBJECT_ID, null, FieldUse.STATS).getFields().size());
assertEquals(1, dataModel .getQueryableFields(OBJECT_ID, null, FieldUse.SUGGESTION).getFields().size());
assertEquals(1, dataModel .getQueryableFields(NAME, null, FieldUse.FACET).getFields().size());
assertEquals(1, dataModel .getQueryableFields(NAME, null, FieldUse.COMPLETION).getFields().size());
assertEquals(1, dataModel .getQueryableFields(NAME, null, FieldUse.FTS).getFields().size());
assertEquals(2, dataModel .getQueryableFields(NAME, null, FieldUse.ID).getFields().size());
assertEquals(1, dataModel .getQueryableFields(NAME, null, FieldUse.MULTI_FACET).getFields().size());
assertEquals(1, dataModel .getQueryableFields(NAME, null, FieldUse.SORT).getFields().size());
assertEquals(1, dataModel .getQueryableFields(NAME, null, FieldUse.STATS).getFields().size());
assertEquals(1, dataModel .getQueryableFields(NAME, null, FieldUse.SUGGESTION).getFields().size());
assertEquals(1, dataModel .getQueryableFields(CREATION_DATE, null, FieldUse.FACET).getFields().size());
assertEquals(1, dataModel .getQueryableFields(CREATION_DATE, null, FieldUse.COMPLETION).getFields().size());
assertEquals(1, dataModel .getQueryableFields(CREATION_DATE, null, FieldUse.FTS).getFields().size());
assertEquals(1, dataModel .getQueryableFields(CREATION_DATE, null, FieldUse.ID).getFields().size());
assertEquals(1, dataModel .getQueryableFields(CREATION_DATE, null, FieldUse.MULTI_FACET).getFields().size());
assertEquals(1, dataModel .getQueryableFields(CREATION_DATE, null, FieldUse.SORT).getFields().size());
assertEquals(1, dataModel .getQueryableFields(CREATION_DATE, null, FieldUse.STATS).getFields().size());
assertEquals(1, dataModel .getQueryableFields(CREATION_DATE, null, FieldUse.SUGGESTION).getFields().size());
assertEquals(1, dataModel .getQueryableFields(IS_PRIVATE_WOKING_COPY, null, FieldUse.FACET).getFields().size());
assertEquals(1, dataModel .getQueryableFields(IS_PRIVATE_WOKING_COPY, null, FieldUse.COMPLETION).getFields().size());
assertEquals(1, dataModel .getQueryableFields(IS_PRIVATE_WOKING_COPY, null, FieldUse.FTS).getFields().size());
assertEquals(1, dataModel .getQueryableFields(IS_PRIVATE_WOKING_COPY, null, FieldUse.ID).getFields().size());
assertEquals(1, dataModel .getQueryableFields(IS_PRIVATE_WOKING_COPY, null, FieldUse.MULTI_FACET).getFields().size());
assertEquals(1, dataModel .getQueryableFields(IS_PRIVATE_WOKING_COPY, null, FieldUse.SORT).getFields().size());
assertEquals(1, dataModel .getQueryableFields(IS_PRIVATE_WOKING_COPY, null, FieldUse.STATS).getFields().size());
assertEquals(1, dataModel .getQueryableFields(IS_PRIVATE_WOKING_COPY, null, FieldUse.SUGGESTION).getFields().size());
assertEquals(1, dataModel .getQueryableFields(CONTENT_STREAM_LENGTH, null, FieldUse.FACET).getFields().size());
assertEquals(1, dataModel .getQueryableFields(CONTENT_STREAM_LENGTH, null, FieldUse.COMPLETION).getFields().size());
assertEquals(1, dataModel .getQueryableFields(CONTENT_STREAM_LENGTH, null, FieldUse.FTS).getFields().size());
assertEquals(1, dataModel .getQueryableFields(CONTENT_STREAM_LENGTH, null, FieldUse.ID).getFields().size());
assertEquals(1, dataModel .getQueryableFields(CONTENT_STREAM_LENGTH, null, FieldUse.MULTI_FACET).getFields().size());
assertEquals(1, dataModel .getQueryableFields(CONTENT_STREAM_LENGTH, null, FieldUse.SORT).getFields().size());
assertEquals(1, dataModel .getQueryableFields(CONTENT_STREAM_LENGTH, null, FieldUse.STATS).getFields().size());
assertEquals(1, dataModel .getQueryableFields(CONTENT_STREAM_LENGTH, null, FieldUse.SUGGESTION).getFields().size());
}
}
@@ -4,6 +4,7 @@ import com.carrotsearch.randomizedtesting.annotations.ThreadLeakLingering;
import org.alfresco.solr.basics.RandomSupplier;
import org.alfresco.solr.client.SOLRAPIQueueClient;
import org.apache.commons.io.FileUtils;
import org.apache.hadoop.util.Time;
import org.apache.solr.SolrTestCaseJ4;
import org.apache.solr.client.solrj.SolrClient;
import org.apache.solr.client.solrj.embedded.JettyConfig;
@@ -69,7 +70,7 @@ import static org.alfresco.solr.AlfrescoSolrUtils.createCoreUsingTemplate;
* @author Michael Suzuki
*/
@ThreadLeakLingering(linger = 5000)
public abstract class SolrTestInitializer extends SolrTestCaseJ4
public abstract class SolrITInitializer extends SolrTestCaseJ4
{
private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
@@ -225,13 +226,14 @@ public abstract class SolrTestInitializer extends SolrTestCaseJ4
SOLRAPIQueueClient.aclMap.clear();
SOLRAPIQueueClient.nodeMap.clear();
}
/**
* Creates a JettySolrRunner (if one didn't exist already). DOES NOT START IT.
* @return
* @throws Exception
*/
private static JettySolrRunner createJetty(String jettyKey, boolean basicAuth) throws Exception
protected static JettySolrRunner createJetty(String jettyKey, boolean basicAuth) throws Exception
{
if (jettyContainers.containsKey(jettyKey))
{
@@ -241,7 +243,7 @@ public abstract class SolrTestInitializer extends SolrTestCaseJ4
{
Path jettySolrHome = testDir.toPath().resolve(jettyKey);
seedSolrHome(jettySolrHome);
JettySolrRunner jetty = createJetty(jettySolrHome.toFile(), null, null, false, getSchemaFile(), basicAuth);
JettySolrRunner jetty = createJetty(jettySolrHome.toFile(), null, null, false, 0, getSchemaFile(), basicAuth);
return jetty;
}
}
@@ -255,7 +257,7 @@ public abstract class SolrTestInitializer extends SolrTestCaseJ4
* @param additionalProperties
* @throws Exception
*/
private static void addCoreToJetty(String jettyKey, String sourceConfigName, String coreName, Properties additionalProperties) throws Exception
protected static void addCoreToJetty(String jettyKey, String sourceConfigName, String coreName, Properties additionalProperties) throws Exception
{
Path jettySolrHome = testDir.toPath().resolve(jettyKey);
Path coreSourceConfig = new File(getTestFilesHome() + "/"+sourceConfigName).toPath();
@@ -297,7 +299,7 @@ public abstract class SolrTestInitializer extends SolrTestCaseJ4
* @param jsr
* @throws Exception
*/
private static void startJetty(JettySolrRunner jsr) throws Exception {
protected static void startJetty(JettySolrRunner jsr) throws Exception {
if (!jsr.isRunning())
{
jsr.start();
@@ -308,6 +310,7 @@ public abstract class SolrTestInitializer extends SolrTestCaseJ4
{
boolean basicAuth = additionalProperties != null ? Boolean.parseBoolean(additionalProperties.getProperty("BasicAuth", "false")) : false;
JettySolrRunner jsr = createJetty(jettyKey, basicAuth);
jettyContainers.put(jettyKey, jsr);
@@ -355,7 +358,7 @@ public abstract class SolrTestInitializer extends SolrTestCaseJ4
props.put("shard.range", ranges[i]);
}
String shardKey = jettyKey+"_shard_"+i;
String shardKey = jettyKey+"_shard_" + i + "/solrhome";
JettySolrRunner j = createJetty(shardKey, basicAuth);
//use the first corename specified as the Share template
addCoreToJetty(shardKey, coreNames[0], shardname, props);
@@ -373,7 +376,7 @@ public abstract class SolrTestInitializer extends SolrTestCaseJ4
protected static void destroyServers() throws Exception
{
List<String> solrHomes = new ArrayList<String>();
List<String> solrHomes = new ArrayList<>();
for (JettySolrRunner jetty : jettyContainers.values())
{
solrHomes.add(jetty.getSolrHome());
@@ -406,9 +409,9 @@ public abstract class SolrTestInitializer extends SolrTestCaseJ4
solrCollectionNameToStandaloneClient.clear();
}
public static JettySolrRunner createJetty(File solrHome, String dataDir, String shardList, boolean sslEnabled, String schemaOverride, boolean basicAuth) throws Exception
public static JettySolrRunner createJetty(File solrHome, String dataDir, String shardList, boolean sslEnabled, int port, String schemaOverride, boolean basicAuth) throws Exception
{
return createJetty(solrHome, dataDir, shardList, sslEnabled, schemaOverride, useExplicitNodeNames, basicAuth);
return createJetty(solrHome, dataDir, shardList, sslEnabled, port, schemaOverride, useExplicitNodeNames, basicAuth);
}
/**
@@ -417,12 +420,13 @@ public abstract class SolrTestInitializer extends SolrTestCaseJ4
* @param solrHome
* @param dataDir
* @param shardList
* @param port
* @param schemaOverride
* @param explicitCoreNodeName
* @return
* @throws Exception
*/
public static JettySolrRunner createJetty(File solrHome, String dataDir, String shardList, boolean sslEnabled,
public static JettySolrRunner createJetty(File solrHome, String dataDir, String shardList, boolean sslEnabled, int port,
String schemaOverride, boolean explicitCoreNodeName, boolean basicAuth) throws Exception
{
Properties props = new Properties();
@@ -445,10 +449,10 @@ public abstract class SolrTestInitializer extends SolrTestCaseJ4
if(basicAuth) {
System.out.println("###### adding basic auth ######");
config = JettyConfig.builder().setContext("/solr").withFilter(BasicAuthFilter.class, "/sql/*").stopAtShutdown(true).withSSLConfig(sslConfig).build();
config = JettyConfig.builder().setContext("/solr").setPort(port).withFilter(BasicAuthFilter.class, "/sql/*").stopAtShutdown(true).withSSLConfig(sslConfig).build();
} else {
System.out.println("###### no basic auth ######");
config = JettyConfig.builder().setContext("/solr").stopAtShutdown(true).withSSLConfig(sslConfig).build();
config = JettyConfig.builder().setContext("/solr").setPort(port).stopAtShutdown(true).withSSLConfig(sslConfig).build();
}
JettySolrRunner jetty = new JettySolrRunner(solrHome.getAbsolutePath(), props, config);
@@ -24,8 +24,6 @@ package org.alfresco.solr;
*/
public interface SolrTestFiles
{
public final String TEST_FILES_LOCATION = "target/test-classes/test-files";
public final String TEST_SOLR_COLLECTION = TEST_FILES_LOCATION + "/collection1";
public final String TEST_SOLR_CONF = TEST_SOLR_COLLECTION + "/conf/";
public final String TEMPLATE_CONF = TEST_FILES_LOCATION + "/templates/%s/conf/";
String TEST_FILES_LOCATION = "target/test-classes/test-files";
String TEST_EXECUTION_FOLDER = "target/test-execution-folder";
}
@@ -40,7 +40,7 @@ import static org.alfresco.solr.AlfrescoSolrUtils.*;
*/
@SolrTestCaseJ4.SuppressSSL
@LuceneTestCase.SuppressCodecs({"Appending","Lucene3x","Lucene40","Lucene41","Lucene42","Lucene43", "Lucene44", "Lucene45","Lucene46","Lucene47","Lucene48","Lucene49"})
public class TemplatesDistributedTest extends AbstractAlfrescoDistributedTest
public class TemplatesDistributedIT extends AbstractAlfrescoDistributedIT
{
private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
final static String JETTY_SERVER_ID = "TemplatesDistributedTest";
@@ -0,0 +1,291 @@
/*
* Copyright (C) 2005-2014 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco 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 3 of the License, or
* (at your option) any later version.
*
* Alfresco 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 Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.solr.content;
import org.apache.commons.io.FileUtils;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.search.Query;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.junit.MockitoJUnitRunner;
import java.io.File;
import java.io.IOException;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
/**
* Solr ContentStore {@link ChangeSet} test case.
*
* @author Andrea Gazzarini
* @since 1.5
*/
@RunWith(MockitoJUnitRunner.class)
public class SolrContentStoreChangeSetTest
{
private ChangeSet changeSet;
private final String contentStoreRootFolder = "/tmp";
private final File rootFolder = new File(contentStoreRootFolder, ChangeSet.CHANGESETS_ROOT_FOLDER_NAME);
@Before
public void setUp()
{
changeSet = new ChangeSet.Builder().withContentStoreRoot(contentStoreRootFolder).build();
}
@After
public void tearDown() throws IOException
{
changeSet.close();
FileUtils.cleanDirectory(rootFolder);
}
@Test(expected = IllegalArgumentException.class)
public void nullContentStoreRootFolder_shouldThrowAnException()
{
new ChangeSet.Builder().withContentStoreRoot(null).build();
}
@Test(expected = IllegalArgumentException.class)
public void nonWriteableContentStoreRootFolder_shouldThrowAnException()
{
new ChangeSet.Builder().withContentStoreRoot("/root").build();
}
@Test
public void newAddOrReplaceRecord_shouldRemovePreviousDeletion()
{
String path ="some/random/dbid.gz";
assertTrue(changeSet.deletes.isEmpty());
assertTrue(changeSet.adds.isEmpty());
changeSet.delete(path);
assertTrue(String.valueOf(changeSet.deletes), changeSet.deletes.contains(path));
assertTrue(String.valueOf(changeSet.adds), changeSet.adds.isEmpty());
changeSet.addOrReplace(path);
assertTrue(String.valueOf(changeSet.deletes), changeSet.deletes.isEmpty());
assertTrue(String.valueOf(changeSet.adds),changeSet.adds.contains(path));
}
@Test
public void deletedRecord_shouldRemovePreviousAdd()
{
String path ="some/random/dbid.gz";
assertTrue(changeSet.deletes.isEmpty());
assertTrue(changeSet.adds.isEmpty());
changeSet.addOrReplace(path);
assertTrue(String.valueOf(changeSet.deletes), changeSet.deletes.isEmpty());
assertTrue(String.valueOf(changeSet.adds), changeSet.adds.contains(path));
changeSet.delete(path);
assertTrue(String.valueOf(changeSet.deletes), changeSet.deletes.contains(path));
assertTrue(String.valueOf(changeSet.adds),changeSet.adds.isEmpty());
}
@Test
public void transientChangeset_doesNothingOnFlush() throws IOException
{
ChangeSet changeset = new ChangeSet.Builder().build();
changeset.addOrReplace("A");
changeset.delete("B");
assertEquals(1, changeset.deletes.size());
assertEquals(1, changeset.adds.size());
changeset.flush();
assertEquals(1, changeset.deletes.size());
assertEquals(1, changeset.adds.size());
}
@Test(expected = UnsupportedOperationException.class)
public void emptyChangeset_isImmutableDoesntAllowAdds()
{
ChangeSet changeset = new ChangeSet.Builder().empty().build();
changeset.addOrReplace("A");
}
@Test(expected = UnsupportedOperationException.class)
public void emptyChangeset_isImmutableDoesntAllowDeletes()
{
ChangeSet changeset = new ChangeSet.Builder().empty().build();
changeset.delete("A");
}
@Test
public void lastCommittedVersionNotPresentAtVeryBeginning()
{
assertEquals(SolrContentStore.NO_VERSION_AVAILABLE, changeSet.getLastCommittedVersion());
}
@Test
public void lastCommittedVersionNotAvailable_shouldReturnNO_AVAILABLE_VERSION() throws IOException
{
changeSet.selectEverything = mock(Query.class);
when(changeSet.selectEverything.rewrite(any(IndexReader.class))).thenThrow(new RuntimeException());
assertEquals(SolrContentStore.NO_VERSION_AVAILABLE, changeSet.getLastCommittedVersion());
}
@Test
public void flushDoesNothingIfThereAreNoChanges() throws IOException
{
assertEquals(SolrContentStore.NO_VERSION_AVAILABLE, changeSet.getLastCommittedVersion());
changeSet.flush();
assertEquals(SolrContentStore.NO_VERSION_AVAILABLE, changeSet.getLastCommittedVersion());
}
@Test
public void invalidOrUnknownVersion() throws IOException
{
assertEquals(SolrContentStore.NO_VERSION_AVAILABLE, changeSet.getLastCommittedVersion());
assertTrue(changeSet.isUnknownVersion(SolrContentStore.NO_VERSION_AVAILABLE));
assertTrue(changeSet.isUnknownVersion(SolrContentStore.NO_VERSION_AVAILABLE - 1L));
assertTrue(changeSet.isUnknownVersion(System.currentTimeMillis()));
changeSet.addOrReplace("A1");
changeSet.addOrReplace("A2");
changeSet.delete("A3");
changeSet.delete("A1");
changeSet.flush();
long lastCommittedVersionAfterFirstFlush = changeSet.getLastCommittedVersion();
assertNotEquals(SolrContentStore.NO_VERSION_AVAILABLE, lastCommittedVersionAfterFirstFlush);
assertTrue(changeSet.isUnknownVersion(System.currentTimeMillis()));
}
@Test
public void validVersion() throws IOException
{
assertEquals(SolrContentStore.NO_VERSION_AVAILABLE, changeSet.getLastCommittedVersion());
assertTrue(changeSet.isUnknownVersion(System.currentTimeMillis()));
changeSet.addOrReplace("A1");
changeSet.addOrReplace("A2");
changeSet.delete("A3");
changeSet.delete("A1");
changeSet.flush();
long lastCommittedVersionAfterFirstFlush = changeSet.getLastCommittedVersion();
changeSet.addOrReplace("B1");
changeSet.addOrReplace("B2");
changeSet.delete("B3");
changeSet.delete("B1");
changeSet.flush();
long lastCommittedVersionAfterSecondFlush = changeSet.getLastCommittedVersion();
assertNotEquals(lastCommittedVersionAfterSecondFlush, lastCommittedVersionAfterFirstFlush);
assertFalse(changeSet.isUnknownVersion(lastCommittedVersionAfterFirstFlush));
assertFalse(changeSet.isUnknownVersion(lastCommittedVersionAfterSecondFlush));
}
@Test
public void inCaseOfFailure_inputVersionIsConsideredUnknown() throws IOException
{
assertEquals(SolrContentStore.NO_VERSION_AVAILABLE, changeSet.getLastCommittedVersion());
assertTrue(changeSet.isUnknownVersion(System.currentTimeMillis()));
changeSet.addOrReplace("A1");
changeSet.addOrReplace("A2");
changeSet.delete("A3");
changeSet.delete("A1");
changeSet.flush();
long lastCommittedVersion = changeSet.getLastCommittedVersion();
// Force a NPE exception...
changeSet.searcher = null;
// ...so a valid version is considered unknown even if it is valid
assertTrue(changeSet.isUnknownVersion(lastCommittedVersion));
}
@Test
public void persistentChangesetsAreMergedBeforeReturningToRequestor() throws IOException
{
assertEquals(SolrContentStore.NO_VERSION_AVAILABLE, changeSet.getLastCommittedVersion());
changeSet.addOrReplace("A1");
changeSet.addOrReplace("A2");
changeSet.delete("A3");
changeSet.delete("A1");
changeSet.flush();
long lastCommittedVersionAfterFirstFlush = changeSet.getLastCommittedVersion();
assertNotEquals(SolrContentStore.NO_VERSION_AVAILABLE, lastCommittedVersionAfterFirstFlush);
changeSet.addOrReplace("A1");
changeSet.addOrReplace("A3");
changeSet.delete("A4");
changeSet.flush();
long lastCommittedVersionAfterSecondFlush = changeSet.getLastCommittedVersion();
assertNotEquals(lastCommittedVersionAfterFirstFlush, lastCommittedVersionAfterSecondFlush);
ChangeSet changesSinceTheVeryBeginning = changeSet.since(SolrContentStore.NO_VERSION_AVAILABLE);
// ADDS = [A1, A2, A3]
// DELS = [A4]
assertEquals(3, changesSinceTheVeryBeginning.adds.size());
assertEquals(1, changesSinceTheVeryBeginning.deletes.size());
assertTrue(changesSinceTheVeryBeginning.adds.contains("A1"));
assertTrue(changesSinceTheVeryBeginning.adds.contains("A2"));
assertTrue(changesSinceTheVeryBeginning.adds.contains("A3"));
assertTrue(changesSinceTheVeryBeginning.deletes.contains("A4"));
ChangeSet changesAfterSecondFlush = changeSet.since(lastCommittedVersionAfterFirstFlush);
// ADDS = [A1, A3]
// DELS = [A4]
assertEquals(2, changesAfterSecondFlush.adds.size());
assertEquals(1, changesAfterSecondFlush.deletes.size());
assertTrue(changesAfterSecondFlush.adds.contains("A1"));
assertTrue(changesAfterSecondFlush.adds.contains("A3"));
assertTrue(changesAfterSecondFlush.deletes.contains("A4"));
}
}
@@ -18,47 +18,245 @@
*/
package org.alfresco.solr.content;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
import org.alfresco.repo.content.ContentContext;
import org.alfresco.service.cmr.repository.ContentReader;
import org.alfresco.service.cmr.repository.ContentWriter;
import org.alfresco.solr.client.NodeMetaData;
import org.apache.commons.io.FileUtils;
import org.apache.solr.common.SolrInputDocument;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
import org.mockito.junit.MockitoJUnitRunner;
import static org.hamcrest.core.Is.is;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.Mockito.mock;
/**
* Tests {@link SolrContentStoreTest}
*
* @author Derek Hulley
* @since 5.0
* @author Andrea Gazzarini
* @since 1.5
*/
@RunWith(MockitoJUnitRunner.class)
public class SolrContentStoreTest
{
private static final String DEFAULT_TENANT = "_DEFAULT_";
private String solrHome = new File("./target/contentstoretest/").getAbsolutePath();
private long dbid = 111;
private String tenant = "me";
private String solrHome;
private SolrContentStore contentStore;
@Before
public void setUp()
{
solrHome = new File("./target/contentstoretest/" + System.currentTimeMillis()).getAbsolutePath();
contentStore = new SolrContentStore(solrHome);
}
@After
public void tearDown() throws IOException
{
contentStore.close();
File rootDir = new File(new SolrContentStore(solrHome).getRootLocation());
FileUtils.deleteDirectory(rootDir);
}
@Test
public void atVeryBeginningAccessModeIsNotSet()
{
assertSame(contentStore.notYetSet, contentStore.currentAccessMode);
}
@Test
public void whenAccessModeIsNotSetMethodCallsThrowsExceptionOrDoNothing()
{
assertSame(contentStore.notYetSet, contentStore.currentAccessMode);
expectIllegalState(contentStore::getLastCommittedVersion);
expectIllegalState(contentStore::setLastCommittedVersion, System.currentTimeMillis());
expectIllegalState(contentStore::getChanges, System.currentTimeMillis());
expectIllegalState(contentStore::removeDocFromContentStore, mock(NodeMetaData.class));
expectIllegalState(contentStore::storeDocOnSolrContentStore, mock(NodeMetaData.class), mock(SolrInputDocument.class));
try
{
contentStore.flushChangeSet();
fail();
}
catch (IOException exception)
{
fail();
}
catch(IllegalStateException expected)
{
// Nothing to be done here
}
try
{
contentStore.storeDocOnSolrContentStore(DEFAULT_TENANT, System.currentTimeMillis(), mock(SolrInputDocument.class));
fail();
}
catch(IllegalStateException expected)
{
// Nothing to be done here
}
}
@Test
public void lastCommittedVersionInReadOnlyModeNotFound()
{
contentStore.toggleReadOnlyMode(true);
assertEquals(SolrContentStore.NO_VERSION_AVAILABLE, contentStore.getLastCommittedVersion());
}
@Test
public void lastCommittedVersionInReadOnlyModeNotFoundBecauseException() throws IOException
{
contentStore.toggleReadOnlyMode(true);
Files.write(new File(contentStore.getRootLocation(), ".version").toPath(), "NAN".getBytes());
assertEquals(SolrContentStore.NO_VERSION_AVAILABLE, contentStore.getLastCommittedVersion());
}
@Test
public void lastCommittedVersionInReadOnlyModeNotFoundBecauseFileIsEmpty() throws IOException
{
contentStore.toggleReadOnlyMode(true);
File emptyVersionFile = new File(contentStore.getRootLocation(), ".version");
emptyVersionFile.createNewFile();
assertEquals(SolrContentStore.NO_VERSION_AVAILABLE, contentStore.getLastCommittedVersion());
}
@Test
public void getLastCommittedVersionInReadOnlyMode() throws IOException
{
contentStore.toggleReadOnlyMode(true);
long expectedLastCommittedVersion = System.currentTimeMillis();
Files.write(new File(contentStore.getRootLocation(), ".version").toPath(), Long.toString(expectedLastCommittedVersion).getBytes());
assertEquals(expectedLastCommittedVersion, contentStore.getLastCommittedVersion());
}
@Test
public void setLastCommittedVersionInReadOnlyMode()
{
contentStore.toggleReadOnlyMode(true);
long expectedLastCommittedVersion = System.currentTimeMillis();
contentStore.setLastCommittedVersion(expectedLastCommittedVersion);
File versionFile = new File(contentStore.getRootLocation(), ".version");
assertTrue(versionFile.canRead());
assertEquals(expectedLastCommittedVersion, contentStore.getLastCommittedVersion());
}
@Test
public void getChangesInReadOnlyModeReturnsAnEmptyMap()
{
contentStore.toggleReadOnlyMode(true);
assertEquals(Collections.<String, List<Map<String, Object>>>emptyMap(), contentStore.getChanges(System.currentTimeMillis()));
}
@Test
public void transitionFromNotSetToReadOnlyMode()
{
assertSame(contentStore.notYetSet, contentStore.currentAccessMode);
contentStore.toggleReadOnlyMode(true);
assertSame(contentStore.readOnly, contentStore.currentAccessMode);
}
@Test
public void transitionFromNotSetToReadWriteMode()
{
assertSame(contentStore.notYetSet, contentStore.currentAccessMode);
contentStore.toggleReadOnlyMode(false);
assertSame(contentStore.readWrite, contentStore.currentAccessMode);
}
@Test
public void transitionFromReadOnlyToReadWriteMode()
{
assertSame(contentStore.notYetSet, contentStore.currentAccessMode);
contentStore.toggleReadOnlyMode(true);
assertSame(contentStore.readOnly, contentStore.currentAccessMode);
contentStore.toggleReadOnlyMode(false);
assertSame(contentStore.readWrite, contentStore.currentAccessMode);
}
@Test
public void transitionFromReadOnlyToReadOnlyHasNoEffect()
{
assertSame(contentStore.notYetSet, contentStore.currentAccessMode);
contentStore.toggleReadOnlyMode(true);
assertSame(contentStore.readOnly, contentStore.currentAccessMode);
contentStore.toggleReadOnlyMode(true);
assertSame(contentStore.readOnly, contentStore.currentAccessMode);
}
@Test
public void transitionFromReadWriteToReadOnlyModeHasNoEffect()
{
assertSame(contentStore.notYetSet, contentStore.currentAccessMode);
contentStore.toggleReadOnlyMode(false);
assertSame(contentStore.readWrite, contentStore.currentAccessMode);
contentStore.toggleReadOnlyMode(true);
assertSame(contentStore.readWrite, contentStore.currentAccessMode);
}
@Test
public void transitionFromReadWriteToReadWriteHasNoEffect()
{
assertSame(contentStore.notYetSet, contentStore.currentAccessMode);
contentStore.toggleReadOnlyMode(false);
assertSame(contentStore.readWrite, contentStore.currentAccessMode);
contentStore.toggleReadOnlyMode(false);
assertSame(contentStore.readWrite, contentStore.currentAccessMode);
}
@Test(expected = RuntimeException.class)
public void contentStoreCreation_solrHomeNull_shouldThrowException()
{
@@ -117,180 +315,110 @@ public class SolrContentStoreTest
Assert.assertThat(solrContentStore.getRootLocation(), is(solrHome + "/" + SolrContentStore.CONTENT_STORE));
}
/**
* Generates a content context using a string. The URL part will be the same if the
* data provided is the same.
*/
private ContentContext createContentContext(String data)
{
return SolrContentUrlBuilder.start().add("data", data).getContentContext();
}
@Test
public void rootLocation()
{
SolrContentStore store = new SolrContentStore(solrHome);
File rootDir = new File(store.getRootLocation());
Assert.assertTrue(rootDir.exists());
Assert.assertTrue(rootDir.isDirectory());
File rootDir = new File(contentStore.getRootLocation());
assertTrue(rootDir.exists());
assertTrue(rootDir.isDirectory());
}
@Test
public void getWriter()
@Test
public void storeDocOnSolrContentStore()
{
SolrContentStore store = new SolrContentStore(solrHome);
contentStore.toggleReadOnlyMode(false);
ContentContext ctx = createContentContext("abc");
ContentWriter writer = store.getWriter(ctx);
String url = writer.getContentUrl();
SolrInputDocument doc = mock(SolrInputDocument.class);
long dbid = 111;
String tenant = "me";
SolrInputDocument document = contentStore.retrieveDocFromSolrContentStore(tenant, dbid);
Assert.assertNull(document);
Assert.assertNotNull(url);
Assert.assertEquals("URL of the context does not match the writer URL. ", ctx.getContentUrl(), url);
contentStore.storeDocOnSolrContentStore(tenant, dbid, doc);
document = contentStore.retrieveDocFromSolrContentStore(tenant, dbid);
Assert.assertNotNull(document);
}
@Test
public void contentByString()
@Test
public void storeDocOnSolrContentStoreNodeMetaData()
{
SolrContentStore store = new SolrContentStore(solrHome);
contentStore.toggleReadOnlyMode(false);
SolrInputDocument doc = mock(SolrInputDocument.class);
NodeMetaData nodeMetaData = mock(NodeMetaData.class);
SolrInputDocument document = contentStore.retrieveDocFromSolrContentStore(DEFAULT_TENANT, 0);
Assert.assertNull(document);
ContentContext ctx = createContentContext("abc");
ContentWriter writer = store.getWriter(ctx);
contentStore.storeDocOnSolrContentStore(nodeMetaData, doc);
document = contentStore.retrieveDocFromSolrContentStore(DEFAULT_TENANT, 0);
Assert.assertNotNull(document);
}
File file = new File(store.getRootLocation() + "/" + writer.getContentUrl().replace("solr://", ""));
Assert.assertFalse("File was created before anything was written", file.exists());
@Test
public void removeDocFromContentStore()
{
contentStore.toggleReadOnlyMode(false);
SolrInputDocument doc = mock(SolrInputDocument.class);
NodeMetaData nodeMetaData = mock(NodeMetaData.class);
contentStore.storeDocOnSolrContentStore(nodeMetaData, doc);
SolrInputDocument document = contentStore.retrieveDocFromSolrContentStore(DEFAULT_TENANT, 0);
Assert.assertNotNull(document);
String content = "Quick brown fox jumps over the lazy dog.";
writer.putContent(content);
Assert.assertTrue("File was not created.", file.exists());
contentStore.removeDocFromContentStore(nodeMetaData);
document = contentStore.retrieveDocFromSolrContentStore(DEFAULT_TENANT, 0);
Assert.assertNull(document);
}
private void expectIllegalState(Supplier<?> function)
{
try
{
writer.putContent("Should not work");
function.get();
fail();
}
catch (IllegalStateException e)
catch (IllegalStateException expected)
{
// Expected
// Nothing to do, this is expected
}
// Now get the reader
ContentReader reader = store.getReader(ctx.getContentUrl());
Assert.assertNotNull(reader);
Assert.assertTrue(reader.exists());
Assert.assertEquals(content, reader.getContentString());
}
@Test
public void contentByStream() throws Exception
private <T> void expectIllegalState(Consumer<T> function, T arg)
{
SolrContentStore store = new SolrContentStore(solrHome);
ContentContext ctx = createContentContext("abc");
ContentWriter writer = store.getWriter(ctx);
byte[] bytes = new byte[] { 1, 7, 13 };
ByteArrayInputStream bis = new ByteArrayInputStream(bytes);
writer.putContent(bis);
// Now get the reader
ContentReader reader = store.getReader(ctx.getContentUrl());
ByteArrayOutputStream bos = new ByteArrayOutputStream(3);
reader.getContent(bos);
Assert.assertEquals(bytes[0], bos.toByteArray()[0]);
Assert.assertEquals(bytes[1], bos.toByteArray()[1]);
Assert.assertEquals(bytes[2], bos.toByteArray()[2]);
try
{
function.accept(arg);
fail();
}
catch (IllegalStateException expected)
{
// Nothing to do, this is expected
}
}
@Test
public void delete() throws Exception
private <I,O> void expectIllegalState(Function<I,O> function, I arg)
{
SolrContentStore store = new SolrContentStore(solrHome);
ContentContext ctx = createContentContext("abc");
String url = ctx.getContentUrl();
ContentWriter writer = store.getWriter(ctx);
writer.putContent("Content goes here.");
// Check the reader
ContentReader reader = store.getReader(url);
Assert.assertNotNull(reader);
Assert.assertTrue(reader.exists());
// Delete
store.delete(url);
reader = store.getReader(url);
Assert.assertNotNull(reader);
Assert.assertFalse(reader.exists());
// Delete when already gone; should just not fail
store.delete(url);
try
{
function.apply(arg);
fail();
}
catch (IllegalStateException expected)
{
// Nothing to do, this is expected
}
}
/**
* A demonstration of how the store might be used.
*/
@Test
public void exampleUsage()
private <A, B> void expectIllegalState(BiConsumer<A, B> function, A arg1, B arg2)
{
SolrContentStore store = new SolrContentStore(solrHome);
String tenant = "alfresco.com";
long dbId = 12345;
String otherData = "sdfklsfdl";
ContentContext ctxWrite = SolrContentUrlBuilder.start()
.add(SolrContentUrlBuilder.KEY_DB_ID, String.valueOf(dbId)).add(SolrContentUrlBuilder.KEY_TENANT, tenant)
.add("otherData", otherData).getContentContext();
ContentWriter writer = store.getWriter(ctxWrite);
writer.putContent("a document in plain text");
// The URL can be reliably rebuilt in any order
String urlRead = SolrContentUrlBuilder.start().add("otherData", otherData)
.add(SolrContentUrlBuilder.KEY_TENANT, tenant).add(SolrContentUrlBuilder.KEY_DB_ID, String.valueOf(dbId))
.get();
ContentReader reader = store.getReader(urlRead);
String documentText = reader.getContentString();
Assert.assertEquals("a document in plain text", documentText);
}
@Test
public void storeDocOnSolrContentStore() throws IOException
{
SolrContentStore solrContentStore = new SolrContentStore(solrHome);
SolrInputDocument doc = Mockito.mock(SolrInputDocument.class);
SolrInputDocument document = solrContentStore.retrieveDocFromSolrContentStore(tenant, dbid);
Assert.assertNull(document);
solrContentStore.storeDocOnSolrContentStore(tenant, dbid, doc);
document = solrContentStore.retrieveDocFromSolrContentStore(tenant, dbid);
Assert.assertNotNull(document);
}
@Test
public void storeDocOnSolrContentStoreNodeMetaData() throws IOException
{
SolrContentStore solrContentStore = new SolrContentStore(solrHome);
SolrInputDocument doc = Mockito.mock(SolrInputDocument.class);
NodeMetaData nodeMetaData = Mockito.mock(NodeMetaData.class);
SolrInputDocument document = solrContentStore.retrieveDocFromSolrContentStore(DEFAULT_TENANT, 0);
Assert.assertNull(document);
solrContentStore.storeDocOnSolrContentStore(nodeMetaData, doc);
document = solrContentStore.retrieveDocFromSolrContentStore(DEFAULT_TENANT, 0);
Assert.assertNotNull(document);
}
@Test
public void removeDocFromContentStore() throws IOException
{
SolrContentStore solrContentStore = new SolrContentStore(solrHome);
SolrInputDocument doc = Mockito.mock(SolrInputDocument.class);
NodeMetaData nodeMetaData = Mockito.mock(NodeMetaData.class);
solrContentStore.storeDocOnSolrContentStore(nodeMetaData, doc);
SolrInputDocument document = solrContentStore.retrieveDocFromSolrContentStore(DEFAULT_TENANT, 0);
Assert.assertNotNull(document);
solrContentStore.removeDocFromContentStore(nodeMetaData);
document = solrContentStore.retrieveDocFromSolrContentStore(DEFAULT_TENANT, 0);
Assert.assertNull(document);
try
{
function.accept(arg1, arg2);
fail();
}
catch (IllegalStateException expected)
{
// Nothing to do, this is expected
}
}
}
@@ -21,7 +21,7 @@ package org.alfresco.solr.content;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.runners.MockitoJUnitRunner;
import org.mockito.junit.MockitoJUnitRunner;
/**
* Tests {@link SolrContentUrlBuilder}
@@ -85,12 +85,12 @@ public class SolrContentUrlBuilderTest
.add(SolrContentUrlBuilder.KEY_DB_ID, "12345")
.get();
Assert.assertEquals(url1, url1Check);
String url2 = SolrContentUrlBuilder
.start()
.add(SolrContentUrlBuilder.KEY_DB_ID, "123456")
.get();
Assert.assertNotEquals(url1, url2);
Assert.assertEquals("Incorrect URL: " + url1, "solr://default/db/1234/5.gz", url1);
}
@@ -108,7 +108,6 @@ public class SolrContentUrlBuilderTest
.add(SolrContentUrlBuilder.KEY_TENANT, "bob")
.get();
Assert.assertEquals(url1, url1Check);
Assert.assertEquals("Incorrect URL: " + url1, "solr://bob/db/1234/56.gz", url1);
}
@@ -126,7 +125,6 @@ public class SolrContentUrlBuilderTest
.add(SolrContentUrlBuilder.KEY_TENANT, "bob")
.get();
Assert.assertEquals(url1, url1Check);
Assert.assertEquals("Incorrect URL: " + url1, "solr://bob/acl/1234/56.gz", url1);
}
@@ -146,7 +144,6 @@ public class SolrContentUrlBuilderTest
.add(SolrContentUrlBuilder.KEY_TENANT, "bob")
.get();
Assert.assertEquals(url1, url1Check);
Assert.assertEquals("Incorrect URL: " + url1, "solr://bob/db/5432/1.gz", url1);
}
@@ -164,6 +161,7 @@ public class SolrContentUrlBuilderTest
{
// Expected
}
try
{
SolrContentUrlBuilder
@@ -176,6 +174,7 @@ public class SolrContentUrlBuilderTest
{
// Expected
}
try
{
SolrContentUrlBuilder
@@ -188,6 +187,7 @@ public class SolrContentUrlBuilderTest
{
// Expected
}
try
{
SolrContentUrlBuilder
@@ -200,6 +200,7 @@ public class SolrContentUrlBuilderTest
{
// Expected
}
try
{
SolrContentUrlBuilder
@@ -0,0 +1,86 @@
package org.alfresco.solr.content;
import org.alfresco.service.cmr.repository.ContentReader;
import org.alfresco.service.cmr.repository.ContentWriter;
import org.apache.commons.io.FileUtils;
import org.junit.After;
import org.junit.Assert;
import org.junit.Test;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
public class SolrContentWriterTest
{
private String solrHome = new File("./target/contentwriter/").getAbsolutePath();
@After
public void tearDown() throws IOException
{
File rootDir = new File(solrHome);
FileUtils.deleteDirectory(rootDir);
}
private ContentWriter getContentWriter(String name)
{
return new SolrFileContentWriter(new File(solrHome, name), solrHome + "/" + name);
}
private ContentReader getContentReader(String name)
{
return new SolrFileContentReader(new File(solrHome, name), solrHome + "/" + name);
}
@Test
public void contentByString()
{
String filename = "abc";
ContentWriter writer = getContentWriter(filename);
File file = new File(solrHome, filename);
Assert.assertFalse("File was created before anything was written", file.exists());
String content = "Quick brown fox jumps over the lazy dog.";
writer.putContent(content);
Assert.assertTrue("File was not created.", file.exists());
try
{
writer.putContent("Should not work");
}
catch (IllegalStateException e)
{
// Expected
}
// Now get the reader
ContentReader reader = getContentReader(filename);
Assert.assertNotNull(reader);
Assert.assertTrue(reader.exists());
Assert.assertEquals(content, reader.getContentString());
}
@Test
public void contentByStream()
{
String filename = "cbs";
ContentWriter writer = getContentWriter(filename);
byte[] bytes = new byte[] { 1, 7, 13 };
ByteArrayInputStream bis = new ByteArrayInputStream(bytes);
writer.putContent(bis);
// Now get the reader
ContentReader reader = getContentReader(filename);
ByteArrayOutputStream bos = new ByteArrayOutputStream(3);
reader.getContent(bos);
Assert.assertEquals(bytes[0], bos.toByteArray()[0]);
Assert.assertEquals(bytes[1], bos.toByteArray()[1]);
Assert.assertEquals(bytes[2], bos.toByteArray()[2]);
}
}
@@ -0,0 +1,286 @@
/*
* Copyright (C) 2005-2019 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco 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 3 of the License, or
* (at your option) any later version.
*
* Alfresco 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 Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.solr.handler;
import org.alfresco.solr.AbstractAlfrescoDistributedIT;
import org.alfresco.solr.client.Acl;
import org.alfresco.solr.client.AclChangeSet;
import org.alfresco.solr.client.AclReaders;
import org.alfresco.solr.client.Node;
import org.alfresco.solr.client.NodeMetaData;
import org.alfresco.solr.client.SOLRAPIQueueClient;
import org.alfresco.solr.client.Transaction;
import org.apache.commons.io.FileUtils;
import org.apache.hadoop.util.Time;
import org.apache.lucene.index.Term;
import org.apache.lucene.search.TermQuery;
import org.apache.solr.SolrTestCaseJ4;
import org.apache.solr.client.solrj.SolrClient;
import org.apache.solr.client.solrj.embedded.JettySolrRunner;
import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Properties;
import static java.util.Collections.singletonList;
import static org.alfresco.solr.AlfrescoSolrUtils.getAcl;
import static org.alfresco.solr.AlfrescoSolrUtils.getAclChangeSet;
import static org.alfresco.solr.AlfrescoSolrUtils.getAclReaders;
import static org.alfresco.solr.AlfrescoSolrUtils.getNode;
import static org.alfresco.solr.AlfrescoSolrUtils.getNodeMetaData;
import static org.alfresco.solr.AlfrescoSolrUtils.getTransaction;
import static org.alfresco.solr.AlfrescoSolrUtils.indexAclChangeSet;
import static org.alfresco.solr.utils.AlfrescoFileUtils.areDirectoryEquals;
import static org.carrot2.shaded.guava.common.collect.ImmutableList.of;
/**
* @author Elia Porciani
*
* This test check if the synchronization of contentstore between master and slave is done correctly.
*/
@SolrTestCaseJ4.SuppressSSL
public class ContentStoreReplicationIT extends AbstractAlfrescoDistributedIT
{
protected static JettySolrRunner master;
protected static JettySolrRunner slave;
protected static SolrClient masterClient;
protected static SolrClient slaveClient;
protected static Path masterSolrHome;
protected static Path slaveSolrHome;
protected static Path masterContentStore;
protected static Path slaveContentStore;
private static Acl acl;
private static final int MILLIS_TIMOUT = 80000;
@BeforeClass
public static void createMasterSlaveEnv() throws Exception
{
Properties properties = new Properties();
clientShards = new ArrayList<>();
solrShards = new ArrayList<>();
solrCollectionNameToStandaloneClient = new HashMap<>();
jettyContainers = new HashMap<>();
String coreName = "master";
boolean basicAuth = Boolean.parseBoolean(properties.getProperty("BasicAuth", "false"));
String masterDir = "master" + Time.now();
String slaveDir = "slave" + Time.now();
String masterKey = masterDir + "/solrHome";
String slaveKey = slaveDir + "/solrHome";
master = createJetty(masterKey, basicAuth);
addCoreToJetty(masterKey, coreName, coreName, null);
startJetty(master);
String slaveCoreName = "slave";
slave = createJetty(slaveKey, basicAuth);
addCoreToJetty(slaveKey, slaveCoreName, slaveCoreName, null);
setMasterUrl(slaveKey, slaveCoreName, master.getBaseUrl().toString() + "/master");
startJetty(slave);
String masterStr = buildUrl(master.getLocalPort()) + "/" + coreName;
String slaveStr = buildUrl(slave.getLocalPort()) + "/" + slaveCoreName;
masterClient = createNewSolrClient(masterStr);
slaveClient = createNewSolrClient(slaveStr);
masterSolrHome = testDir.toPath().resolve(masterKey);
slaveSolrHome = testDir.toPath().resolve(slaveKey);
masterContentStore = testDir.toPath().resolve(masterDir + "/contentstore");
slaveContentStore = testDir.toPath().resolve(slaveDir + "/contentstore");
AclChangeSet aclChangeSet = getAclChangeSet(1);
acl = getAcl(aclChangeSet);
AclReaders aclReaders = getAclReaders(aclChangeSet, acl, singletonList("joel"), singletonList("phil"), null);
indexAclChangeSet(aclChangeSet,
of(acl),
of(aclReaders));
}
@AfterClass
public static void cleanupMasterSlave() throws Exception
{
master.stop();
slave.stop();
FileUtils.forceDelete(new File(masterSolrHome.getParent().toUri()));
FileUtils.forceDelete(new File(slaveSolrHome.getParent().toUri()));
SOLRAPIQueueClient.nodeMetaDataMap.clear();
SOLRAPIQueueClient.transactionQueue.clear();
SOLRAPIQueueClient.aclChangeSetQueue.clear();
SOLRAPIQueueClient.aclReadersMap.clear();
SOLRAPIQueueClient.aclMap.clear();
SOLRAPIQueueClient.nodeMap.clear();
SOLRAPIQueueClient.nodeContentMap.clear();
}
@Test
public void contentStoreReplicationTest() throws Exception
{
// ADD 250 nodes and check they are replicated
int numNodes = 250;
Transaction bigTxn = getTransaction(0, numNodes);
List<Node> nodes = new ArrayList<>();
List<NodeMetaData> nodeMetaDatas = new ArrayList<>();
for(int i = 0; i<numNodes; i++) {
Node node = getNode(i, bigTxn, acl, Node.SolrApiNodeStatus.UPDATED);
nodes.add(node);
NodeMetaData nodeMetaData = getNodeMetaData(node, bigTxn, acl, "mike", null, false);
node.setNodeRef(nodeMetaData.getNodeRef().toString());
nodeMetaDatas.add(nodeMetaData);
}
indexTransaction(bigTxn, nodes, nodeMetaDatas);
waitForDocCountCore(masterClient,
luceneToSolrQuery(new TermQuery(new Term("content@s___t@{http://www.alfresco.org/model/content/1.0}content", "world"))),
numNodes, MILLIS_TIMOUT, System.currentTimeMillis());
long filesInMasterContentStore = Files.walk(Paths.get(masterContentStore.toUri().resolve("_DEFAULT_")))
.filter(Files::isRegularFile)
.count();
Assert.assertEquals( "master contentStore should have " + numNodes + "files", numNodes, filesInMasterContentStore);
assertTrue("slave content store is not in sync after timeout", waitForContentStoreSync(MILLIS_TIMOUT));
// ADD other 10 nodes
int numUpdates = 10;
int totalNodes = numNodes + numUpdates;
Transaction updateTx = getTransaction(0, numUpdates);
List<Node> updateNodes = new ArrayList<>();
List<NodeMetaData> updateNodeMetaDatas = new ArrayList<>();
for(int i = numNodes; i < totalNodes; i++) {
Node node = getNode(i, updateTx, acl, Node.SolrApiNodeStatus.UPDATED);
updateNodes.add(node);
NodeMetaData nodeMetaData = getNodeMetaData(node, updateTx, acl, "mike", null, false);
node.setNodeRef(nodeMetaData.getNodeRef().toString());
updateNodeMetaDatas.add(nodeMetaData);
}
indexTransaction(updateTx, updateNodes, updateNodeMetaDatas);
waitForDocCountCore(masterClient,
luceneToSolrQuery(new TermQuery(new Term("content@s___t@{http://www.alfresco.org/model/content/1.0}content", "world"))),
numNodes + numUpdates, MILLIS_TIMOUT, System.currentTimeMillis());
filesInMasterContentStore = Files.walk(Paths.get(masterContentStore.toUri().resolve("_DEFAULT_")))
.filter(Files::isRegularFile)
.count();
Assert.assertEquals( "master contentStore should have " + totalNodes + "files", totalNodes, filesInMasterContentStore);
assertTrue("slave content store is not in sync after timeout", waitForContentStoreSync(MILLIS_TIMOUT));
// DELETES 30 nodes
int numDeletes = 30;
Transaction deleteTx = getTransaction(numDeletes, 0);
totalNodes = numNodes + numUpdates - numDeletes;
List<Node> deleteNodes = new ArrayList<>();
List<NodeMetaData> deleteNodeMetaDatas = new ArrayList<>();
for(int i = 0; i<numDeletes; i++) {
Node node = getNode(i, deleteTx, acl, Node.SolrApiNodeStatus.DELETED);
deleteNodes.add(node);
NodeMetaData nodeMetaData = getNodeMetaData(node, deleteTx, acl, "mike", null, false);
node.setNodeRef(nodeMetaData.getNodeRef().toString());
deleteNodeMetaDatas.add(nodeMetaData);
}
indexTransaction(deleteTx, deleteNodes, deleteNodeMetaDatas);
waitForDocCountCore(masterClient,
luceneToSolrQuery(new TermQuery(new Term("content@s___t@{http://www.alfresco.org/model/content/1.0}content", "world"))),
totalNodes, MILLIS_TIMOUT, System.currentTimeMillis());
filesInMasterContentStore = Files.walk(Paths.get(masterContentStore.toUri().resolve("_DEFAULT_")))
.filter(Files::isRegularFile)
.count();
Assert.assertEquals( "master contentStore should have " + totalNodes + "files", totalNodes, filesInMasterContentStore);
assertTrue("slave content store is not in sync after timeout", waitForContentStoreSync(MILLIS_TIMOUT));
}
private static boolean waitForContentStoreSync(long waitMillis) throws InterruptedException
{
long startMillis = System.currentTimeMillis();
long timeout = startMillis + waitMillis;
long increment = 1;
while(new Date().getTime() < timeout)
{
try{
if (areDirectoryEquals(masterContentStore, slaveContentStore, new String[]{"gz"}, true))
{
return true;
}
} catch (Exception e){
// do nothing
}
Thread.sleep(500 * increment);
}
return false;
}
private static void setMasterUrl(String jettyKey, String coreName, String masterUrl) throws IOException
{
Path jettySolrHome = testDir.toPath().resolve(jettyKey);
Path coreHome = jettySolrHome.resolve(coreName);
Path confDir = coreHome.resolve("conf");
Path solrConfigPath = confDir.resolve("solrconfig.xml");
String content = new String(Files.readAllBytes(solrConfigPath));
content = content.replaceAll("\\{masterURL\\}", masterUrl);
Files.write(solrConfigPath, content.getBytes());
}
}
@@ -18,7 +18,7 @@
*/
package org.alfresco.solr.highlight;
import org.alfresco.solr.AbstractAlfrescoDistributedTest;
import org.alfresco.solr.AbstractAlfrescoDistributedIT;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.lucene.util.LuceneTestCase;
@@ -35,9 +35,9 @@ import org.junit.Test;
*/
@SolrTestCaseJ4.SuppressSSL
@LuceneTestCase.SuppressCodecs({"Appending","Lucene3x","Lucene40","Lucene41","Lucene42","Lucene43", "Lucene44", "Lucene45","Lucene46","Lucene47","Lucene48","Lucene49"})
public class AlfrescoHighligherDistributedTest extends AbstractAlfrescoDistributedTest
public class AlfrescoHighligherDistributedIT extends AbstractAlfrescoDistributedIT
{
private static Log logger = LogFactory.getLog(AlfrescoHighligherDistributedTest.class);
private static Log logger = LogFactory.getLog(AlfrescoHighligherDistributedIT.class);
@BeforeClass
private static void initData() throws Throwable
@@ -22,13 +22,9 @@ package org.alfresco.solr.highlight;
import java.util.Map;
import org.alfresco.model.ContentModel;
import org.alfresco.repo.search.adaptor.lucene.QueryConstants;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.namespace.QName;
import org.alfresco.solr.AbstractAlfrescoSolrTests;
import org.alfresco.solr.AlfrescoCoreAdminHandler;
import org.alfresco.solr.AbstractAlfrescoSolrIT;
import org.alfresco.solr.client.*;
import org.alfresco.solr.dataload.TestDataProvider;
import org.alfresco.util.Pair;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.lucene.index.Term;
@@ -37,39 +33,27 @@ import org.apache.lucene.search.BooleanQuery;
import org.apache.lucene.search.LegacyNumericRangeQuery;
import org.apache.lucene.search.TermQuery;
import org.apache.lucene.util.LuceneTestCase;
import org.apache.solr.client.solrj.response.QueryResponse;
import org.apache.solr.common.params.HighlightParams;
import org.apache.solr.common.params.ModifiableSolrParams;
import org.apache.solr.response.BasicResultContext;
import org.apache.solr.response.SolrQueryResponse;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Random;
import static com.google.common.collect.ImmutableMap.of;
import static java.util.Arrays.asList;
import static java.util.stream.IntStream.range;
import static junit.framework.TestCase.assertTrue;
import static org.alfresco.model.ContentModel.PROP_CREATOR;
import static org.alfresco.model.ContentModel.PROP_RATING_SCHEME;
import static org.alfresco.model.ContentModel.TYPE_CONTENT;
import static org.alfresco.solr.AlfrescoSolrUtils.*;
import static org.apache.solr.SolrJettyTestBase.jetty;
import static org.junit.Assert.assertNotNull;
@LuceneTestCase.SuppressCodecs({"Appending","Lucene3x","Lucene40","Lucene41","Lucene42","Lucene43", "Lucene44", "Lucene45","Lucene46","Lucene47","Lucene48","Lucene49"})
public class AlfrescoHighlighterTest extends AbstractAlfrescoSolrTests
public class AlfrescoHighlighterIT extends AbstractAlfrescoSolrIT
{
private static Log logger = LogFactory.getLog(AlfrescoHighlighterTest.class);
private static Log logger = LogFactory.getLog(AlfrescoHighlighterIT.class);
private static long MAX_WAIT_TIME = 80000;
@BeforeClass
@@ -1,176 +0,0 @@
/*
* 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.alfresco.solr.highlight;
import org.alfresco.solr.AbstractAlfrescoSolrTests;
import org.apache.lucene.util.LuceneTestCase;
import org.apache.solr.SolrTestCaseJ4;
import org.apache.solr.handler.component.AlfrescoSolrHighlighter;
import org.apache.solr.handler.component.HighlightComponent;
import org.apache.solr.highlight.PostingsSolrHighlighter;
import org.apache.solr.highlight.SolrHighlighter;
import org.apache.solr.schema.IndexSchema;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import static junit.framework.TestCase.*;
@LuceneTestCase.SuppressCodecs({"Appending","Lucene3x","Lucene40","Lucene41","Lucene42","Lucene43", "Lucene44", "Lucene45","Lucene46","Lucene47","Lucene48","Lucene49"})
public class TestPostingsSolrHighlighter extends AbstractAlfrescoSolrTests
{
@BeforeClass
public static void beforeClass() throws Exception
{
initAlfrescoCore("schema.xml");
// test our config is sane, just to be sure:
// postingshighlighter should be used
SolrHighlighter highlighter = HighlightComponent.getHighlighter(getCore());
assertTrue("wrong highlighter: " + highlighter.getClass(), highlighter instanceof AlfrescoSolrHighlighter);
// 'text' and 'text3' should have offsets, 'text2' should not
IndexSchema schema = getCore().getLatestSchema();
assertTrue(schema.getField("text").storeOffsetsWithPositions());
assertTrue(schema.getField("text3").storeOffsetsWithPositions());
assertFalse(schema.getField("text2").storeOffsetsWithPositions());
}
@Before
public void setUp() throws Exception
{
// if you override setUp or tearDown, you better call
// the super classes version
clearIndex();
assertU(adoc("text", "document one", "text2", "document one", "text3", "crappy document", "id", "101"));
assertU(adoc("text", "second document", "text2", "second document", "text3", "crappier document", "id", "102"));
assertU(commit());
}
@Test
public void testSimple() {
assertQ("simplest test",
req("q", "text:document", "sort", "id asc", "hl", "true"),
"count(//lst[@name='highlighting']/*)=2",
"//lst[@name='highlighting']/lst[@name='101']/arr[@name='text']/str='<em>document</em> one'",
"//lst[@name='highlighting']/lst[@name='102']/arr[@name='text']/str='second <em>document</em>'");
}
public void testPagination() {
assertQ("pagination test",
req("q", "text:document", "sort", "id asc", "hl", "true", "rows", "1", "start", "1"),
"count(//lst[@name='highlighting']/*)=1",
"//lst[@name='highlighting']/lst[@name='102']/arr[@name='text']/str='second <em>document</em>'");
}
public void testEmptySnippet() {
assertQ("null snippet test",
req("q", "text:one OR *:*", "sort", "id asc", "hl", "true"),
"count(//lst[@name='highlighting']/*)=2",
"//lst[@name='highlighting']/lst[@name='101']/arr[@name='text']/str='document <em>one</em>'",
"count(//lst[@name='highlighting']/lst[@name='102']/arr[@name='text']/*)=0");
}
public void testDefaultSummary() {
assertQ("null snippet test",
req("q", "text:one OR *:*", "sort", "id asc", "hl", "true", "hl.defaultSummary", "true"),
"count(//lst[@name='highlighting']/*)=2",
"//lst[@name='highlighting']/lst[@name='101']/arr[@name='text']/str='document <em>one</em>'",
"//lst[@name='highlighting']/lst[@name='102']/arr[@name='text']/str='second document'");
}
public void testDifferentField() {
assertQ("highlighting text3",
req("q", "text3:document", "sort", "id asc", "hl", "true", "hl.fl", "text3"),
"count(//lst[@name='highlighting']/*)=2",
"//lst[@name='highlighting']/lst[@name='101']/arr[@name='text3']/str='crappy <em>document</em>'",
"//lst[@name='highlighting']/lst[@name='102']/arr[@name='text3']/str='crappier <em>document</em>'");
}
public void testTwoFields() {
assertQ("highlighting text and text3",
req("q", "text:document text3:document", "sort", "id asc", "hl", "true", "hl.fl", "text,text3"),
"count(//lst[@name='highlighting']/*)=2",
"//lst[@name='highlighting']/lst[@name='101']/arr[@name='text']/str='<em>document</em> one'",
"//lst[@name='highlighting']/lst[@name='101']/arr[@name='text3']/str='crappy <em>document</em>'",
"//lst[@name='highlighting']/lst[@name='102']/arr[@name='text']/str='second <em>document</em>'",
"//lst[@name='highlighting']/lst[@name='102']/arr[@name='text3']/str='crappier <em>document</em>'");
}
/**
public void testMisconfiguredField() {
ignoreException("was indexed without offsets");
try {
assertQ("should fail, has no offsets",
req("q", "text2:document", "sort", "id asc", "hl", "true", "hl.fl", "text2"));
fail();
} catch (Exception expected) {
// expected
}
resetExceptionIgnores();
}
**/
public void testTags() {
assertQ("different pre/post tags",
req("q", "text:document", "sort", "id asc", "hl", "true", "hl.tag.pre", "[", "hl.tag.post", "]"),
"count(//lst[@name='highlighting']/*)=2",
"//lst[@name='highlighting']/lst[@name='101']/arr[@name='text']/str='[document] one'",
"//lst[@name='highlighting']/lst[@name='102']/arr[@name='text']/str='second [document]'");
}
public void testTagsPerField() {
assertQ("highlighting text and text3",
req("q", "text:document text3:document", "sort", "id asc", "hl", "true", "hl.fl", "text,text3", "f.text3.hl.tag.pre", "[", "f.text3.hl.tag.post", "]"),
"count(//lst[@name='highlighting']/*)=2",
"//lst[@name='highlighting']/lst[@name='101']/arr[@name='text']/str='<em>document</em> one'",
"//lst[@name='highlighting']/lst[@name='101']/arr[@name='text3']/str='crappy [document]'",
"//lst[@name='highlighting']/lst[@name='102']/arr[@name='text']/str='second <em>document</em>'",
"//lst[@name='highlighting']/lst[@name='102']/arr[@name='text3']/str='crappier [document]'");
}
public void testBreakIterator() {
assertQ("different breakiterator",
req("q", "text:document", "sort", "id asc", "hl", "true", "hl.bs.type", "WORD"),
"count(//lst[@name='highlighting']/*)=2",
"//lst[@name='highlighting']/lst[@name='101']/arr[@name='text']/str='<em>document</em>'",
"//lst[@name='highlighting']/lst[@name='102']/arr[@name='text']/str='<em>document</em>'");
}
public void testBreakIterator2() {
assertU(adoc("text", "Document one has a first sentence. Document two has a second sentence.", "id", "103"));
assertU(commit());
assertQ("different breakiterator",
req("q", "text:document", "sort", "id asc", "hl", "true", "hl.bs.type", "WHOLE"),
"//lst[@name='highlighting']/lst[@name='103']/arr[@name='text']/str='<em>Document</em> one has a first sentence. <em>Document</em> two has a second sentence.'");
}
public void testEncoder() {
assertU(adoc("text", "Document one has a first <i>sentence</i>.", "id", "103"));
assertU(commit());
assertQ("html escaped",
req("q", "text:document", "sort", "id asc", "hl", "true", "hl.encoder", "html"),
"//lst[@name='highlighting']/lst[@name='103']/arr[@name='text']/str='<em>Document</em>&#32;one&#32;has&#32;a&#32;first&#32;&lt;i&gt;sentence&lt;&#x2F;i&gt;&#46;'");
}
public void testWildcard() {
assertQ("simplest test",
req("q", "text:doc*ment", "sort", "id asc", "hl", "true", "hl.highlightMultiTerm", "true"),
"count(//lst[@name='highlighting']/*)=2",
"//lst[@name='highlighting']/lst[@name='101']/arr[@name='text']/str='<em>document</em> one'",
"//lst[@name='highlighting']/lst[@name='102']/arr[@name='text']/str='second <em>document</em>'");
}
}
@@ -22,7 +22,7 @@ package org.alfresco.solr.query;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import org.alfresco.solr.AbstractAlfrescoSolrTests;
import org.alfresco.solr.AbstractAlfrescoSolrIT;
import org.apache.lucene.util.LuceneTestCase;
import org.apache.solr.common.params.ModifiableSolrParams;
import org.apache.solr.common.util.NamedList;
@@ -38,7 +38,7 @@ import org.junit.Test;
@LuceneTestCase.SuppressCodecs({"Appending","Lucene3x","Lucene40","Lucene41","Lucene42","Lucene43", "Lucene44", "Lucene45","Lucene46","Lucene47","Lucene48","Lucene49"})
public class AlfrescoReRankQParserPluginTest extends AbstractAlfrescoSolrTests
public class AlfrescoReRankQParserPluginIT extends AbstractAlfrescoSolrIT
{
@BeforeClass
public static void beforeClass() throws Exception
@@ -21,7 +21,7 @@ package org.alfresco.solr.query;
import org.alfresco.model.ContentModel;
import org.alfresco.repo.search.adaptor.lucene.QueryConstants;
import org.alfresco.solr.AbstractAlfrescoSolrTests;
import org.alfresco.solr.AbstractAlfrescoSolrIT;
import org.alfresco.solr.client.Acl;
import org.alfresco.solr.client.AclChangeSet;
import org.alfresco.solr.client.AclReaders;
@@ -51,7 +51,7 @@ import static java.util.Collections.singletonList;
import static java.util.stream.IntStream.range;
import static org.alfresco.solr.AlfrescoSolrUtils.*;
public class AlfrescoSolrFingerprintTest extends AbstractAlfrescoSolrTests
public class AlfrescoSolrFingerprintIT extends AbstractAlfrescoSolrIT
{
private static long MAX_WAIT_TIME = 80000;
@@ -19,31 +19,17 @@
package org.alfresco.solr.query;
import static org.junit.Assert.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import org.alfresco.solr.AbstractAlfrescoSolrTests;
import org.alfresco.solr.AbstractAlfrescoSolrIT;
import org.apache.lucene.util.LuceneTestCase;
import org.apache.lucene.util.RefCount;
import org.apache.solr.common.params.ModifiableSolrParams;
import org.apache.solr.common.util.NamedList;
import org.apache.solr.request.SolrQueryRequest;
import org.apache.solr.response.ResultContext;
import org.apache.solr.response.SolrQueryResponse;
import org.apache.solr.search.DocIterator;
import org.apache.solr.search.DocList;
import org.apache.solr.search.SolrIndexSearcher;
import org.apache.solr.util.RefCounted;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
@LuceneTestCase.SuppressCodecs({"Appending","Lucene3x","Lucene40","Lucene41","Lucene42","Lucene43", "Lucene44", "Lucene45","Lucene46","Lucene47","Lucene48","Lucene49"})
public class AlfrescoSolrSpellcheckerTest extends AbstractAlfrescoSolrTests
public class AlfrescoSolrSpellcheckerIT extends AbstractAlfrescoSolrIT
{
@BeforeClass
public static void beforeClass() throws Exception
@@ -34,7 +34,7 @@ import org.alfresco.service.cmr.repository.StoreRef;
import org.alfresco.service.cmr.repository.datatype.DefaultTypeConverter;
import org.alfresco.service.namespace.NamespaceService;
import org.alfresco.service.namespace.QName;
import org.alfresco.solr.AbstractAlfrescoSolrTests;
import org.alfresco.solr.AbstractAlfrescoSolrIT;
import org.alfresco.solr.client.ContentPropertyValue;
import org.alfresco.solr.client.MLTextPropertyValue;
import org.alfresco.solr.client.PropertyValue;
@@ -46,7 +46,7 @@ import org.junit.BeforeClass;
* @author Michael Suzuki
*
*/
public class AuthDataLoad extends AbstractAlfrescoSolrTests
public class AuthDataLoad extends AbstractAlfrescoSolrIT
{
static int count = 100;
static long maxReader = 1000;
@@ -39,7 +39,7 @@ import org.junit.Test;
* @author Michael Suzuki
*
*/
public class AuthQueryTest extends AuthDataLoad
public class AuthQueryIT extends AuthDataLoad
{
@Test
public void checkAuth()
@@ -109,7 +109,6 @@ public class AuthQueryTest extends AuthDataLoad
searchParameters.setQuery(queryString);
Query query = dataModel.getFTSQuery(new Pair<SearchParameters, Boolean>(searchParameters, Boolean.FALSE),
solrQueryRequest, FTSQueryParser.RerankPhase.SINGLE_PASS);
System.out.println("##################### Query:"+query);
TopDocs docs = solrIndexSearcher.search(query, count * 2 + 10);
Assert.assertEquals(count, docs.totalHits);
@@ -18,7 +18,7 @@
*/
package org.alfresco.solr.query;
import org.alfresco.solr.AbstractAlfrescoDistributedTest;
import org.alfresco.solr.AbstractAlfrescoDistributedIT;
import org.apache.lucene.util.LuceneTestCase;
import org.apache.solr.SolrTestCaseJ4;
import org.apache.solr.client.solrj.response.FacetField;
@@ -37,12 +37,12 @@ import static org.hamcrest.core.Is.is;
@SolrTestCaseJ4.SuppressSSL @LuceneTestCase.SuppressCodecs({ "Appending", "Lucene3x", "Lucene40", "Lucene41",
"Lucene42", "Lucene43", "Lucene44", "Lucene45", "Lucene46", "Lucene47", "Lucene48",
"Lucene49" }) public class DistributedAlfrescoSolrFacetingTest extends AbstractAlfrescoDistributedTest
"Lucene49" }) public class DistributedAlfrescoSolrFacetingIT extends AbstractAlfrescoDistributedIT
{
@BeforeClass
private static void initData() throws Throwable
{
initSolrServers(2, "DistributedAlfrescoSolrFacetingTest", null);
initSolrServers(2, "DistributedAlfrescoSolrFacetingIT", null);
indexSampleDocumentsForFacetingMincount();
}
@@ -34,7 +34,7 @@ import java.util.Random;
import org.alfresco.model.ContentModel;
import org.alfresco.repo.search.adaptor.lucene.QueryConstants;
import org.alfresco.solr.AbstractAlfrescoDistributedTest;
import org.alfresco.solr.AbstractAlfrescoDistributedIT;
import org.alfresco.solr.client.Acl;
import org.alfresco.solr.client.AclChangeSet;
import org.alfresco.solr.client.AclReaders;
@@ -61,7 +61,7 @@ import org.junit.Test;
*/
@SolrTestCaseJ4.SuppressSSL
@LuceneTestCase.SuppressCodecs({"Appending","Lucene3x","Lucene40","Lucene41","Lucene42","Lucene43", "Lucene44", "Lucene45","Lucene46","Lucene47","Lucene48","Lucene49"})
public class DistributedAlfrescoSolrFingerPrintTest extends AbstractAlfrescoDistributedTest
public class DistributedAlfrescoSolrFingerPrintIT extends AbstractAlfrescoDistributedIT
{
private static long MAX_WAIT_TIME = 80000;
@@ -18,7 +18,7 @@
*/
package org.alfresco.solr.query;
import org.alfresco.solr.AbstractAlfrescoDistributedTest;
import org.alfresco.solr.AbstractAlfrescoDistributedIT;
import org.apache.lucene.util.LuceneTestCase;
import org.apache.solr.SolrTestCaseJ4;
import org.apache.solr.client.solrj.response.QueryResponse;
@@ -32,7 +32,7 @@ import org.junit.Test;
*/
@SolrTestCaseJ4.SuppressSSL
@LuceneTestCase.SuppressCodecs({"Appending","Lucene3x","Lucene40","Lucene41","Lucene42","Lucene43", "Lucene44", "Lucene45","Lucene46","Lucene47","Lucene48","Lucene49"})
public class DistributedAlfrescoSolrSpellcheckerTest extends AbstractAlfrescoDistributedTest
public class DistributedAlfrescoSolrSpellcheckerIT extends AbstractAlfrescoDistributedIT
{
@BeforeClass
private static void initData() throws Throwable
@@ -20,19 +20,18 @@
package org.alfresco.solr.query;
import org.alfresco.repo.search.adaptor.lucene.QueryConstants;
import org.alfresco.solr.AbstractAlfrescoSolrTests;
import org.alfresco.solr.AbstractAlfrescoSolrIT;
import org.apache.lucene.util.LuceneTestCase;
import org.apache.solr.SolrTestCaseJ4;
import org.apache.solr.common.params.ModifiableSolrParams;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runners.Parameterized.Parameters;
@LuceneTestCase.SuppressCodecs({"Appending","Lucene3x","Lucene40","Lucene41","Lucene42","Lucene43", "Lucene44", "Lucene45","Lucene46","Lucene47","Lucene48","Lucene49"})
@SolrTestCaseJ4.SuppressSSL
public class SolrAuthTest extends AbstractAlfrescoSolrTests
public class SolrAuthIT extends AbstractAlfrescoSolrIT
{
@BeforeClass
@@ -18,9 +18,7 @@
*/
package org.alfresco.solr.query;
import org.alfresco.model.ContentModel;
import org.alfresco.service.cmr.search.GeneralHighlightParameters;
import org.alfresco.solr.AbstractAlfrescoSolrTests;
import org.alfresco.solr.AbstractAlfrescoSolrIT;
import org.junit.Before;
import org.junit.Test;
@@ -32,7 +30,7 @@ import org.junit.Test;
* @author Michael Suzuki
*
*/
public class UntokenisedFieldTest extends AbstractAlfrescoSolrTests
public class UntokenisedFieldIT extends AbstractAlfrescoSolrIT
{
String nodeRef = "workspace://SpacesStore/00000000-0000-1-4731-76966678";
String nodeRefS = "noderef@s_@mytest" ;
@@ -18,25 +18,16 @@
*/
package org.alfresco.solr.query.afts.qparser;
import static java.util.Arrays.asList;
import static java.util.stream.IntStream.range;
import org.alfresco.solr.AbstractAlfrescoSolrTests;
import org.alfresco.solr.AbstractAlfrescoSolrIT;
import org.junit.BeforeClass;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
* Supertype layer for all AFTS QParser tests.
*
* @author Andrea Gazzarini
*/
public abstract class AbstractQParserPluginTest extends AbstractAlfrescoSolrTests
public abstract class AbstractQParserPluginIT extends AbstractAlfrescoSolrIT
{
@BeforeClass
public static void spinUpSolr() throws Exception
@@ -24,7 +24,7 @@ import org.alfresco.util.ISO9075;
import org.junit.BeforeClass;
import org.junit.Test;
public class FieldNameEscapingTest extends AbstractQParserPluginTest implements QueryConstants
public class FieldNameEscapingIT extends AbstractQParserPluginIT implements QueryConstants
{
private static TestDataProvider DATASETS_PROVIDER;
@@ -43,7 +43,7 @@ import org.junit.BeforeClass;
import org.junit.Test;
@SolrTestCaseJ4.SuppressSSL
public class QParserPluginTest extends AbstractQParserPluginTest implements QueryConstants
public class QParserPluginIT extends AbstractQParserPluginIT implements QueryConstants
{
/** The UTC time zone. */
private static final TimeZone UTC = TimeZone.getTimeZone("UTC");
@@ -49,7 +49,7 @@ import static com.google.common.collect.ImmutableMap.of;
* This test set checks that in all these query types the default fields are involved in the search.
*
*/
public class AFTSDefaultTextQueryTest extends AbstractRequestHandlerTest
public class AFTSDefaultTextQueryIT extends AbstractRequestHandlerIT
{
@BeforeClass
@@ -39,7 +39,7 @@ import org.junit.Test;
*
* @author msuzuki
*/
public class AFTSDisjunctionTest extends AbstractRequestHandlerTest
public class AFTSDisjunctionIT extends AbstractRequestHandlerIT
{
@BeforeClass
public static void beforeClass() throws Exception
@@ -43,7 +43,7 @@ import java.util.Map;
* @author eporciani
* @author agazzarini
*/
public class AFTSIdentifierFieldsTest extends AbstractRequestHandlerTest
public class AFTSIdentifierFieldsIT extends AbstractRequestHandlerIT
{
@BeforeClass
public static void beforeClass() throws Exception
@@ -41,7 +41,7 @@ import java.util.Map;
*
* @author elia
*/
public class AFTSRangeQueryTest extends AbstractRequestHandlerTest
public class AFTSRangeQueryIT extends AbstractRequestHandlerIT
{
@BeforeClass
public static void beforeClass() throws Exception
@@ -20,7 +20,7 @@ import java.util.Locale;
@LuceneTestCase.SuppressCodecs({"Appending","Lucene3x","Lucene40","Lucene41","Lucene42","Lucene43", "Lucene44", "Lucene45","Lucene46","Lucene47","Lucene48","Lucene49"})
@SolrTestCaseJ4.SuppressSSL
public class AFTSRequestHandlerTest extends AbstractRequestHandlerTest implements QueryConstants
public class AFTSRequestHandlerIT extends AbstractRequestHandlerIT implements QueryConstants
{
private static TestDataProvider DATASETS_PROVIDER;
@@ -1,6 +1,6 @@
package org.alfresco.solr.query.afts.requestHandler;
import org.alfresco.solr.AbstractAlfrescoSolrTests;
import org.alfresco.solr.AbstractAlfrescoSolrIT;
import org.junit.BeforeClass;
import java.util.ArrayList;
@@ -12,7 +12,7 @@ import java.util.stream.Stream;
import static java.util.Arrays.asList;
import static java.util.stream.IntStream.range;
public abstract class AbstractRequestHandlerTest extends AbstractAlfrescoSolrTests
public abstract class AbstractRequestHandlerIT extends AbstractAlfrescoSolrIT
{
@BeforeClass
public static void spinUpSolr() throws Exception
@@ -9,7 +9,7 @@ import org.junit.Test;
*
* @author Andrea Gazzarini
*/
public class MNTTest extends AbstractRequestHandlerTest
public class MNTIT extends AbstractRequestHandlerIT
{
@BeforeClass
public static void loadData() throws Exception
@@ -27,7 +27,7 @@ import org.junit.Test;
@LuceneTestCase.SuppressCodecs({"Appending","Lucene3x","Lucene40","Lucene41","Lucene42","Lucene43", "Lucene44", "Lucene45","Lucene46","Lucene47","Lucene48","Lucene49"})
@SolrTestCaseJ4.SuppressSSL
public class AlfrescoCMISQParserPluginTest extends LoadCMISData implements QueryConstants
public class AlfrescoCMISQParserPluginIT extends LoadCMISData implements QueryConstants
{
@Test
public void cmisBasic() throws Exception
@@ -31,7 +31,7 @@ import org.junit.Test;
* @author Michael Suzuki
*
*/
public class CmisTest extends LoadCMISData
public class CmisIT extends LoadCMISData
{
@Before
public void setup() throws Exception
@@ -37,7 +37,7 @@ import org.alfresco.service.cmr.repository.StoreRef;
import org.alfresco.service.cmr.repository.datatype.DefaultTypeConverter;
import org.alfresco.service.namespace.NamespaceService;
import org.alfresco.service.namespace.QName;
import org.alfresco.solr.AbstractAlfrescoSolrTests;
import org.alfresco.solr.AbstractAlfrescoSolrIT;
import org.alfresco.solr.AlfrescoSolrDataModel;
import org.alfresco.solr.client.ContentPropertyValue;
import org.alfresco.solr.client.MLTextPropertyValue;
@@ -51,7 +51,7 @@ import org.junit.BeforeClass;
* @author Michael Suzuki
*
*/
public class LoadCMISData extends AbstractAlfrescoSolrTests
public class LoadCMISData extends AbstractAlfrescoSolrIT
{
protected static NodeRef testCMISContent00NodeRef;
protected static NodeRef testCMISRootNodeRef;
@@ -39,7 +39,7 @@ import java.util.Map;
import java.util.Properties;
@RunWith(MockitoJUnitRunner.class)
public class AclModCountRouterTest
public class AclModCountRouterIT
{
private DocRouter router;
@@ -36,7 +36,7 @@ import org.alfresco.model.ContentModel;
import org.alfresco.repo.search.adaptor.lucene.QueryConstants;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.StoreRef;
import org.alfresco.solr.AbstractAlfrescoSolrTests;
import org.alfresco.solr.AbstractAlfrescoSolrIT;
import org.alfresco.solr.client.Acl;
import org.alfresco.solr.client.AclChangeSet;
import org.alfresco.solr.client.AclReaders;
@@ -62,9 +62,9 @@ import org.junit.Test;
@LuceneTestCase.SuppressCodecs({"Appending","Lucene3x","Lucene40","Lucene41","Lucene42","Lucene43", "Lucene44", "Lucene45","Lucene46","Lucene47","Lucene48","Lucene49"})
@SolrTestCaseJ4.SuppressSSL
public class AlfrescoSolrTrackerExceptionTest extends AbstractAlfrescoSolrTests
public class AlfrescoSolrTrackerExceptionIT extends AbstractAlfrescoSolrIT
{
private static Log logger = LogFactory.getLog(AlfrescoSolrTrackerTest.class);
private static Log logger = LogFactory.getLog(AlfrescoSolrTrackerExceptionIT.class);
private static long MAX_WAIT_TIME = 80000;
@BeforeClass
public static void beforeClass() throws Exception
@@ -37,7 +37,7 @@ import org.alfresco.model.ContentModel;
import org.alfresco.repo.search.adaptor.lucene.QueryConstants;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.StoreRef;
import org.alfresco.solr.AbstractAlfrescoSolrTests;
import org.alfresco.solr.AbstractAlfrescoSolrIT;
import org.alfresco.solr.client.Acl;
import org.alfresco.solr.client.AclChangeSet;
import org.alfresco.solr.client.AclReaders;
@@ -64,9 +64,9 @@ import org.junit.Test;
@LuceneTestCase.SuppressCodecs({"Appending","Lucene3x","Lucene40","Lucene41","Lucene42","Lucene43", "Lucene44", "Lucene45","Lucene46","Lucene47","Lucene48","Lucene49"})
@SolrTestCaseJ4.SuppressSSL
public class AlfrescoSolrTrackerTest extends AbstractAlfrescoSolrTests
public class AlfrescoSolrTrackerIT extends AbstractAlfrescoSolrIT
{
private static Log logger = LogFactory.getLog(AlfrescoSolrTrackerTest.class);
private static Log logger = LogFactory.getLog(AlfrescoSolrTrackerIT.class);
private static long MAX_WAIT_TIME = 80000;
@BeforeClass
public static void beforeClass() throws Exception
@@ -32,7 +32,7 @@ import java.util.Collection;
import org.alfresco.repo.search.adaptor.lucene.QueryConstants;
import org.alfresco.solr.AbstractAlfrescoSolrTests;
import org.alfresco.solr.AbstractAlfrescoSolrIT;
import org.alfresco.solr.AlfrescoCoreAdminHandler;
import org.alfresco.solr.client.Acl;
@@ -58,9 +58,9 @@ import org.junit.Test;
@LuceneTestCase.SuppressCodecs({"Appending","Lucene3x","Lucene40","Lucene41","Lucene42","Lucene43", "Lucene44", "Lucene45","Lucene46","Lucene47","Lucene48","Lucene49"})
@SolrTestCaseJ4.SuppressSSL
public class AlfrescoSolrTrackerRollbackTest extends AbstractAlfrescoSolrTests
public class AlfrescoSolrTrackerRollbackIT extends AbstractAlfrescoSolrIT
{
private static Log logger = LogFactory.getLog(AlfrescoSolrTrackerTest.class);
private static Log logger = LogFactory.getLog(AlfrescoSolrTrackerIT.class);
private static long MAX_WAIT_TIME = 80000;
@BeforeClass
public static void beforeClass() throws Exception
@@ -36,7 +36,7 @@ import static org.junit.Assert.assertNotEquals;
import org.alfresco.repo.index.shard.ShardState;
import org.alfresco.repo.search.adaptor.lucene.QueryConstants;
import org.alfresco.solr.AbstractAlfrescoSolrTests;
import org.alfresco.solr.AbstractAlfrescoSolrIT;
import org.alfresco.solr.AlfrescoCoreAdminHandler;
import org.alfresco.solr.client.Acl;
import org.alfresco.solr.client.AclChangeSet;
@@ -70,7 +70,7 @@ import java.util.stream.Collectors;
*
* @author agazzarini
*/
public class AlfrescoSolrTrackerStateTest extends AbstractAlfrescoSolrTests
public class AlfrescoSolrTrackerStateIT extends AbstractAlfrescoSolrIT
{
@BeforeClass
public static void beforeClass() throws Exception
@@ -35,7 +35,7 @@ import org.alfresco.model.ContentModel;
import org.alfresco.repo.search.adaptor.lucene.QueryConstants;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.StoreRef;
import org.alfresco.solr.AbstractAlfrescoSolrTests;
import org.alfresco.solr.AbstractAlfrescoSolrIT;
import org.alfresco.solr.client.*;
import org.apache.lucene.index.Term;
import org.apache.lucene.search.*;
@@ -52,7 +52,7 @@ import java.util.List;
@LuceneTestCase.SuppressCodecs({"Appending","Lucene3x","Lucene40","Lucene41","Lucene42","Lucene43", "Lucene44", "Lucene45","Lucene46","Lucene47","Lucene48","Lucene49"})
@SolrTestCaseJ4.SuppressSSL
public class CascadeTrackerTest extends AbstractAlfrescoSolrTests
public class CascadeTrackerIT extends AbstractAlfrescoSolrIT
{
private static long MAX_WAIT_TIME = 80000;
@@ -38,7 +38,7 @@ import org.mockito.Spy;
import org.mockito.runners.MockitoJUnitRunner;
@RunWith(MockitoJUnitRunner.class)
public class ContentTrackerTest
public class ContentTrackerIT
{
private ContentTracker contentTracker;
@@ -46,7 +46,7 @@ import java.util.Properties;
import java.util.Random;
@RunWith(MockitoJUnitRunner.class)
public class DateMonthRouterTest
public class DateMonthRouterIT
{
private Random randomizer = new Random();
@@ -19,7 +19,7 @@
package org.alfresco.solr.tracker;
import org.alfresco.repo.index.shard.ShardMethodEnum;
import org.alfresco.solr.AbstractAlfrescoDistributedTest;
import org.alfresco.solr.AbstractAlfrescoDistributedIT;
import org.alfresco.solr.SolrInformationServer;
import org.alfresco.solr.client.Acl;
import org.alfresco.solr.client.AclChangeSet;
@@ -57,7 +57,7 @@ import static org.alfresco.solr.AlfrescoSolrUtils.list;
*/
@SolrTestCaseJ4.SuppressSSL
@LuceneTestCase.SuppressCodecs({"Appending","Lucene3x","Lucene40","Lucene41","Lucene42","Lucene43", "Lucene44", "Lucene45","Lucene46","Lucene47","Lucene48","Lucene49"})
public class DistributedAclIdAlfrescoSolrTrackerTest extends AbstractAlfrescoDistributedTest
public class DistributedAclIdAlfrescoSolrTrackerIT extends AbstractAlfrescoDistributedIT
{
@BeforeClass
@@ -19,7 +19,7 @@
package org.alfresco.solr.tracker;
import org.alfresco.repo.search.adaptor.lucene.QueryConstants;
import org.alfresco.solr.AbstractAlfrescoDistributedTest;
import org.alfresco.solr.AbstractAlfrescoDistributedIT;
import org.alfresco.solr.client.Acl;
import org.alfresco.solr.client.AclChangeSet;
import org.alfresco.solr.client.Node;
@@ -50,7 +50,7 @@ import static org.alfresco.solr.AlfrescoSolrUtils.getTransaction;
*/
@SolrTestCaseJ4.SuppressSSL
@LuceneTestCase.SuppressCodecs({"Appending","Lucene3x","Lucene40","Lucene41","Lucene42","Lucene43", "Lucene44", "Lucene45","Lucene46","Lucene47","Lucene48","Lucene49"})
public class DistributedAlfrescoSolrJsonTest extends AbstractAlfrescoDistributedTest
public class DistributedAlfrescoSolrJsonIT extends AbstractAlfrescoDistributedIT
{
@BeforeClass
private static void initData() throws Throwable
@@ -19,7 +19,7 @@
package org.alfresco.solr.tracker;
import org.alfresco.repo.search.adaptor.lucene.QueryConstants;
import org.alfresco.solr.AbstractAlfrescoDistributedTest;
import org.alfresco.solr.AbstractAlfrescoDistributedIT;
import org.alfresco.solr.client.Acl;
import org.alfresco.solr.client.AclChangeSet;
import org.alfresco.solr.client.AclReaders;
@@ -56,7 +56,7 @@ import static org.alfresco.solr.AlfrescoSolrUtils.list;
*/
@SolrTestCaseJ4.SuppressSSL
@LuceneTestCase.SuppressCodecs({"Appending","Lucene3x","Lucene40","Lucene41","Lucene42","Lucene43", "Lucene44", "Lucene45","Lucene46","Lucene47","Lucene48","Lucene49"})
public class DistributedAlfrescoSolrTrackerTest extends AbstractAlfrescoDistributedTest
public class DistributedAlfrescoSolrTrackerIT extends AbstractAlfrescoDistributedIT
{
@BeforeClass
@@ -19,7 +19,7 @@
package org.alfresco.solr.tracker;
import org.alfresco.repo.search.adaptor.lucene.QueryConstants;
import org.alfresco.solr.AbstractAlfrescoDistributedTest;
import org.alfresco.solr.AbstractAlfrescoDistributedIT;
import org.alfresco.solr.client.Acl;
import org.alfresco.solr.client.AclChangeSet;
import org.alfresco.solr.client.AclReaders;
@@ -54,7 +54,7 @@ import static org.alfresco.solr.AlfrescoSolrUtils.indexAclChangeSet;
*/
@SolrTestCaseJ4.SuppressSSL
@LuceneTestCase.SuppressCodecs({"Appending","Lucene3x","Lucene40","Lucene41","Lucene42","Lucene43", "Lucene44", "Lucene45","Lucene46","Lucene47","Lucene48","Lucene49"})
public class DistributedAlfrescoSolrTrackerRaceTest extends AbstractAlfrescoDistributedTest
public class DistributedAlfrescoSolrTrackerRaceIT extends AbstractAlfrescoDistributedIT
{
@BeforeClass
@@ -33,7 +33,7 @@ import static org.alfresco.solr.AlfrescoSolrUtils.indexAclChangeSet;
import org.alfresco.repo.index.shard.ShardState;
import org.alfresco.repo.search.adaptor.lucene.QueryConstants;
import org.alfresco.solr.AbstractAlfrescoDistributedTest;
import org.alfresco.solr.AbstractAlfrescoDistributedIT;
import org.alfresco.solr.AlfrescoCoreAdminHandler;
import org.alfresco.solr.client.Acl;
import org.alfresco.solr.client.AclChangeSet;
@@ -68,7 +68,7 @@ import java.util.stream.Collectors;
* @author agazzarini
*/
@SolrTestCaseJ4.SuppressSSL
public class DistributedAlfrescoSolrTrackerStateTest extends AbstractAlfrescoDistributedTest
public class DistributedAlfrescoSolrTrackerStateIT extends AbstractAlfrescoDistributedIT
{
@BeforeClass
private static void initData() throws Throwable
@@ -30,7 +30,7 @@ import static org.alfresco.solr.AlfrescoSolrUtils.indexAclChangeSet;
import static org.carrot2.shaded.guava.common.collect.ImmutableList.of;
import org.alfresco.model.ContentModel;
import org.alfresco.solr.AbstractAlfrescoDistributedTest;
import org.alfresco.solr.AbstractAlfrescoDistributedIT;
import org.alfresco.solr.client.Acl;
import org.alfresco.solr.client.AclChangeSet;
import org.alfresco.solr.client.AclReaders;
@@ -55,7 +55,7 @@ import java.util.Properties;
*/
@SolrTestCaseJ4.SuppressSSL
@LuceneTestCase.SuppressCodecs({"Appending","Lucene3x","Lucene40","Lucene41","Lucene42","Lucene43", "Lucene44", "Lucene45","Lucene46","Lucene47","Lucene48","Lucene49"})
public class DistributedCascadeTrackerTest extends AbstractAlfrescoDistributedTest
public class DistributedCascadeTrackerIT extends AbstractAlfrescoDistributedIT
{
private Node parentFolder;
private NodeMetaData parentFolderMetadata;
@@ -21,7 +21,7 @@ package org.alfresco.solr.tracker;
import org.alfresco.model.ContentModel;
import org.alfresco.service.cmr.repository.datatype.DefaultTypeConverter;
import org.alfresco.service.namespace.QName;
import org.alfresco.solr.AbstractAlfrescoDistributedTest;
import org.alfresco.solr.AbstractAlfrescoDistributedIT;
import org.alfresco.solr.AlfrescoSolrDataModel;
import org.alfresco.solr.client.Acl;
import org.alfresco.solr.client.AclChangeSet;
@@ -63,7 +63,7 @@ import static org.alfresco.solr.AlfrescoSolrUtils.indexAclChangeSet;
*/
@SolrTestCaseJ4.SuppressSSL
@LuceneTestCase.SuppressCodecs({"Appending","Lucene3x","Lucene40","Lucene41","Lucene42","Lucene43", "Lucene44", "Lucene45","Lucene46","Lucene47","Lucene48","Lucene49"})
public abstract class DistributedDateAbstractSolrTrackerTest extends AbstractAlfrescoDistributedTest
public abstract class DistributedDateAbstractSolrTrackerIT extends AbstractAlfrescoDistributedIT
{
@Test
public void testDateMonth() throws Exception
@@ -33,7 +33,7 @@ import org.alfresco.model.ContentModel;
import org.alfresco.repo.index.shard.ShardMethodEnum;
import org.alfresco.service.cmr.repository.datatype.DefaultTypeConverter;
import org.alfresco.service.namespace.QName;
import org.alfresco.solr.AbstractAlfrescoDistributedTest;
import org.alfresco.solr.AbstractAlfrescoDistributedIT;
import org.alfresco.solr.AlfrescoSolrDataModel;
import org.alfresco.solr.SolrInformationServer;
import org.alfresco.solr.client.Acl;
@@ -63,13 +63,13 @@ import java.util.Properties;
import java.util.TimeZone;
@SolrTestCaseJ4.SuppressSSL
public class DistributedDateMonthAlfrescoSolrTrackerTest extends AbstractAlfrescoDistributedTest
public class DistributedDateMonthAlfrescoSolrTrackerIT extends AbstractAlfrescoDistributedIT
{
@BeforeClass
@SuppressWarnings("unused")
public static void initData() throws Throwable
{
initSolrServers(5, DistributedDateMonthAlfrescoSolrTrackerTest.class.getSimpleName(), getShardMethod());
initSolrServers(5, DistributedDateMonthAlfrescoSolrTrackerIT.class.getSimpleName(), getShardMethod());
}
@AfterClass
@@ -33,7 +33,7 @@ import java.util.Properties;
@SolrTestCaseJ4.SuppressSSL
@LuceneTestCase.SuppressCodecs({"Appending","Lucene3x","Lucene40","Lucene41","Lucene42","Lucene43", "Lucene44", "Lucene45","Lucene46","Lucene47","Lucene48","Lucene49"})
public class DistributedDateQuarterAlfrescoSolrTrackerTest extends DistributedDateAbstractSolrTrackerTest
public class DistributedDateQuarterAlfrescoSolrTrackerIT extends DistributedDateAbstractSolrTrackerIT
{
@BeforeClass
@@ -33,12 +33,12 @@ import java.util.Properties;
@SolrTestCaseJ4.SuppressSSL
@LuceneTestCase.SuppressCodecs({"Appending","Lucene3x","Lucene40","Lucene41","Lucene42","Lucene43", "Lucene44", "Lucene45","Lucene46","Lucene47","Lucene48","Lucene49"})
public class DistributedDateSplitYearAlfrescoSolrTrackerTest extends DistributedDateAbstractSolrTrackerTest
public class DistributedDateSplitYearAlfrescoSolrTrackerIT extends DistributedDateAbstractSolrTrackerIT
{
@BeforeClass
private static void initData() throws Throwable
{
initSolrServers(3, "DistributedDateSplitYearAlfrescoSolrTrackerTest", getShardMethod());
initSolrServers(3, "DistributedDateSplitYearAlfrescoSolrTrackerIT", getShardMethod());
}
@AfterClass
@@ -18,7 +18,7 @@
*/
package org.alfresco.solr.tracker;
import org.alfresco.solr.AbstractAlfrescoDistributedTest;
import org.alfresco.solr.AbstractAlfrescoDistributedIT;
import org.alfresco.solr.SolrInformationServer;
import org.alfresco.solr.client.Acl;
import org.alfresco.solr.client.AclChangeSet;
@@ -53,12 +53,12 @@ import static org.alfresco.solr.AlfrescoSolrUtils.list;
*/
@SolrTestCaseJ4.SuppressSSL
@LuceneTestCase.SuppressCodecs({"Appending","Lucene3x","Lucene40","Lucene41","Lucene42","Lucene43", "Lucene44", "Lucene45","Lucene46","Lucene47","Lucene48","Lucene49"})
public class DistributedDbidRangeAlfrescoSolrTrackerTest extends AbstractAlfrescoDistributedTest
public class DistributedDbidRangeAlfrescoSolrTrackerIT extends AbstractAlfrescoDistributedIT
{
@BeforeClass
private static void initData() throws Throwable
{
initSolrServers(2, "DistributedDbidRangeAlfrescoSolrTrackerTest", getShardMethod());
initSolrServers(2, "DistributedDbidRangeAlfrescoSolrTrackerIT", getShardMethod());
}
@AfterClass
@@ -18,7 +18,7 @@
*/
package org.alfresco.solr.tracker;
import org.alfresco.solr.AbstractAlfrescoDistributedTest;
import org.alfresco.solr.AbstractAlfrescoDistributedIT;
import org.alfresco.solr.SolrInformationServer;
import org.alfresco.solr.client.Acl;
import org.alfresco.solr.client.AclChangeSet;
@@ -56,12 +56,12 @@ import static org.alfresco.solr.AlfrescoSolrUtils.list;
*/
@SolrTestCaseJ4.SuppressSSL
@LuceneTestCase.SuppressCodecs({"Appending","Lucene3x","Lucene40","Lucene41","Lucene42","Lucene43", "Lucene44", "Lucene45","Lucene46","Lucene47","Lucene48","Lucene49"})
public class DistributedExpandDbidRangeAlfrescoSolrTrackerTest extends AbstractAlfrescoDistributedTest
public class DistributedExpandDbidRangeAlfrescoSolrTrackerIT extends AbstractAlfrescoDistributedIT
{
@BeforeClass
private static void initData() throws Throwable
{
initSolrServers(2, "DistributedExpandDbidRangeAlfrescoSolrTrackerTest", getShardMethod());
initSolrServers(2, "DistributedExpandDbidRangeAlfrescoSolrTrackerIT", getShardMethod());
}
@AfterClass
@@ -21,7 +21,7 @@ package org.alfresco.solr.tracker;
import java.util.Properties;
import org.alfresco.model.ContentModel;
import org.alfresco.repo.index.shard.ShardMethodEnum;
import org.alfresco.solr.AbstractAlfrescoDistributedTest;
import org.alfresco.solr.AbstractAlfrescoDistributedIT;
import org.alfresco.solr.client.Acl;
import org.alfresco.solr.client.AclChangeSet;
import org.alfresco.solr.client.AclReaders;
@@ -53,7 +53,7 @@ import static org.carrot2.shaded.guava.common.collect.ImmutableList.of;
@SolrTestCaseJ4.SuppressSSL
@SolrTestCaseJ4.SuppressObjectReleaseTracker (bugUrl = "RAMDirectory")
@LuceneTestCase.SuppressCodecs({"Appending","Lucene3x","Lucene40","Lucene41","Lucene42","Lucene43", "Lucene44", "Lucene45","Lucene46","Lucene47","Lucene48","Lucene49"})
public class DistributedExplicitShardIdWithStaticPropertyRouterTest extends AbstractAlfrescoDistributedTest
public class DistributedExplicitShardIdWithStaticPropertyRouterIT extends AbstractAlfrescoDistributedIT
{
private static long MAX_WAIT_TIME = 80000;
private final int timeout = 100000;
@@ -20,7 +20,7 @@ package org.alfresco.solr.tracker;
import org.alfresco.model.ContentModel;
import org.alfresco.repo.index.shard.ShardMethodEnum;
import org.alfresco.solr.AbstractAlfrescoDistributedTest;
import org.alfresco.solr.AbstractAlfrescoDistributedIT;
import org.alfresco.solr.SolrInformationServer;
import org.alfresco.solr.client.Acl;
import org.alfresco.solr.client.AclChangeSet;
@@ -62,12 +62,12 @@ import static org.alfresco.solr.tracker.DocRouterFactory.SHARD_KEY_KEY;
@SolrTestCaseJ4.SuppressSSL
@SolrTestCaseJ4.SuppressObjectReleaseTracker (bugUrl = "RAMDirectory")
@LuceneTestCase.SuppressCodecs({"Appending","Lucene3x","Lucene40","Lucene41","Lucene42","Lucene43", "Lucene44", "Lucene45","Lucene46","Lucene47","Lucene48","Lucene49"})
public class DistributedExplicitShardRoutingTrackerTest extends AbstractAlfrescoDistributedTest
public class DistributedExplicitShardRoutingTrackerIT extends AbstractAlfrescoDistributedIT
{
@BeforeClass
private static void initData() throws Throwable
{
initSolrServers(3, "DistributedExplicitShardRoutingTrackerTest", getProperties());
initSolrServers(3, "DistributedExplicitShardRoutingTrackerIT", getProperties());
}
@AfterClass
@@ -41,7 +41,7 @@ import com.carrotsearch.randomizedtesting.RandomizedContext;
import org.alfresco.model.ContentModel;
import org.alfresco.repo.index.shard.ShardMethodEnum;
import org.alfresco.solr.AbstractAlfrescoDistributedTest;
import org.alfresco.solr.AbstractAlfrescoDistributedIT;
import org.alfresco.solr.SolrInformationServer;
import org.alfresco.solr.client.Acl;
import org.alfresco.solr.client.AclChangeSet;
@@ -66,7 +66,7 @@ import org.junit.Test;
@SolrTestCaseJ4.SuppressSSL
@SolrTestCaseJ4.SuppressObjectReleaseTracker (bugUrl = "RAMDirectory")
@LuceneTestCase.SuppressCodecs({"Appending","Lucene3x","Lucene40","Lucene41","Lucene42","Lucene43", "Lucene44", "Lucene45","Lucene46","Lucene47","Lucene48","Lucene49"})
public class DistributedPropertyBasedAlfrescoSolrTrackerTest extends AbstractAlfrescoDistributedTest
public class DistributedPropertyBasedAlfrescoSolrTrackerIT extends AbstractAlfrescoDistributedIT
{
private static final String[] DOMAINS = {"alfresco.com", "king.com", "gmail.com", "yahoo.com", "cookie.es"};
private static final Map<String,Integer> domainsCount = new HashMap<>();
@@ -78,7 +78,7 @@ public class DistributedPropertyBasedAlfrescoSolrTrackerTest extends AbstractAlf
{
domainsCount.put(domain,0);
}
initSolrServers(4, "DistributedPropertyBasedAlfrescoSolrTrackerTest", getProperties());
initSolrServers(4, "DistributedPropertyBasedAlfrescoSolrTrackerIT", getProperties());
}
@AfterClass
@@ -41,7 +41,7 @@ import java.util.Map;
import java.util.Properties;
@RunWith(MockitoJUnitRunner.class)
public class ExplicitIDRouterTest
public class ExplicitIDRouterIT
{
private DocRouter router;
@@ -42,7 +42,7 @@ import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.when;
@RunWith(MockitoJUnitRunner.class)
public class ExplicitIDWithLRISRouterTest
public class ExplicitIDWithLRISRouterIT
{
private DocRouter router;
@@ -56,7 +56,7 @@ import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
@RunWith(MockitoJUnitRunner.class)
public class ModelTrackerTest
public class ModelTrackerIT
{
private ModelTracker modelTracker;
@@ -42,7 +42,7 @@ import java.util.HashMap;
import java.util.Map;
@RunWith(MockitoJUnitRunner.class)
public class PropertyRouterTest
public class PropertyRouterIT
{
private PropertyRouter router;

Some files were not shown because too many files have changed in this diff Show More