Merge branch 'fix/SEARCH_2175_CascadeTracker' into 'master'

Fix/search 2175 cascade tracker

See merge request search_discovery/insightengine!463
This commit is contained in:
Andrea Gazzarini
2020-04-15 14:39:48 +01:00
22 changed files with 909 additions and 408 deletions
@@ -0,0 +1,30 @@
/*
* Copyright (C) 2005-2020 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;
/**
* Marker exception thrown for indicating a failure in obtaining / releasing a lock.
*/
public class AlfrescoLockException extends Exception
{
AlfrescoLockException(String message)
{
super(message);
}
}
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2019 Alfresco Software Limited.
* Copyright (C) 2020 Alfresco Software Limited.
*
* This file is part of Alfresco
*
@@ -69,6 +69,7 @@ import static org.alfresco.repo.search.adaptor.lucene.QueryConstants.FIELD_TXCOM
import static org.alfresco.repo.search.adaptor.lucene.QueryConstants.FIELD_TXID;
import static org.alfresco.repo.search.adaptor.lucene.QueryConstants.FIELD_TYPE;
import static org.alfresco.repo.search.adaptor.lucene.QueryConstants.FIELD_VERSION;
import static org.alfresco.solr.utils.Utils.notNullOrEmpty;
import java.io.File;
import java.io.IOException;
@@ -85,7 +86,6 @@ import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
@@ -296,6 +296,8 @@ public class SolrInformationServer implements InformationServer
private String skippingDocsQueryString;
private boolean isSkippingDocsInitialized;
private long maxAllowedTimeForAcquiringDbIdLock;
protected enum FTSStatus {New, Dirty, Clean}
static class DocListCollector implements Collector, LeafCollector
@@ -451,7 +453,7 @@ public class SolrInformationServer implements InformationServer
this.core = core;
this.nativeRequestHandler = core.getRequestHandler(REQUEST_HANDLER_NATIVE);
this.cloud = new Cloud();
this.repositoryClient = repositoryClient;
this.repositoryClient = Objects.requireNonNull(repositoryClient);
this.solrContentStore = solrContentStore;
Properties p = core.getResourceLoader().getCoreProperties();
@@ -461,6 +463,8 @@ public class SolrInformationServer implements InformationServer
holeRetention = Integer.parseInt(p.getProperty("alfresco.hole.retention", "3600000"));
minHash = Boolean.parseBoolean(p.getProperty("alfresco.fingerprint", "true"));
maxAllowedTimeForAcquiringDbIdLock = Long.parseLong(p.getProperty("alfresco.tracker.maxNodeLockMs", "120000"));
dataModel = AlfrescoSolrDataModel.getInstance();
contentStreamLimit = Integer.parseInt(p.getProperty("alfresco.contentStreamLimit", "10000000"));
@@ -1502,7 +1506,7 @@ public class SolrInformationServer implements InformationServer
NodeMetaDataParameters nmdp = new NodeMetaDataParameters();
nmdp.setFromNodeId(node.getId());
nmdp.setToNodeId(node.getId());
List<NodeMetaData> nodeMetaDatas;
Collection<NodeMetaData> nodeMetaDatas;
if ((node.getStatus() == SolrApiNodeStatus.DELETED)
|| cascadeTrackingEnabled() && ((node.getStatus() == SolrApiNodeStatus.NON_SHARD_DELETED)
|| (node.getStatus() == SolrApiNodeStatus.NON_SHARD_UPDATED)))
@@ -1513,13 +1517,14 @@ public class SolrInformationServer implements InformationServer
}
else
{
nodeMetaDatas = repositoryClient.getNodesMetaData(nmdp, Integer.MAX_VALUE);
nmdp.setMaxResults(Integer.MAX_VALUE);
nodeMetaDatas = getNodesMetaDataFromRepository(nmdp);
}
NodeMetaData nodeMetaData;
if (!nodeMetaDatas.isEmpty())
{
nodeMetaData = nodeMetaDatas.get(0);
nodeMetaData = nodeMetaDatas.iterator().next();
if (!(nodeMetaData.getTxnId() > node.getTxnId()))
{
if (node.getStatus() == SolrApiNodeStatus.DELETED)
@@ -1557,15 +1562,15 @@ public class SolrInformationServer implements InformationServer
NodeMetaDataParameters nmdp = new NodeMetaDataParameters();
nmdp.setFromNodeId(node.getId());
nmdp.setToNodeId(node.getId());
List<NodeMetaData> nodeMetaDatas = repositoryClient.getNodesMetaData(nmdp, Integer.MAX_VALUE);
nmdp.setMaxResults(Integer.MAX_VALUE);
Collection<NodeMetaData> nodeMetaDatas = getNodesMetaDataFromRepository(nmdp);
AddUpdateCommand addDocCmd = new AddUpdateCommand(request);
addDocCmd.overwrite = overwrite;
if (!nodeMetaDatas.isEmpty())
{
NodeMetaData nodeMetaData = nodeMetaDatas.get(0);
NodeMetaData nodeMetaData = nodeMetaDatas.iterator().next();
if(node.getTxnId() == Long.MAX_VALUE) {
//This is a re-index. We need to clear the txnId from the pr
this.cleanContentCache.remove(nodeMetaData.getTxnId());
@@ -1722,8 +1727,9 @@ public class SolrInformationServer implements InformationServer
nmdp.setIncludePaths(true);
nmdp.setIncludeProperties(false);
nmdp.setIncludeTxnId(true);
nmdp.setMaxResults(1);
// Gets only one
List<NodeMetaData> nodeMetaDatas = repositoryClient.getNodesMetaData(nmdp, 1);
Collection<NodeMetaData> nodeMetaDatas = getNodesMetaDataFromRepository(nmdp);
allNodeMetaDatas.addAll(nodeMetaDatas);
}
@@ -1808,6 +1814,14 @@ public class SolrInformationServer implements InformationServer
processor.processAdd(addDocCmd);
}
}
catch (AlfrescoLockException exception)
{
LOGGER.error(exception.getMessage());
}
catch (Exception exception)
{
LOGGER.error("Unable to update the text content of node {}. See the stacktrace below for further details.", dbId, exception);
}
finally
{
unlock(dbId);
@@ -1862,7 +1876,8 @@ public class SolrInformationServer implements InformationServer
nmdp.setIncludeAspects(false);
nmdp.setIncludePaths(false);
nmdp.setIncludeParentAssociations(false);
nodeMetaDatas.addAll(repositoryClient.getNodesMetaData(nmdp, Integer.MAX_VALUE));
nmdp.setMaxResults(Integer.MAX_VALUE);
nodeMetaDatas.addAll(getNodesMetaDataFromRepository(nmdp));
}
for (NodeMetaData nodeMetaData : nodeMetaDatas)
@@ -1907,7 +1922,8 @@ public class SolrInformationServer implements InformationServer
nmdp.setIncludeChildAssociations(false);
// Fetches bulk metadata
List<NodeMetaData> nodeMetaDatas = repositoryClient.getNodesMetaData(nmdp, Integer.MAX_VALUE);
nmdp.setMaxResults(Integer.MAX_VALUE);
Collection<NodeMetaData> nodeMetaDatas = getNodesMetaDataFromRepository(nmdp);
NEXT_NODE:
for (NodeMetaData nodeMetaData : nodeMetaDatas)
@@ -2484,11 +2500,12 @@ public class SolrInformationServer implements InformationServer
NodeMetaDataParameters nmdp = new NodeMetaDataParameters();
nmdp.setFromNodeId(dbId);
nmdp.setToNodeId(dbId);
List<NodeMetaData> nodeMetaDatas = repositoryClient.getNodesMetaData(nmdp, Integer.MAX_VALUE);
nmdp.setMaxResults(Integer.MAX_VALUE);
Collection<NodeMetaData> nodeMetaDatas = getNodesMetaDataFromRepository(nmdp);
SolrInputDocument newDoc = null;
if (!nodeMetaDatas.isEmpty())
{
NodeMetaData nodeMetaData = nodeMetaDatas.get(0);
NodeMetaData nodeMetaData = nodeMetaDatas.iterator().next();
newDoc = createNewDoc(nodeMetaData, DOC_TYPE_NODE);
addFieldsToDoc(nodeMetaData, newDoc);
boolean isContentIndexedForNode = isContentIndexedForNode(nodeMetaData.getProperties());
@@ -3155,10 +3172,10 @@ public class SolrInformationServer implements InformationServer
}
}
private void lock(Object id) throws IOException
private void lock(Object id) throws AlfrescoLockException
{
long startTime = System.currentTimeMillis();
while(!lockRegistry.add(id))
while (!lockRegistry.add(id))
{
try
{
@@ -3169,9 +3186,9 @@ public class SolrInformationServer implements InformationServer
// I don't think we are concerned with this exception.
}
if(System.currentTimeMillis() - startTime > 120000)
if (System.currentTimeMillis() - startTime > maxAllowedTimeForAcquiringDbIdLock)
{
throw new IOException("Unable to acquire lock on nodeId " + id + " after " + 120000 + " msecs.");
throw new AlfrescoLockException("Unable to acquire lock on nodeId " + id + " after " + maxAllowedTimeForAcquiringDbIdLock + " msecs.");
}
}
}
@@ -3375,12 +3392,13 @@ public class SolrInformationServer implements InformationServer
nmdp.setIncludeProperties(false);
nmdp.setIncludeType(false);
nmdp.setIncludeTxnId(true);
nmdp.setMaxResults(1);
// Gets only one
List<NodeMetaData> nodeMetaDatas = repositoryClient.getNodesMetaData(nmdp, 1);
Collection<NodeMetaData> nodeMetaDatas = getNodesMetaDataFromRepository(nmdp);
if (!nodeMetaDatas.isEmpty())
{
NodeMetaData nodeMetaData = nodeMetaDatas.get(0);
NodeMetaData nodeMetaData = nodeMetaDatas.iterator().next();
// Only cascade update nods we know can not have changed and must be in this shard
// Node in the current TX will be explicitly updated in the outer loop
@@ -3432,6 +3450,14 @@ public class SolrInformationServer implements InformationServer
LOGGER.debug("No child doc found to update {}", childId);
}
}
catch (AlfrescoLockException exception)
{
LOGGER.error(exception.getMessage());
}
catch (Exception exception)
{
LOGGER.error("Cascade update failure on child document {}. See the stacktrace below for further details.", childId, exception);
}
finally
{
unlock(nodeId);
@@ -3823,4 +3849,22 @@ public class SolrInformationServer implements InformationServer
{
solrContentStore.flushChangeSet();
}
private Collection<NodeMetaData> getNodesMetaDataFromRepository(NodeMetaDataParameters parameters)
{
try
{
return notNullOrEmpty(repositoryClient.getNodesMetaData(parameters));
}
catch (JSONException exception)
{
// Nothing to be done here: the exception has been already logged in repositoryClient
return Collections.emptyList();
}
catch (Exception exception)
{
LOGGER.error("Unable to get nodes metadata from repository. See the stacktrace below for further details.", exception);
return Collections.emptyList();
}
}
}
@@ -20,7 +20,6 @@ package org.alfresco.solr.tracker;
import static java.util.Optional.ofNullable;
import java.lang.invoke.MethodHandles;
import java.net.ConnectException;
import java.net.SocketTimeoutException;
import java.util.Properties;
@@ -45,7 +44,7 @@ public abstract class AbstractTracker implements Tracker
static final long TIME_STEP_1_HR_IN_MS = 60 * 60 * 1000L;
static final String SHARD_METHOD_DBID = "DB_ID";
protected final static Logger LOGGER = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
protected final Logger logger = LoggerFactory.getLogger(getClass());
protected Properties props;
protected SOLRAPIClient client;
@@ -73,6 +72,7 @@ public abstract class AbstractTracker implements Tracker
*/
protected Throwable rollbackCausedBy;
protected final Type type;
protected final String trackerId;
/*
* A thread handler can be used by subclasses, but they have to intentionally instantiate it.
@@ -85,8 +85,9 @@ public abstract class AbstractTracker implements Tracker
protected AbstractTracker(Type type)
{
this.type = type;
this.trackerId = type + "@" + hashCode();
}
protected AbstractTracker(Properties p, SOLRAPIClient client, String coreName, InformationServer informationServer,Type type)
{
this.props = p;
@@ -109,6 +110,8 @@ public abstract class AbstractTracker implements Tracker
this.trackerStats = this.infoSrv.getTrackerStats();
this.type = type;
this.trackerId = type + "@" + hashCode();
}
@@ -121,8 +124,10 @@ public abstract class AbstractTracker implements Tracker
* <li>Index</li>
* <li>Track repository</li>
* </ol>
*
* @param iterationId an identifier which is uniquely associated with a given iteration.
*/
protected abstract void doTrack() throws Throwable;
protected abstract void doTrack(String iterationId) throws Throwable;
private boolean assertTrackerStateRemainsNull() {
@@ -156,14 +161,16 @@ public abstract class AbstractTracker implements Tracker
}
/**
* Template method - subclasses must implement the {@link Tracker}-specific indexing
* by implementing the abstract method {@link #doTrack()}.
* by implementing the abstract method {@link #doTrack(String)}.
*/
@Override
public void track()
{
String iterationId = "IT #" + System.currentTimeMillis();
if(runLock.availablePermits() == 0)
{
LOGGER.info("... {} for core [{}] is already in use {}", this.getClass().getSimpleName(), coreName, getClass());
logger.info("[{} / {} / {}] Tracker already registered.", coreName, trackerId, iterationId);
return;
}
@@ -180,13 +187,11 @@ public abstract class AbstractTracker implements Tracker
assert(assertTrackerStateRemainsNull());
}
LOGGER.info("[CORE {}] Running {}", coreName, this.getClass().getSimpleName());
if(this.state == null)
{
this.state = getTrackerState();
LOGGER.debug("[CORE {}] Global Tracker State set to: {}", coreName, this.state.toString());
logger.debug("[{} / {} / {}] Global Tracker State set to: {}", coreName, trackerId, iterationId, this.state.toString());
this.state.setRunning(true);
}
else
@@ -199,33 +204,33 @@ public abstract class AbstractTracker implements Tracker
try
{
doTrack();
doTrack(iterationId);
}
catch(IndexTrackingShutdownException t)
{
setRollback(true, t);
LOGGER.info("[CORE {}] Stopping index tracking for {}", coreName, getClass().getSimpleName());
logger.info("[{} / {} / {}] Tracking cycle stopped. See the stacktrace below for further details.", coreName, trackerId, iterationId, t);
}
catch(Throwable t)
{
setRollback(true, t);
if (t instanceof SocketTimeoutException || t instanceof ConnectException)
{
LOGGER.warn("[CORE {}] Tracking communication timed out for {}", coreName, getClass().getSimpleName());
if (LOGGER.isDebugEnabled())
logger.warn("[{} / {} / {}] Tracking communication timed out. See the stacktrace below for further details.", coreName, trackerId, iterationId);
if (logger.isDebugEnabled())
{
LOGGER.debug("[CORE {}] Stack trace", coreName, t);
logger.debug("[{} / {} / {}] Stack trace", coreName, trackerId, iterationId, t);
}
}
else
{
LOGGER.error("[CORE {}] Tracking failed for {}", coreName, getClass().getSimpleName(), t);
logger.error("[{} / {} / {}] Tracking failure. See the stacktrace below for further details.", coreName, trackerId, iterationId, t);
}
}
}
catch (InterruptedException e)
{
LOGGER.error("[CORE {}] Semaphore interrupted for {}", coreName, getClass().getSimpleName(), e);
logger.error("[{} / {} / {}] Semaphore interruption. See the stacktrace below for further details.", coreName, trackerId, iterationId, e);
}
finally
{
@@ -90,7 +90,7 @@ public class AclTracker extends AbstractTracker
}
@Override
protected void doTrack() throws Throwable
protected void doTrack(String iterationId) throws Throwable
{
trackRepository();
}
@@ -36,6 +36,9 @@ import org.json.JSONException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static java.util.stream.Collectors.joining;
import static org.alfresco.solr.utils.Utils.notNullOrEmpty;
/*
* This tracks Cascading Updates
* @author Joel Bernstein
@@ -61,13 +64,13 @@ public class CascadeTracker extends AbstractTracker implements Tracker
}
@Override
protected void doTrack() throws AuthenticationException, IOException, JSONException, EncoderException
protected void doTrack(String iterationId) throws AuthenticationException, IOException, JSONException, EncoderException
{
// MetadataTracker must wait until ModelTracker has run
ModelTracker modelTracker = this.infoSrv.getAdminHandler().getTrackerRegistry().getModelTracker();
if (modelTracker != null && modelTracker.hasModels())
{
trackRepository();
trackRepository(iterationId);
}
}
@@ -79,10 +82,10 @@ public class CascadeTracker extends AbstractTracker implements Tracker
return false;
}
private void trackRepository() throws IOException, AuthenticationException, JSONException, EncoderException
private void trackRepository(String iterationId) throws IOException, AuthenticationException, JSONException, EncoderException
{
checkShutdown();
processCascades();
processCascades(iterationId);
}
private void updateTransactionsAfterAsynchronous(List<Transaction> txsIndexed)
@@ -125,9 +128,8 @@ public class CascadeTracker extends AbstractTracker implements Tracker
infoSrv.setCleanCascadeTxnFloor(-1);
}
private void processCascades() throws IOException
private void processCascades(String iterationId) throws IOException
{
//System.out.println("######### processCascades()");
int num = 50;
List<Transaction> txBatch = null;
do {
@@ -160,7 +162,17 @@ public class CascadeTracker extends AbstractTracker implements Tracker
batch.add(stack.removeFirst());
}
CascadeIndexWorkerRunnable worker = new CascadeIndexWorkerRunnable(this.threadHandler, batch, infoSrv);
if (logger.isTraceEnabled())
{
String nodes = notNullOrEmpty(batch).stream()
.map(NodeMetaData::getId)
.map(Object::toString)
.collect(joining(","));
logger.trace("[{} / {} / {} / {}] Worker has been created for nodes {}", coreName, trackerId, iterationId, worker.hashCode(), nodes);
}
this.threadHandler.scheduleTask(worker);
}
while (stack.size() > 0);
@@ -101,7 +101,7 @@ public class CommitTracker extends AbstractTracker
}
@Override
protected void doTrack() throws Throwable
protected void doTrack(String iterationId) throws Throwable
{
long currentTime = System.currentTimeMillis();
boolean commitNeeded = false;
@@ -57,7 +57,7 @@ public class ContentTracker extends AbstractTracker implements Tracker
}
@Override
protected void doTrack() throws Exception
protected void doTrack(String iterationId) throws Exception
{
//System.out.println("############## Content Tracker doTrack()");
try {
@@ -118,7 +118,7 @@ public abstract class CoreStatePublisher extends AbstractTracker
updateShardProperty();
if (shardProperty.isEmpty())
{
LOGGER.warn("Sharding property {} was set to {}, but no such property was found.", SHARD_KEY_KEY, shardKeyName);
logger.warn("Sharding property {} was set to {}, but no such property was found.", SHARD_KEY_KEY, shardKeyName);
}
});
}
@@ -134,11 +134,11 @@ public abstract class CoreStatePublisher extends AbstractTracker
{
if (updatedShardProperty.isEmpty())
{
LOGGER.warn("The model defining {} property has been disabled", shardKeyName);
logger.warn("The model defining {} property has been disabled", shardKeyName);
}
else
{
LOGGER.info("New {} property found for {} ", SHARD_KEY_KEY, shardKeyName);
logger.info("New {} property found for {} ", SHARD_KEY_KEY, shardKeyName);
}
}
shardProperty = updatedShardProperty;
@@ -169,7 +169,7 @@ public class MetadataTracker extends CoreStatePublisher implements Tracker
}
@Override
protected void doTrack() throws AuthenticationException, IOException, JSONException, EncoderException
protected void doTrack(String iterationId) throws AuthenticationException, IOException, JSONException, EncoderException
{
log.debug("### MetadataTracker doTrack ###");
// MetadataTracker must wait until ModelTracker has run
@@ -106,7 +106,7 @@ public class ModelTracker extends AbstractTracker implements Tracker
super(p, client, coreName, informationServer, Tracker.Type.MODEL);
String normalSolrHome = SolrResourceLoader.normalizeDir(solrHome);
alfrescoModelDir = new File(ConfigUtil.locateProperty("solr.model.dir", normalSolrHome+"alfrescoModels"));
LOGGER.info("Alfresco Model dir " + alfrescoModelDir);
logger.info("Alfresco Model dir " + alfrescoModelDir);
if (!alfrescoModelDir.exists())
{
alfrescoModelDir.mkdir();
@@ -191,13 +191,13 @@ public class ModelTracker extends AbstractTracker implements Tracker
}
@Override
protected void doTrack() throws AuthenticationException, IOException, JSONException
protected void doTrack(String iterationId) throws AuthenticationException, IOException, JSONException
{
// Is the InformationServer ready to update
int registeredSearcherCount = this.infoSrv.getRegisteredSearcherCount();
if (registeredSearcherCount >= getMaxLiveSearchers())
{
LOGGER.info(".... skipping tracking registered searcher count = " + registeredSearcherCount);
logger.info(".... skipping tracking registered searcher count = " + registeredSearcherCount);
return;
}
@@ -268,7 +268,7 @@ public class ModelTracker extends AbstractTracker implements Tracker
}
catch (Throwable t)
{
LOGGER.error("Model tracking failed for core: "+ coreName, t);
logger.error("Model tracking failed for core: "+ coreName, t);
}
}
@@ -534,7 +534,7 @@ public class ModelTracker extends AbstractTracker implements Tracker
{
loadedModels.add(modelName);
}
LOGGER.info("Loading model " + model.getName());
logger.info("Loading model " + model.getName());
}
}
@@ -58,7 +58,7 @@ public class SlaveCoreStatePublisher extends CoreStatePublisher
}
@Override
protected void doTrack()
protected void doTrack(String iterationId)
{
try
{
@@ -67,7 +67,7 @@ public class SlaveCoreStatePublisher extends CoreStatePublisher
}
catch (EncoderException | IOException | AuthenticationException exception )
{
LOGGER.error("Unable to publish this node state. " +
logger.error("Unable to publish this node state. " +
"A failure condition has been met during the outbound subscription message encoding process. " +
"See the stacktrace below for further details.", exception);
}
@@ -30,6 +30,9 @@ alfresco.hole.check.after=300000
alfresco.batch.count=5000
alfresco.recordUnindexedNodes=false
# max time (in msecs) a given tracker instance will try to acquire a lock on a given DBID
alfresco.tracker.maxNodeLockMs=120000
# encryption
# none, https
@@ -30,6 +30,9 @@ alfresco.hole.check.after=300000
alfresco.batch.count=5000
alfresco.recordUnindexedNodes=false
# max time (in msecs) a given tracker instance will try to acquire a lock on a given DBID
alfresco.tracker.maxNodeLockMs=120000
# encryption
# none, https
@@ -71,7 +71,7 @@ public class ContentTrackerIT
public void doTrackWithNoContentDoesNothing() throws Exception
{
this.contentTracker.doTrack();
this.contentTracker.doTrack("anIterationId");
verify(srv, never()).updateContentToIndexAndCache(anyLong(), anyString());
verify(srv, never()).commit();
}
@@ -107,7 +107,7 @@ public class ContentTrackerIT
.thenReturn(docs1)
.thenReturn(docs2)
.thenReturn(emptyList);
this.contentTracker.doTrack();
this.contentTracker.doTrack("anIterationId");
InOrder order = inOrder(srv);
order.verify(srv).getDocsWithUncleanContent(0, READ_BATCH);
@@ -117,7 +117,7 @@ public class MetadataTrackerTest
nodes.add(node );
when(repositoryClient.getNodes(any(GetNodesParameters.class), anyInt())).thenReturn(nodes);
this.metadataTracker.doTrack();
this.metadataTracker.doTrack("AnIterationId");
InOrder inOrder = inOrder(srv);
inOrder.verify(srv).indexNodes(nodes, true);
@@ -141,7 +141,7 @@ public class MetadataTrackerTest
when(repositoryClient.getTransactions(anyLong(), anyLong(), anyLong(), anyLong(), anyInt(), any(ShardState.class))).thenReturn(txs);
when(repositoryClient.getTransactions(anyLong(), anyLong(), anyLong(), anyLong(), anyInt())).thenReturn(txs);
this.metadataTracker.doTrack();
this.metadataTracker.doTrack("AnIterationId");
verify(srv, never()).commit();
}
@@ -118,7 +118,7 @@ public class ModelTrackerIT
public void testDoTrack() throws AuthenticationException, IOException, JSONException
{
ModelTracker spiedModelTracker = spy(this.modelTracker);
spiedModelTracker.doTrack();
spiedModelTracker.doTrack("AnIterationId");
verify(this.srv).getRegisteredSearcherCount();
verify(spiedModelTracker).trackModels(false);
@@ -0,0 +1,245 @@
/*
* Copyright (C) 2005-2020 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.client;
import java.io.BufferedReader;
import java.util.LinkedList;
import static java.util.stream.Collector.of;
import org.slf4j.Logger;
import java.io.IOException;
import java.io.Reader;
/**
* This is an enhanced {@link BufferedReader} used for transparently collect data from the incoming stream before
* it gets consumed by the usual buffered reader logic. The data is not actually consumed, it is just buffered beside and
* it can be printed in case we want to debug the underlying stream content.
*
* The name refers to the usage pattern: this reader is used for wrapping a character stream coming from a remote call and, in case
* of issues, it collects the remaining (i.e. unread) part of the stream so it will be available for debugging purposes.
*
* It provides two different collecting modes:
*
* <ul>
* <li>
* windowing: the collected data is a window of the original stream (about 500 chars), and the character that
* caused a stop in the reading is more or less in the middle of that window.
* </li>
* <li>
* everything: the collected data is the whole character stream
* </li>
* </ul>
*
* The two modes are activated on each instance depending on the level of the {@link Logger} passed in input.
* Specifically:
*
* <ul>
* <li>DEBUG: enables the windowing mode</li>
* <li>TRACE: enables the "collect everything" mode</li>
* <li>other levels simply disables the buffering behaviour (i.e. nothing is collected)</li>
* </ul>
*
*/
public class LookAheadBufferedReader extends BufferedReader
{
final static String BUFFERING_DISABLED_INFO_MESSAGE = "Not available: please set the logging LEVEL to DEBUG or TRACE.";
private interface BufferingMode
{
void append(char ch);
void forceAppend(char ch);
boolean canAccept(boolean force);
}
private static class Windowing implements BufferingMode
{
private final LinkedList<Character> window = new LinkedList<>();
private final int maxSize;
private Windowing(int maxSize)
{
this.maxSize = maxSize;
}
@Override
public void append(char ch)
{
window.add(ch);
if (window.size() == maxSize)
{
window.removeFirst();
}
}
@Override
public void forceAppend(char ch)
{
window.add(ch);
}
@Override
public boolean canAccept(boolean force)
{
return window.size() < (maxSize * 2);
}
@Override
public String toString()
{
return window.stream()
.collect(
of(
StringBuilder::new,
StringBuilder::append,
StringBuilder::append,
StringBuilder::toString));
}
}
private static class WholeValue implements BufferingMode
{
private final StringBuilder content = new StringBuilder();
@Override
public void append(char ch)
{
content.append(ch);
}
@Override
public void forceAppend(char ch)
{
content.append(ch);
}
@Override
public String toString()
{
return content.toString();
}
@Override
public boolean canAccept(boolean force)
{
return true;
}
}
private static class NoOp implements BufferingMode
{
@Override
public void append(char ch)
{
// Nothing to be done here
}
@Override
public void forceAppend(char ch)
{
// Nothing to be done here
}
@Override
public boolean canAccept(boolean force)
{
return false;
}
@Override
public String toString()
{
return BUFFERING_DISABLED_INFO_MESSAGE;
}
}
private final BufferingMode bufferingMode;
LookAheadBufferedReader(Reader in, final int windowSize, boolean isDebugEnabled, boolean isTraceEnabled)
{
super(in);
if (isTraceEnabled)
{
bufferingMode = new WholeValue();
}
else if(isDebugEnabled)
{
bufferingMode = new Windowing(windowSize);
}
else {
bufferingMode = new NoOp();
}
}
public LookAheadBufferedReader(Reader in, final int windowSize, Logger logger)
{
this(in, windowSize, logger.isDebugEnabled(), logger.isTraceEnabled());
}
public LookAheadBufferedReader(Reader in, Logger logger)
{
this(in, 250, logger);
}
@Override
public int read() throws IOException
{
int ch = super.read();
if (ch != -1) bufferingMode.append((char)ch);
return ch;
}
public String lookAheadAndGetBufferedContent()
{
try
{
int ch;
while ((ch = super.read()) != -1 && bufferingMode.canAccept(true))
{
bufferingMode.forceAppend((char) ch);
}
}
catch (Exception ignore)
{
// Ignore any I/O exception causing further reading on the underlying stream
// Just return the collected data
}
return bufferingMode.toString();
}
boolean isInWindowingMode()
{
return bufferingMode instanceof Windowing;
}
boolean isInCollectEverythingMode()
{
return bufferingMode instanceof WholeValue;
}
boolean isBufferingDisabled()
{
return bufferingMode instanceof NoOp;
}
}
@@ -1,31 +1,32 @@
/*
* #%L
* Alfresco Solr Client
* %%
* 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/>.
* #L%
*/
/*
* #%L
* Alfresco Solr Client
* %%
* 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/>.
* #L%
*/
package org.alfresco.solr.client;
import java.util.List;
import java.util.OptionalInt;
/**
* Stores node meta data query parameters for use in SOLR remote api calls
@@ -39,7 +40,7 @@ public class NodeMetaDataParameters
private Long toTxnId;
// default is 'all' results
private int maxResults = 0;
private OptionalInt maxResults = OptionalInt.empty();
private Long fromNodeId;
private Long toNodeId;
@@ -147,14 +148,14 @@ public class NodeMetaDataParameters
this.includeParentAssociations = includeParentAssociations;
}
public int getMaxResults()
public OptionalInt getMaxResults()
{
return maxResults;
}
public void setMaxResults(int maxResults)
{
this.maxResults = maxResults;
this.maxResults = OptionalInt.of(maxResults);
}
public List<Long> getNodeIds()
@@ -25,27 +25,15 @@
*/
package org.alfresco.solr.client;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import org.alfresco.error.AlfrescoRuntimeException;
import org.alfresco.httpclient.AlfrescoHttpClient;
import org.alfresco.httpclient.AuthenticationException;
import org.alfresco.httpclient.GetRequest;
import org.alfresco.httpclient.PostRequest;
import org.alfresco.httpclient.Request;
import org.alfresco.httpclient.Response;
import org.alfresco.repo.dictionary.M2Model;
import org.alfresco.repo.dictionary.NamespaceDAO;
@@ -81,9 +69,23 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.extensions.surf.util.URLEncoder;
import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import java.io.Closeable;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import static java.util.Optional.ofNullable;
// TODO error handling, including dealing with a repository that is not responsive (ConnectException in sendRemoteRequest)
// TODO get text content transform status handling
@@ -94,7 +96,7 @@ import com.fasterxml.jackson.core.JsonToken;
*/
public class SOLRAPIClient
{
protected final static Logger log = LoggerFactory.getLogger(SOLRAPIClient.class);
protected final static Logger LOGGER = LoggerFactory.getLogger(SOLRAPIClient.class);
private static final String GET_ACL_CHANGESETS_URL = "api/solr/aclchangesets";
private static final String GET_ACLS = "api/solr/acls";
private static final String GET_ACLS_READERS = "api/solr/aclsReaders";
@@ -142,7 +144,7 @@ public class SOLRAPIClient
this.jsonFactory = new JsonFactory();
this.compression = compression;
}
/**
* Get the ACL ChangeSets
*
@@ -179,32 +181,7 @@ public class SOLRAPIClient
url.append(args);
GetRequest req = new GetRequest(url.toString());
Response response = null;
JSONObject json = null;
try
{
response = repositoryHttpClient.sendRequest(req);
if (response.getStatus() != HttpStatus.SC_OK)
{
throw new AlfrescoRuntimeException(GET_ACL_CHANGESETS_URL + " return status:" + response.getStatus());
}
Reader reader = new BufferedReader(new InputStreamReader(response.getContentAsStream(), "UTF-8"));
json = new JSONObject(new JSONTokener(reader));
}
finally
{
if(response != null)
{
response.release();
}
}
if (log.isDebugEnabled())
{
log.debug(json.toString(3));
}
JSONObject json = callRepository(GET_ACL_CHANGESETS_URL, req);
JSONArray aclChangeSetsJSON = json.getJSONArray("aclChangeSets");
List<AclChangeSet> aclChangeSets = new ArrayList<AclChangeSet>(aclChangeSetsJSON.length());
@@ -217,8 +194,7 @@ public class SOLRAPIClient
AclChangeSet aclChangeSet = new AclChangeSet(aclChangeSetId, commitTimeMs, aclCount);
aclChangeSets.add(aclChangeSet);
}
Long maxChangeSetCommitTime = null;
if(json.has("maxChangeSetCommitTime"))
{
@@ -273,32 +249,7 @@ public class SOLRAPIClient
jsonReq.put("aclChangeSetIds", aclChangeSetIdsJSON);
PostRequest req = new PostRequest(url.toString(), jsonReq.toString(), "application/json");
Response response = null;
JSONObject json = null;
try
{
response = repositoryHttpClient.sendRequest(req);
if (response.getStatus() != HttpStatus.SC_OK)
{
throw new AlfrescoRuntimeException(GET_ACL_CHANGESETS_URL + " return status:" + response.getStatus());
}
Reader reader = new BufferedReader(new InputStreamReader(response.getContentAsStream(), "UTF-8"));
json = new JSONObject(new JSONTokener(reader));
}
finally
{
if(response != null)
{
response.release();
}
}
if (log.isDebugEnabled())
{
log.debug(json.toString(3));
}
JSONObject json = callRepository(GET_ACL_CHANGESETS_URL, req);
JSONArray aclsJSON = json.getJSONArray("acls");
List<Acl> acls = new ArrayList<Acl>(aclsJSON.length());
@@ -336,32 +287,7 @@ public class SOLRAPIClient
jsonReq.put("aclIds", aclIdsJSON);
PostRequest req = new PostRequest(url.toString(), jsonReq.toString(), "application/json");
Response response = null;
JSONObject json = null;
try
{
response = repositoryHttpClient.sendRequest(req);
if (response.getStatus() != HttpStatus.SC_OK)
{
throw new AlfrescoRuntimeException(GET_ACLS_READERS + " return status:" + response.getStatus());
}
Reader reader = new BufferedReader(new InputStreamReader(response.getContentAsStream(), "UTF-8"));
json = new JSONObject(new JSONTokener(reader));
}
finally
{
if(response != null)
{
response.release();
}
}
if (log.isDebugEnabled())
{
log.debug(json.toString(3));
}
JSONObject json = callRepository(GET_ACLS_READERS, req);
JSONArray aclsReadersJSON = json.getJSONArray("aclsReaders");
List<AclReaders> aclsReaders = new ArrayList<AclReaders>(aclsReadersJSON.length());
@@ -421,7 +347,6 @@ public class SOLRAPIClient
public Transactions getTransactions(Long fromCommitTime, Long minTxnId, Long toCommitTime, Long maxTxnId, int maxResults, ShardState shardState) throws AuthenticationException, IOException, JSONException, EncoderException
{
log.debug("### get transactions ###");
URLCodec encoder = new URLCodec();
StringBuilder url = new StringBuilder(GET_TRANSACTIONS_URL);
@@ -448,7 +373,7 @@ public class SOLRAPIClient
}
if(shardState != null)
{
log.debug("### Shard state exists ###");
LOGGER.debug("### Shard state exists ###");
args.append(args.length() == 0 ? "?" : "&");
args.append(encoder.encode("baseUrl")).append("=").append(encoder.encode(shardState.getShardInstance().getBaseUrl()));
args.append("&").append(encoder.encode("hostName")).append("=").append(encoder.encode(shardState.getShardInstance().getHostName()));
@@ -497,12 +422,14 @@ public class SOLRAPIClient
}
url.append(args);
log.debug("### GetRequest: " + url.toString());
LOGGER.debug("### GetRequest: " + url.toString());
GetRequest req = new GetRequest(url.toString());
Response response = null;
List<Transaction> transactions = new ArrayList<Transaction>();
Long maxTxnCommitTime = null;
Long maxTxnIdOnServer = null;
LookAheadBufferedReader reader = null;
try
{
response = repositoryHttpClient.sendRequest(req);
@@ -510,7 +437,8 @@ public class SOLRAPIClient
{
throw new AlfrescoRuntimeException("GetTransactions return status is " + response.getStatus());
}
Reader reader = new BufferedReader(new InputStreamReader(response.getContentAsStream(), "UTF-8"));
reader = new LookAheadBufferedReader(new InputStreamReader(response.getContentAsStream(), StandardCharsets.UTF_8), LOGGER);
JsonParser parser = jsonFactory.createParser(reader);
JsonToken token = parser.nextValue();
@@ -556,18 +484,25 @@ public class SOLRAPIClient
token = parser.nextValue();
}
parser.close();
reader.close();
}
catch (JSONException exception)
{
String message = "Received a malformed JSON payload. Request was \"" +
req.getFullUri() +
"Data: "
+ ofNullable(reader)
.map(LookAheadBufferedReader::lookAheadAndGetBufferedContent)
.orElse("Not available");
LOGGER.error(message);
throw exception;
}
finally
{
log.debug("## end getTransactions");
if(response != null)
{
response.release();
}
ofNullable(response).ifPresent(Response::release);
ofNullable(reader).ifPresent(this::silentlyClose);
}
log.debug("### Transactions found maxTxnCommitTime: " + maxTxnCommitTime );
LOGGER.debug("### Transactions found maxTxnCommitTime: " + maxTxnCommitTime );
return new Transactions(transactions, maxTxnCommitTime, maxTxnIdOnServer);
}
@@ -637,35 +572,10 @@ public class SOLRAPIClient
PostRequest req = new PostRequest(url.toString(), body.toString(), "application/json");
Response response = null;
JSONObject json = null;
try
{
response = repositoryHttpClient.sendRequest(req);
if(response.getStatus() != HttpStatus.SC_OK)
{
throw new AlfrescoRuntimeException("GetNodes return status is " + response.getStatus());
}
Reader reader = new BufferedReader(new InputStreamReader(response.getContentAsStream(), "UTF-8"));
json = new JSONObject(new JSONTokener(reader));
}
finally
{
if(response != null)
{
response.release();
}
}
if(log.isDebugEnabled())
{
log.debug(json.toString());
}
JSONObject json = callRepository(GET_NODES_URL, req);
JSONArray jsonNodes = json.getJSONArray("nodes");
List<Node> nodes = new ArrayList<Node>(jsonNodes.length());
List<Node> nodes = new ArrayList<>(jsonNodes.length());
for(int i = 0; i < jsonNodes.length(); i++)
{
JSONObject jsonNodeInfo = jsonNodes.getJSONObject(i);
@@ -821,7 +731,7 @@ public class SOLRAPIClient
return ret;
}
public List<NodeMetaData> getNodesMetaData(NodeMetaDataParameters params, int maxResults) throws AuthenticationException, IOException, JSONException
public List<NodeMetaData> getNodesMetaData(NodeMetaDataParameters params) throws AuthenticationException, IOException, JSONException
{
List<Long> nodeIds = params.getNodeIds();
@@ -890,38 +800,16 @@ public class SOLRAPIClient
body.put("includeTxnId", params.isIncludeTxnId());
}
body.put("maxResults", maxResults);
if (params.getMaxResults().isPresent())
{
body.put("maxResults", params.getMaxResults().getAsInt());
}
PostRequest req = new PostRequest(url.toString(), body.toString(), "application/json");
Response response = null;
JSONObject json = null;
try
{
response = repositoryHttpClient.sendRequest(req);
if(response.getStatus() != HttpStatus.SC_OK)
{
throw new AlfrescoRuntimeException("GetNodeMetaData return status is " + response.getStatus());
}
Reader reader = new BufferedReader(new InputStreamReader(response.getContentAsStream(), "UTF-8"));
json = new JSONObject(new JSONTokener(reader));
}
finally
{
if(response != null)
{
response.release();
}
}
JSONObject json = callRepository(GET_METADATA_URL, req);
if (log.isDebugEnabled())
{
log.debug(json.toString(3));
}
JSONArray jsonNodes = json.getJSONArray("nodes");
List<NodeMetaData> nodes = new ArrayList<NodeMetaData>(jsonNodes.length());
List<NodeMetaData> nodes = new ArrayList<>(jsonNodes.length());
for(int i = 0; i < jsonNodes.length(); i++)
{
JSONObject jsonNodeInfo = jsonNodes.getJSONObject(i);
@@ -1159,7 +1047,7 @@ public class SOLRAPIClient
return new GetTextContentResponse(response);
}
public AlfrescoModel getModel(String coreName, QName modelName) throws AuthenticationException, IOException, JSONException
public AlfrescoModel getModel(String coreName, QName modelName) throws AuthenticationException, IOException
{
// If the model is new to the SOLR side the prefix will be unknown so we can not generate prefixes for the request!
// Always use the full QName with explicit URI
@@ -1209,31 +1097,8 @@ public class SOLRAPIClient
body.put("models", jsonModels);
PostRequest req = new PostRequest(url.toString(), body.toString(), "application/json");
Response response = null;
JSONObject json = null;
try
{
response = repositoryHttpClient.sendRequest(req);
if(response.getStatus() != HttpStatus.SC_OK)
{
throw new AlfrescoRuntimeException(coreName + " GetModelsDiff return status is " + response.getStatus());
}
Reader reader = new BufferedReader(new InputStreamReader(response.getContentAsStream(), "UTF-8"));
json = new JSONObject(new JSONTokener(reader));
}
finally
{
if(response != null)
{
response.release();
}
}
if(log.isDebugEnabled())
{
log.debug(json.toString());
}
JSONObject json = callRepository(GET_MODELS_DIFF, req);
JSONArray jsonDiffs = json.getJSONArray("diffs");
if(jsonDiffs == null)
{
@@ -1271,6 +1136,7 @@ public class SOLRAPIClient
GetRequest get = new GetRequest(url.toString());
Response response = null;
JSONObject json = null;
LookAheadBufferedReader reader = null;
try
{
response = repositoryHttpClient.sendRequest(get);
@@ -1280,19 +1146,29 @@ public class SOLRAPIClient
+ response.getStatus() + " when invoking " + url);
}
Reader reader = new BufferedReader(new InputStreamReader(response.getContentAsStream(), "UTF-8"));
json = new JSONObject(new JSONTokener(reader));
reader = new LookAheadBufferedReader(new InputStreamReader(response.getContentAsStream(), StandardCharsets.UTF_8), LOGGER);
json = new JSONObject(new JSONTokener(reader));
}
catch (JSONException exception)
{
String message = "Received a malformed JSON payload. Request was \"" +
get.getFullUri() +
"Data: "
+ ofNullable(reader)
.map(LookAheadBufferedReader::lookAheadAndGetBufferedContent)
.orElse("Not available");
LOGGER.error(message);
throw exception;
}
finally
{
if (response != null)
{
response.release();
}
ofNullable(response).ifPresent(Response::release);
ofNullable(reader).ifPresent(this::silentlyClose);
}
if (log.isDebugEnabled())
if (LOGGER.isDebugEnabled())
{
log.debug(json.toString());
LOGGER.debug(json.toString());
}
return Long.parseLong(json.get("nextTransactionCommitTimeMs").toString());
@@ -1318,6 +1194,7 @@ public class SOLRAPIClient
GetRequest get = new GetRequest(url.toString());
Response response = null;
JSONObject json = null;
LookAheadBufferedReader reader = null;
try
{
response = repositoryHttpClient.sendRequest(get);
@@ -1327,19 +1204,29 @@ public class SOLRAPIClient
+ response.getStatus() + " when invoking " + url);
}
Reader reader = new BufferedReader(new InputStreamReader(response.getContentAsStream(), "UTF-8"));
reader = new LookAheadBufferedReader(new InputStreamReader(response.getContentAsStream(), StandardCharsets.UTF_8), LOGGER);
json = new JSONObject(new JSONTokener(reader));
}
catch(JSONException exception)
{
String message = "Received a malformed JSON payload. Request was \"" +
get.getFullUri() +
"Data: "
+ ofNullable(reader)
.map(LookAheadBufferedReader::lookAheadAndGetBufferedContent)
.orElse("Not available");
LOGGER.error(message);
throw exception;
}
finally
{
if (response != null)
{
response.release();
}
ofNullable(response).ifPresent(Response::release);
ofNullable(reader).ifPresent(this::silentlyClose);
}
if (log.isDebugEnabled())
if (LOGGER.isDebugEnabled())
{
log.debug(json.toString());
LOGGER.debug(json.toString());
}
return new Pair<Long, Long>(Long.parseLong(json.get("minTransactionCommitTimeMs").toString()),
@@ -1692,4 +1579,56 @@ public class SOLRAPIClient
{
repositoryHttpClient.close();
}
private JSONObject callRepository(String msgId, Request req) throws IOException, AuthenticationException
{
Response response = null;
LookAheadBufferedReader reader = null;
JSONObject json;
try
{
response = repositoryHttpClient.sendRequest(req);
if (response.getStatus() != HttpStatus.SC_OK)
{
throw new AlfrescoRuntimeException(msgId + " return status:" + response.getStatus());
}
reader = new LookAheadBufferedReader(new InputStreamReader(response.getContentAsStream(), StandardCharsets.UTF_8), LOGGER);
json = new JSONObject(new JSONTokener(reader));
if (LOGGER.isDebugEnabled())
{
LOGGER.debug(json.toString(3));
}
return json;
}
catch (JSONException exception)
{
String message = "Received a malformed JSON payload. Request was \"" +
req.getFullUri() +
"Data: "
+ ofNullable(reader)
.map(LookAheadBufferedReader::lookAheadAndGetBufferedContent)
.orElse("Not available");
LOGGER.error(message);
throw exception;
}
finally
{
ofNullable(response).ifPresent(Response::release);
ofNullable(reader).ifPresent(this::silentlyClose);
}
}
private void silentlyClose(Closeable closeable)
{
try
{
closeable.close();
}
catch (Exception ignore)
{
// Nothing to be done here
}
}
}
@@ -285,7 +285,8 @@ public class SOLRAPIQueueClient extends SOLRAPIClient
return allNodes;
}
public List<NodeMetaData> getNodesMetaData(NodeMetaDataParameters params, int maxResults) throws AuthenticationException, IOException, JSONException
@Override
public List<NodeMetaData> getNodesMetaData(NodeMetaDataParameters params) throws AuthenticationException, IOException, JSONException
{
if(throwException) {
throw new ConnectException("THROWING EXCEPTION, better be ready!");
@@ -0,0 +1,210 @@
/*-
* #%L
* Alfresco Remote API
* %%
* Copyright (C) 2005 - 2020 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/>.
* #L%
*/
package org.alfresco.solr.client;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import java.io.Reader;
import java.io.StringReader;
import static java.util.stream.IntStream.range;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
@RunWith(MockitoJUnitRunner.class)
public class LookAheadBufferedReaderTest
{
@Mock
Reader reader;
private final String data = "1234567890ABCDEFGHILMNOPQRSTUVYXZ";
@Test
public void windowingModeEnabled()
{
LookAheadBufferedReader classUnderTest = new LookAheadBufferedReader(reader, data.length(), true, false);
assertTrue(classUnderTest.isInWindowingMode());
assertFalse(classUnderTest.isInCollectEverythingMode());
assertFalse(classUnderTest.isBufferingDisabled());
}
@Test
public void collectEverythingModeEnabled()
{
LookAheadBufferedReader classUnderTest = new LookAheadBufferedReader(reader, data.length(), false, true);
assertTrue(classUnderTest.isInCollectEverythingMode());
assertFalse(classUnderTest.isInWindowingMode());
assertFalse(classUnderTest.isBufferingDisabled());
}
@Test
public void bufferingDisabled()
{
LookAheadBufferedReader classUnderTest = new LookAheadBufferedReader(reader, data.length(), false, false);
assertTrue(classUnderTest.isBufferingDisabled());
assertFalse(classUnderTest.isInCollectEverythingMode());
assertFalse(classUnderTest.isInWindowingMode());
}
@Test
public void collectEverythingWinsOverWindowing()
{
LookAheadBufferedReader classUnderTest = new LookAheadBufferedReader(reader, data.length(), true, true);
assertTrue(classUnderTest.isInCollectEverythingMode());
assertFalse(classUnderTest.isInWindowingMode());
assertFalse(classUnderTest.isBufferingDisabled());
}
@Test
public void windowingModeShouldCollectPartialWindowsOfData()
{
int windowSize = 10;
Reader reader = new StringReader(data);
LookAheadBufferedReader classUnderTest = new LookAheadBufferedReader(reader, windowSize, true, false);
// Read only 12 chars from the underlying stream
range(0, 12).forEach(index -> consume(classUnderTest));
String collectedWindow = classUnderTest.lookAheadAndGetBufferedContent();
assertEquals(windowSize * 2, collectedWindow.length());
assertEquals("4567890ABCDEFGHILMNO", collectedWindow);
}
@Test
public void notEnoughCharsForTheWindow()
{
int windowSize = 10;
Reader reader = new StringReader(data);
LookAheadBufferedReader classUnderTest = new LookAheadBufferedReader(reader, windowSize, true, false);
range(0, data.length() - 3).forEach(index -> consume(classUnderTest));
String collectedWindow = classUnderTest.lookAheadAndGetBufferedContent();
assertEquals(windowSize + 2, collectedWindow.length());
assertEquals("NOPQRSTUVYXZ", collectedWindow);
}
@Test
public void emptyDataShouldCollectEmptyWindow()
{
Reader reader = new StringReader("");
LookAheadBufferedReader classUnderTest = new LookAheadBufferedReader(reader, 10, true, false);
range(0, data.length() - 3).forEach(index -> consume(classUnderTest));
String collectedWindow = classUnderTest.lookAheadAndGetBufferedContent();
assertEquals(0, collectedWindow.length());
assertEquals("", collectedWindow);
}
@Test
public void collectEverythingModeShouldCollectTheWholeStream()
{
Reader reader = new StringReader(data);
LookAheadBufferedReader classUnderTest =
new LookAheadBufferedReader(
reader,
10, // this has no effect
false,
true);
// Read only 12 chars from the underlying stream
range(0, 12).forEach(index -> consume(classUnderTest));
String collectedData = classUnderTest.lookAheadAndGetBufferedContent();
assertEquals(data.length(), collectedData.length());
assertEquals(data, collectedData);
}
@Test
public void emptyDataShouldCollectEmptyStringInCollectEverythingMode()
{
Reader reader = new StringReader("");
LookAheadBufferedReader classUnderTest =
new LookAheadBufferedReader(
reader,
10, // this has no effect
false,
true);
range(0, data.length() - 3).forEach(index -> consume(classUnderTest));
String collectedData = classUnderTest.lookAheadAndGetBufferedContent();
assertEquals(0, collectedData.length());
assertEquals("", collectedData);
}
@Test
public void bufferingModeDisableShouldCollectNoData()
{
int windowSize = 10;
Reader reader = new StringReader(data);
LookAheadBufferedReader classUnderTest = new LookAheadBufferedReader(reader, windowSize, false, false);
// Read only 12 chars from the underlying stream
range(0, 12).forEach(index -> consume(classUnderTest));
assertEquals(LookAheadBufferedReader.BUFFERING_DISABLED_INFO_MESSAGE, classUnderTest.lookAheadAndGetBufferedContent());
}
@Test
public void emptyDataShouldCollectEmptyDataWhenBufferingIsDisabled()
{
Reader reader = new StringReader("");
LookAheadBufferedReader classUnderTest =
new LookAheadBufferedReader(
reader,
10, // this has no effect
false,
false);
range(0, data.length() - 3).forEach(index -> consume(classUnderTest));
String collectedData = classUnderTest.lookAheadAndGetBufferedContent();
assertEquals(LookAheadBufferedReader.BUFFERING_DISABLED_INFO_MESSAGE, collectedData);
}
private void consume(Reader reader)
{
try
{
reader.read();
}
catch (Exception exception)
{
throw new RuntimeException(exception);
}
}
}
@@ -1,93 +1,93 @@
/*
* #%L
* Alfresco Solr Client
* %%
* 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/>.
* #L%
*/
/*
* #%L
* Alfresco Solr Client
* %%
* 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/>.
* #L%
*/
package org.alfresco.solr.client;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.security.AlgorithmParameters;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import org.alfresco.encryption.DefaultEncryptionUtils;
import org.alfresco.encryption.KeyProvider;
import org.alfresco.encryption.KeyResourceLoader;
import org.alfresco.encryption.KeyStoreParameters;
import org.alfresco.encryption.MACUtils.MACInput;
import org.alfresco.encryption.ssl.SSLEncryptionParameters;
import org.alfresco.error.AlfrescoRuntimeException;
import org.alfresco.httpclient.AlfrescoHttpClient;
import org.alfresco.httpclient.AuthenticationException;
import org.alfresco.httpclient.HttpClientFactory;
import org.alfresco.httpclient.HttpClientFactory.SecureCommsType;
import org.alfresco.opencmis.dictionary.CMISDictionaryRegistry;
import org.alfresco.opencmis.dictionary.CMISStrictDictionaryService;
import org.alfresco.opencmis.mapping.CMISMapping;
import org.alfresco.opencmis.mapping.RuntimePropertyLuceneBuilderMapping;
import org.alfresco.repo.cache.MemoryCache;
import org.alfresco.repo.dictionary.CompiledModelsCache;
import org.alfresco.repo.dictionary.DictionaryComponent;
import org.alfresco.repo.dictionary.DictionaryDAOImpl;
import org.alfresco.repo.dictionary.DictionaryNamespaceComponent;
import org.alfresco.repo.dictionary.M2Model;
import org.alfresco.repo.dictionary.M2Namespace;
import org.alfresco.repo.dictionary.NamespaceDAO;
import org.alfresco.repo.i18n.StaticMessageLookup;
import org.alfresco.repo.tenant.SingleTServiceImpl;
import org.alfresco.repo.tenant.TenantService;
import org.alfresco.service.namespace.QName;
import org.alfresco.util.DynamicallySizedThreadPoolExecutor;
import org.alfresco.util.Pair;
import org.alfresco.util.TraceableThreadFactory;
import org.alfresco.util.cache.DefaultAsynchronouslyRefreshedCacheRegistry;
import org.apache.chemistry.opencmis.commons.enums.CmisVersion;
import org.apache.commons.httpclient.HttpMethod;
import org.apache.commons.httpclient.methods.ByteArrayRequestEntity;
import org.apache.commons.httpclient.methods.PostMethod;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.json.JSONException;
import junit.framework.TestCase;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.security.AlgorithmParameters;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import org.alfresco.encryption.DefaultEncryptionUtils;
import org.alfresco.encryption.KeyProvider;
import org.alfresco.encryption.KeyResourceLoader;
import org.alfresco.encryption.KeyStoreParameters;
import org.alfresco.encryption.MACUtils.MACInput;
import org.alfresco.encryption.ssl.SSLEncryptionParameters;
import org.alfresco.error.AlfrescoRuntimeException;
import org.alfresco.httpclient.AlfrescoHttpClient;
import org.alfresco.httpclient.AuthenticationException;
import org.alfresco.httpclient.HttpClientFactory;
import org.alfresco.httpclient.HttpClientFactory.SecureCommsType;
import org.alfresco.opencmis.dictionary.CMISDictionaryRegistry;
import org.alfresco.opencmis.dictionary.CMISStrictDictionaryService;
import org.alfresco.opencmis.mapping.CMISMapping;
import org.alfresco.opencmis.mapping.RuntimePropertyLuceneBuilderMapping;
import org.alfresco.repo.cache.MemoryCache;
import org.alfresco.repo.dictionary.CompiledModelsCache;
import org.alfresco.repo.dictionary.DictionaryComponent;
import org.alfresco.repo.dictionary.DictionaryDAOImpl;
import org.alfresco.repo.dictionary.DictionaryNamespaceComponent;
import org.alfresco.repo.dictionary.M2Model;
import org.alfresco.repo.dictionary.M2Namespace;
import org.alfresco.repo.dictionary.NamespaceDAO;
import org.alfresco.repo.i18n.StaticMessageLookup;
import org.alfresco.repo.tenant.SingleTServiceImpl;
import org.alfresco.repo.tenant.TenantService;
import org.alfresco.service.namespace.QName;
import org.alfresco.util.DynamicallySizedThreadPoolExecutor;
import org.alfresco.util.Pair;
import org.alfresco.util.TraceableThreadFactory;
import org.alfresco.util.cache.DefaultAsynchronouslyRefreshedCacheRegistry;
import org.apache.chemistry.opencmis.commons.enums.CmisVersion;
import org.apache.commons.httpclient.HttpMethod;
import org.apache.commons.httpclient.methods.ByteArrayRequestEntity;
import org.apache.commons.httpclient.methods.PostMethod;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.json.JSONException;
import junit.framework.TestCase;
/**
* Tests {@link SOLRAPIClient} Note: need to make sure that source/solr/instance is on the run classpath. Note: doesn't
* currently work, need to change to use SSL.
@@ -109,7 +109,7 @@ public class SOLRAPIClientTest extends TestCase
private DictionaryDAOImpl dictionaryDAO;
private CMISStrictDictionaryService cmisDictionaryService;
private static final String CORENAME = "collection1";
// private M2Model testModel;
@@ -140,10 +140,10 @@ public class SOLRAPIClientTest extends TestCase
{
if(client == null)
{
TenantService tenantService = new SingleTServiceImpl();
TenantService tenantService = new SingleTServiceImpl();
dictionaryDAO = new DictionaryDAOImpl();
NamespaceDAO namespaceDAO = dictionaryDAO;
NamespaceDAO namespaceDAO = dictionaryDAO;
dictionaryDAO.setTenantService(tenantService);
CompiledModelsCache compiledModelsCache = new CompiledModelsCache();
@@ -163,7 +163,7 @@ public class SOLRAPIClientTest extends TestCase
dictionaryDAO.setResourceClassLoader(getResourceClassLoader());
dictionaryDAO.init();
DictionaryComponent dictionaryComponent = new DictionaryComponent();
DictionaryComponent dictionaryComponent = new DictionaryComponent();
dictionaryComponent.setDictionaryDAO(dictionaryDAO);
dictionaryComponent.setMessageLookup(new StaticMessageLookup());
@@ -283,7 +283,7 @@ public class SOLRAPIClientTest extends TestCase
8443, 40, 40, 0);
// TODO need to make port configurable depending on secure comms, or just make redirects
// work
return httpClientFactory.getRepoClient("localhost", 8443);
return httpClientFactory.getRepoClient("localhost", 8443);
}
public ClassLoader getResourceClassLoader()
@@ -452,7 +452,8 @@ public class SOLRAPIClientTest extends TestCase
NodeMetaDataParameters metaParams = new NodeMetaDataParameters();
metaParams.setNodeIds(nodeIds);
List<NodeMetaData> metadata = client.getNodesMetaData(metaParams, 3);
metaParams.setMaxResults(3);
List<NodeMetaData> metadata = client.getNodesMetaData(metaParams);
for (NodeMetaData info : metadata)
{
logger.debug(info);
@@ -463,9 +464,10 @@ public class SOLRAPIClientTest extends TestCase
{
NodeMetaDataParameters metaParams = new NodeMetaDataParameters();
List<Long> nodeIds = new ArrayList<Long>(1);
nodeIds.add(1l);
nodeIds.add(1L);
metaParams.setMaxResults(3);
metaParams.setNodeIds(nodeIds);
List<NodeMetaData> metadata = client.getNodesMetaData(metaParams, 3);
List<NodeMetaData> metadata = client.getNodesMetaData(metaParams);
for (NodeMetaData info : metadata)
{
logger.debug(info);
@@ -473,9 +475,10 @@ public class SOLRAPIClientTest extends TestCase
metaParams = new NodeMetaDataParameters();
nodeIds = new ArrayList<Long>(1);
nodeIds.add(9l);
nodeIds.add(9L);
metaParams.setNodeIds(nodeIds);
metadata = client.getNodesMetaData(metaParams, 3);
metaParams.setMaxResults(3);
metadata = client.getNodesMetaData(metaParams);
for (NodeMetaData info : metadata)
{
logger.debug(info);
@@ -483,9 +486,10 @@ public class SOLRAPIClientTest extends TestCase
metaParams = new NodeMetaDataParameters();
nodeIds = new ArrayList<Long>(1);
nodeIds.add(19l);
nodeIds.add(19L);
metaParams.setNodeIds(nodeIds);
metadata = client.getNodesMetaData(metaParams, 3);
metaParams.setMaxResults(3);
metadata = client.getNodesMetaData(metaParams);
for (NodeMetaData info : metadata)
{
logger.debug(info);
@@ -495,9 +499,10 @@ public class SOLRAPIClientTest extends TestCase
// TODO check why the category path has a null QName
metaParams = new NodeMetaDataParameters();
nodeIds = new ArrayList<Long>(1);
nodeIds.add(49437l);
nodeIds.add(49437L);
metaParams.setNodeIds(nodeIds);
metadata = client.getNodesMetaData(metaParams, 3);
metaParams.setMaxResults(3);
metadata = client.getNodesMetaData(metaParams);
for (NodeMetaData info : metadata)
{
logger.debug(info);
@@ -506,9 +511,10 @@ public class SOLRAPIClientTest extends TestCase
// content with tags
metaParams = new NodeMetaDataParameters();
nodeIds = new ArrayList<Long>(1);
nodeIds.add(49431l);
nodeIds.add(49431L);
metaParams.setNodeIds(nodeIds);
metadata = client.getNodesMetaData(metaParams, 3);
metaParams.setMaxResults(3);
metadata = client.getNodesMetaData(metaParams);
for (NodeMetaData info : metadata)
{
logger.debug(info);
@@ -525,9 +531,10 @@ public class SOLRAPIClientTest extends TestCase
// content with null property values for author
metaParams = new NodeMetaDataParameters();
nodeIds = new ArrayList<Long>(1);
nodeIds.add(117630l);
nodeIds.add(117630L);
metaParams.setNodeIds(nodeIds);
metadata = client.getNodesMetaData(metaParams, 3);
metaParams.setMaxResults(3);
metadata = client.getNodesMetaData(metaParams);
for (NodeMetaData info : metadata)
{
logger.debug(info);
@@ -536,9 +543,10 @@ public class SOLRAPIClientTest extends TestCase
// content with accented characters in title properties
metaParams = new NodeMetaDataParameters();
nodeIds = new ArrayList<Long>(1);
nodeIds.add(117678l);
nodeIds.add(117678L);
metaParams.setNodeIds(nodeIds);
metadata = client.getNodesMetaData(metaParams, 3);
metaParams.setMaxResults(3);
metadata = client.getNodesMetaData(metaParams);
for (NodeMetaData info : metadata)
{
logger.debug(info);