From 0f6950a72e5d549f42abd52d5e5b0d86f7728234 Mon Sep 17 00:00:00 2001 From: Maciej Pichura <41297682+mpichura@users.noreply.github.com> Date: Wed, 25 Sep 2024 15:27:06 +0200 Subject: [PATCH] MNT-24346 Fix "0" return status code when SORL unavailable (#2942) * MNT-24346: Fix 0 status code in Search API when Solr is unavailable. * MNT-24346: Fixing PMD issues. * MNT-24346: Fixing formatting. * MNT-24346: Reverting accidental method name change. * MNT-24346: Fixing spotless issues. * MNT-24346: Fixing spotless issues. * MNT-24346: Fixing spotless issues. * MNT-24346 Fix line endings. --------- Co-authored-by: Tom Page --- .../solr/AbstractSolrQueryHTTPClient.java | 26 +- .../solr/AbstractSolrQueryHTTPClientTest.java | 346 +++++++++--------- 2 files changed, 186 insertions(+), 186 deletions(-) diff --git a/repository/src/main/java/org/alfresco/repo/search/impl/solr/AbstractSolrQueryHTTPClient.java b/repository/src/main/java/org/alfresco/repo/search/impl/solr/AbstractSolrQueryHTTPClient.java index e901917ebd..25b5d632e0 100644 --- a/repository/src/main/java/org/alfresco/repo/search/impl/solr/AbstractSolrQueryHTTPClient.java +++ b/repository/src/main/java/org/alfresco/repo/search/impl/solr/AbstractSolrQueryHTTPClient.java @@ -2,23 +2,23 @@ * #%L * Alfresco Repository * %% - * Copyright (C) 2005 - 2023 Alfresco Software Limited + * Copyright (C) 2005 - 2024 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 + * 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% @@ -30,11 +30,8 @@ import java.io.IOException; import java.io.InputStreamReader; import java.io.Reader; import java.util.List; - import jakarta.servlet.http.HttpServletResponse; -import org.alfresco.httpclient.HttpClientException; -import org.alfresco.repo.search.QueryParserException; import org.apache.commons.httpclient.Header; import org.apache.commons.httpclient.HttpClient; import org.apache.commons.httpclient.HttpStatus; @@ -48,13 +45,16 @@ import org.json.JSONException; import org.json.JSONObject; import org.json.JSONTokener; +import org.alfresco.httpclient.HttpClientException; +import org.alfresco.repo.search.QueryParserException; + public abstract class AbstractSolrQueryHTTPClient { /** Logger for the class. */ private static final Log LOGGER = LogFactory.getLog(AbstractSolrQueryHTTPClient.class); public static final int DEFAULT_SAVEPOST_BUFFER = 4096; - + // Constants copied from org.apache.solr.common.params.HighlightParams (solr-solrj:1.4.1) // These values have been moved to this Alfresco class to avoid using solr-solrj library as dependency public static final String HIGHLIGHT_PARAMS_HIGHLIGHT = "hl"; @@ -86,7 +86,7 @@ public abstract class AbstractSolrQueryHTTPClient /** List of SOLR Exceptions that should be returning HTTP 501 status code in Remote API. */ private static final List STATUS_CODE_501_EXCEPTIONS = List.of("java.lang.UnsupportedOperationException"); - + protected JSONObject postQuery(HttpClient httpClient, String url, JSONObject body) throws IOException, JSONException { PostMethod post = createNewPostMethod(url); @@ -99,7 +99,7 @@ public abstract class AbstractSolrQueryHTTPClient try { httpClient.executeMethod(post); - if(post.getStatusCode() == HttpStatus.SC_MOVED_PERMANENTLY || post.getStatusCode() == HttpStatus.SC_MOVED_TEMPORARILY) + if (post.getStatusCode() == HttpStatus.SC_MOVED_PERMANENTLY || post.getStatusCode() == HttpStatus.SC_MOVED_TEMPORARILY) { Header locationHeader = post.getResponseHeader("location"); if (locationHeader != null) diff --git a/repository/src/test/java/org/alfresco/repo/search/impl/solr/AbstractSolrQueryHTTPClientTest.java b/repository/src/test/java/org/alfresco/repo/search/impl/solr/AbstractSolrQueryHTTPClientTest.java index a2926ee02d..8be37a4db3 100644 --- a/repository/src/test/java/org/alfresco/repo/search/impl/solr/AbstractSolrQueryHTTPClientTest.java +++ b/repository/src/test/java/org/alfresco/repo/search/impl/solr/AbstractSolrQueryHTTPClientTest.java @@ -1,173 +1,173 @@ -/* - * #%L - * Alfresco Repository - * %% - * Copyright (C) 2005 - 2023 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.repo.search.impl.solr; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertThrows; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; -import static org.mockito.MockitoAnnotations.openMocks; - -import jakarta.servlet.http.HttpServletResponse; -import java.io.ByteArrayInputStream; -import java.io.IOException; - -import org.alfresco.httpclient.HttpClientException; -import org.alfresco.repo.search.QueryParserException; -import org.apache.commons.httpclient.Header; -import org.apache.commons.httpclient.HttpClient; -import org.apache.commons.httpclient.URI; -import org.apache.commons.httpclient.methods.PostMethod; -import org.json.JSONObject; -import org.junit.Before; -import org.junit.Test; -import org.mockito.Mock; - -/** Tests for the {@link AbstractSolrQueryHTTPClient}. */ -public class AbstractSolrQueryHTTPClientTest -{ - /** A URL for use in the tests. */ - private static final String URL = "http://this/is/a/url"; - - /** The abstract class under test. */ - private final AbstractSolrQueryHTTPClient abstractSolrQueryHTTPClient = spy(AbstractSolrQueryHTTPClient.class); - @Mock - private HttpClient httpClient; - @Mock - private JSONObject body; - @Mock - private PostMethod postMethod; - @Mock - private Header header; - - @Before - public void setUp() throws Exception - { - openMocks(this); - - doReturn(postMethod).when(abstractSolrQueryHTTPClient).createNewPostMethod(URL); - when(postMethod.getResponseCharSet()).thenReturn("UTF-8"); - } - - /** Check postQuery works as expected for the success case. */ - @Test - public void testPostQuery_success() throws Exception - { - when(body.toString()).thenReturn("Example body"); - when(postMethod.getStatusCode()).thenReturn(HttpServletResponse.SC_OK); - when(postMethod.getResponseBodyAsStream()).thenReturn(convertStringToInputStream("{}")); - - JSONObject response = abstractSolrQueryHTTPClient.postQuery(httpClient, URL, body); - - assertEquals("Unexpected JSON response received.", "{}", response.toString()); - } - - /** Check that the status code is usually passed through from Solr. */ - @Test - public void testPostQuery_failure() throws Exception - { - String failureMessage = "{\"error\": {\"trace\": \"ExceptionClass: Stacktrace\"}}"; - - when(body.toString()).thenReturn("Example body"); - when(postMethod.getStatusCode()).thenReturn(HttpServletResponse.SC_NOT_FOUND); - when(postMethod.getResponseBodyAsStream()).thenReturn(convertStringToInputStream(failureMessage)); - when(postMethod.getResponseBodyAsString()).thenReturn(failureMessage); - - try - { - abstractSolrQueryHTTPClient.postQuery(httpClient, URL, body); - fail("Expected a QueryParserException to be thrown."); - } - catch (QueryParserException e) - { - assertEquals("Unexpected status code in exception.", e.getHttpStatusCode(), HttpServletResponse.SC_NOT_FOUND); - verify(postMethod).releaseConnection(); - } - } - - /** Check that the status code is replaced with "Not Implemented" for an unsupported query option. */ - @Test - public void testPostQuery_unsupportedOperation() throws Exception - { - String failureMessage = "{\"error\": {\"trace\": \"java.lang.UnsupportedOperationException: Stacktrace\"}}"; - - when(body.toString()).thenReturn("Example body"); - when(postMethod.getStatusCode()).thenReturn(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); - when(postMethod.getResponseBodyAsStream()).thenReturn(convertStringToInputStream(failureMessage)); - when(postMethod.getResponseBodyAsString()).thenReturn(failureMessage); - - try - { - abstractSolrQueryHTTPClient.postQuery(httpClient, URL, body); - fail("Expected a QueryParserException to be thrown."); - } - catch (QueryParserException e) - { - assertEquals("Unexpected status code in exception.", e.getHttpStatusCode(), HttpServletResponse.SC_NOT_IMPLEMENTED); - } - } - - /** Check that a redirect can be followed if the endpoint reports that it's moved. */ - @Test - public void testPostQuery_moved() throws Exception - { - when(body.toString()).thenReturn("Example body"); - // Report "moved" for the first invocation and then OK for subsequent requests. - when(postMethod.getStatusCode()).thenReturn(HttpServletResponse.SC_MOVED_PERMANENTLY).thenReturn(HttpServletResponse.SC_OK); - when(postMethod.getResponseBodyAsStream()).thenReturn(convertStringToInputStream("{}")); - when(postMethod.getResponseHeader("location")).thenReturn(header); - when(header.getValue()).thenReturn("http://new/URL"); - - JSONObject response = abstractSolrQueryHTTPClient.postQuery(httpClient, URL, body); - - verify(postMethod).setURI(new URI("http://new/URL", true)); - assertEquals("Unexpected JSON response received.", "{}", response.toString()); - } - - @Test - public void testPostQuery_handlesIOException() throws Exception - { - String messageFromHttp = "Some IO Exception"; - when(httpClient.executeMethod(any())).thenThrow(new IOException(messageFromHttp)); - - HttpClientException expectedException = - assertThrows(HttpClientException.class, () -> abstractSolrQueryHTTPClient.postQuery(httpClient, URL, body)); - - String exceptionMessage = expectedException.getMessage(); - assertTrue(exceptionMessage.endsWith("[%s] %s".formatted(abstractSolrQueryHTTPClient.getClass().getSimpleName(), messageFromHttp))); - } - - /** Create an input stream containing the given string. */ - private ByteArrayInputStream convertStringToInputStream(String message) - { - return new ByteArrayInputStream(message.getBytes()); - } -} +/* + * #%L + * Alfresco Repository + * %% + * Copyright (C) 2005 - 2024 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.repo.search.impl.solr; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThrows; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import static org.mockito.MockitoAnnotations.openMocks; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import jakarta.servlet.http.HttpServletResponse; + +import org.apache.commons.httpclient.Header; +import org.apache.commons.httpclient.HttpClient; +import org.apache.commons.httpclient.URI; +import org.apache.commons.httpclient.methods.PostMethod; +import org.json.JSONObject; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; + +import org.alfresco.httpclient.HttpClientException; +import org.alfresco.repo.search.QueryParserException; + +/** Tests for the {@link AbstractSolrQueryHTTPClient}. */ +public class AbstractSolrQueryHTTPClientTest +{ + /** A URL for use in the tests. */ + private static final String URL = "http://this/is/a/url"; + + /** The abstract class under test. */ + private final AbstractSolrQueryHTTPClient abstractSolrQueryHTTPClient = spy(AbstractSolrQueryHTTPClient.class); + @Mock + private HttpClient httpClient; + @Mock + private JSONObject body; + @Mock + private PostMethod postMethod; + @Mock + private Header header; + + @Before + public void setUp() throws Exception + { + openMocks(this); + + doReturn(postMethod).when(abstractSolrQueryHTTPClient).createNewPostMethod(URL); + when(postMethod.getResponseCharSet()).thenReturn("UTF-8"); + } + + /** Check postQuery works as expected for the success case. */ + @Test + public void testPostQuery_success() throws Exception + { + when(body.toString()).thenReturn("Example body"); + when(postMethod.getStatusCode()).thenReturn(HttpServletResponse.SC_OK); + when(postMethod.getResponseBodyAsStream()).thenReturn(convertStringToInputStream("{}")); + + JSONObject response = abstractSolrQueryHTTPClient.postQuery(httpClient, URL, body); + + assertEquals("Unexpected JSON response received.", "{}", response.toString()); + } + + /** Check that the status code is usually passed through from Solr. */ + @Test + public void testPostQuery_failure() throws Exception + { + String failureMessage = "{\"error\": {\"trace\": \"ExceptionClass: Stacktrace\"}}"; + + when(body.toString()).thenReturn("Example body"); + when(postMethod.getStatusCode()).thenReturn(HttpServletResponse.SC_NOT_FOUND); + when(postMethod.getResponseBodyAsStream()).thenReturn(convertStringToInputStream(failureMessage)); + when(postMethod.getResponseBodyAsString()).thenReturn(failureMessage); + + try + { + abstractSolrQueryHTTPClient.postQuery(httpClient, URL, body); + fail("Expected a QueryParserException to be thrown."); + } + catch (QueryParserException e) + { + assertEquals("Unexpected status code in exception.", e.getHttpStatusCode(), HttpServletResponse.SC_NOT_FOUND); + verify(postMethod).releaseConnection(); + } + } + + /** Check that the status code is replaced with "Not Implemented" for an unsupported query option. */ + @Test + public void testPostQuery_unsupportedOperation() throws Exception + { + String failureMessage = "{\"error\": {\"trace\": \"java.lang.UnsupportedOperationException: Stacktrace\"}}"; + + when(body.toString()).thenReturn("Example body"); + when(postMethod.getStatusCode()).thenReturn(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); + when(postMethod.getResponseBodyAsStream()).thenReturn(convertStringToInputStream(failureMessage)); + when(postMethod.getResponseBodyAsString()).thenReturn(failureMessage); + + try + { + abstractSolrQueryHTTPClient.postQuery(httpClient, URL, body); + fail("Expected a QueryParserException to be thrown."); + } + catch (QueryParserException e) + { + assertEquals("Unexpected status code in exception.", e.getHttpStatusCode(), HttpServletResponse.SC_NOT_IMPLEMENTED); + } + } + + /** Check that a redirect can be followed if the endpoint reports that it's moved. */ + @Test + public void testPostQuery_moved() throws Exception + { + when(body.toString()).thenReturn("Example body"); + // Report "moved" for the first invocation and then OK for subsequent requests. + when(postMethod.getStatusCode()).thenReturn(HttpServletResponse.SC_MOVED_PERMANENTLY).thenReturn(HttpServletResponse.SC_OK); + when(postMethod.getResponseBodyAsStream()).thenReturn(convertStringToInputStream("{}")); + when(postMethod.getResponseHeader("location")).thenReturn(header); + when(header.getValue()).thenReturn("http://new/URL"); + + JSONObject response = abstractSolrQueryHTTPClient.postQuery(httpClient, URL, body); + + verify(postMethod).setURI(new URI("http://new/URL", true)); + assertEquals("Unexpected JSON response received.", "{}", response.toString()); + } + + @Test + public void testPostQuery_handlesIOException() throws Exception + { + String messageFromHttp = "Some IO Exception"; + when(httpClient.executeMethod(any())).thenThrow(new IOException(messageFromHttp)); + + HttpClientException expectedException = assertThrows(HttpClientException.class, () -> abstractSolrQueryHTTPClient.postQuery(httpClient, URL, body)); + + String exceptionMessage = expectedException.getMessage(); + assertTrue(exceptionMessage.endsWith("[%s] %s".formatted(abstractSolrQueryHTTPClient.getClass().getSimpleName(), messageFromHttp))); + } + + /** Create an input stream containing the given string. */ + private ByteArrayInputStream convertStringToInputStream(String message) + { + return new ByteArrayInputStream(message.getBytes()); + } +}