diff --git a/search-services/alfresco-solrclient-lib/src/main/java/org/alfresco/solr/client/LookAheadBufferedReader.java b/search-services/alfresco-solrclient-lib/src/main/java/org/alfresco/solr/client/LookAheadBufferedReader.java
new file mode 100644
index 000000000..50edf4018
--- /dev/null
+++ b/search-services/alfresco-solrclient-lib/src/main/java/org/alfresco/solr/client/LookAheadBufferedReader.java
@@ -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 .
+ */
+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:
+ *
+ *
+ * -
+ * 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.
+ *
+ * -
+ * everything: the collected data is the whole character stream
+ *
+ *
+ *
+ * The two modes are activated on each instance depending on the level of the {@link Logger} passed in input.
+ * Specifically:
+ *
+ *
+ * - DEBUG: enables the windowing mode
+ * - TRACE: enables the "collect everything" mode
+ * - other levels simply disables the buffering behaviour (i.e. nothing is collected)
+ *
+ *
+ */
+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 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;
+ }
+}
\ No newline at end of file
diff --git a/search-services/alfresco-solrclient-lib/src/main/java/org/alfresco/solr/client/SOLRAPIClient.java b/search-services/alfresco-solrclient-lib/src/main/java/org/alfresco/solr/client/SOLRAPIClient.java
index a4cbe6b4a..628114700 100644
--- a/search-services/alfresco-solrclient-lib/src/main/java/org/alfresco/solr/client/SOLRAPIClient.java
+++ b/search-services/alfresco-solrclient-lib/src/main/java/org/alfresco/solr/client/SOLRAPIClient.java
@@ -25,11 +25,13 @@
*/
package org.alfresco.solr.client;
-import java.io.BufferedReader;
+import static java.util.Optional.ofNullable;
+
+import java.io.Closeable;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
-import java.io.Reader;
+import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
@@ -46,6 +48,7 @@ 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;
@@ -94,7 +97,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 +145,7 @@ public class SOLRAPIClient
this.jsonFactory = new JsonFactory();
this.compression = compression;
}
-
+
/**
* Get the ACL ChangeSets
*
@@ -179,32 +182,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 aclChangeSets = new ArrayList(aclChangeSetsJSON.length());
@@ -217,8 +195,7 @@ public class SOLRAPIClient
AclChangeSet aclChangeSet = new AclChangeSet(aclChangeSetId, commitTimeMs, aclCount);
aclChangeSets.add(aclChangeSet);
}
-
-
+
Long maxChangeSetCommitTime = null;
if(json.has("maxChangeSetCommitTime"))
{
@@ -273,32 +250,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 acls = new ArrayList(aclsJSON.length());
@@ -336,32 +288,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 aclsReaders = new ArrayList(aclsReadersJSON.length());
@@ -421,7 +348,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 +374,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 +423,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 transactions = new ArrayList();
Long maxTxnCommitTime = null;
Long maxTxnIdOnServer = null;
+
+ LookAheadBufferedReader reader = null;
try
{
response = repositoryHttpClient.sendRequest(req);
@@ -510,7 +438,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 +485,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 +573,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 nodes = new ArrayList(jsonNodes.length());
+ List nodes = new ArrayList<>(jsonNodes.length());
for(int i = 0; i < jsonNodes.length(); i++)
{
JSONObject jsonNodeInfo = jsonNodes.getJSONObject(i);
@@ -893,34 +804,9 @@ public class SOLRAPIClient
body.put("maxResults", maxResults);
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 nodes = new ArrayList(jsonNodes.length());
for(int i = 0; i < jsonNodes.length(); i++)
{
@@ -1159,7 +1045,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 +1095,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 +1134,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 +1144,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 +1192,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 +1202,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.parseLong(json.get("minTransactionCommitTimeMs").toString()),
@@ -1692,4 +1577,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
+ }
+ }
}
diff --git a/search-services/alfresco-solrclient-lib/src/test/java/org/alfresco/solr/client/LookAheadBufferedReaderTest.java b/search-services/alfresco-solrclient-lib/src/test/java/org/alfresco/solr/client/LookAheadBufferedReaderTest.java
new file mode 100644
index 000000000..f51142c43
--- /dev/null
+++ b/search-services/alfresco-solrclient-lib/src/test/java/org/alfresco/solr/client/LookAheadBufferedReaderTest.java
@@ -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 .
+ * #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);
+ }
+ }
+}
\ No newline at end of file