mirror of
https://github.com/Alfresco/alfresco-community-repo.git
synced 2025-10-01 14:41:46 +00:00
Merge branch 'master' of github.com:Alfresco/alfresco-community-repo into feature/MNT-24127-EndpointToCalculateFolderSize
# Conflicts: # remote-api/src/test/java/org/alfresco/AppContext04TestSuite.java
This commit is contained in:
@@ -7,7 +7,7 @@
|
||||
<parent>
|
||||
<groupId>org.alfresco</groupId>
|
||||
<artifactId>alfresco-community-repo</artifactId>
|
||||
<version>23.4.0.9-SNAPSHOT</version>
|
||||
<version>23.4.0.42-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<dependencies>
|
||||
@@ -94,7 +94,7 @@
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-lang3</artifactId>
|
||||
<version>3.14.0</version>
|
||||
<version>3.17.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>commons-codec</groupId>
|
||||
@@ -358,7 +358,7 @@
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.woodstox</groupId>
|
||||
<artifactId>woodstox-core</artifactId>
|
||||
<version>6.5.1</version>
|
||||
<version>7.0.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
@@ -397,12 +397,12 @@
|
||||
<dependency>
|
||||
<groupId>org.mybatis</groupId>
|
||||
<artifactId>mybatis</artifactId>
|
||||
<version>3.5.13</version>
|
||||
<version>3.5.16</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.mybatis</groupId>
|
||||
<artifactId>mybatis-spring</artifactId>
|
||||
<version>3.0.3</version>
|
||||
<version>3.0.4</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Activiti -->
|
||||
|
@@ -46,8 +46,6 @@ import org.alfresco.service.cmr.repository.ScriptLocation;
|
||||
import org.alfresco.service.cmr.repository.StoreRef;
|
||||
import org.alfresco.service.cmr.security.PersonService;
|
||||
import org.alfresco.util.UrlUtil;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Action to execute a JavaScript. The script has access to the default model.
|
||||
@@ -58,10 +56,8 @@ import org.slf4j.LoggerFactory;
|
||||
*/
|
||||
public class ScriptActionExecuter extends ActionExecuterAbstractBase
|
||||
{
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(ScriptActionExecuter.class);
|
||||
public static final String NAME = "script";
|
||||
public static final String PARAM_SCRIPTREF = "script-ref";
|
||||
private static final String SYSTEM_USER = "System";
|
||||
|
||||
private ServiceRegistry serviceRegistry;
|
||||
private SysAdminParams sysAdminParams;
|
||||
@@ -156,14 +152,11 @@ public class ScriptActionExecuter extends ActionExecuterAbstractBase
|
||||
{
|
||||
// get the references we need to build the default scripting data-model
|
||||
String userName = this.serviceRegistry.getAuthenticationService().getCurrentUserName();
|
||||
if (SYSTEM_USER.equals(userName))
|
||||
{
|
||||
LOGGER.info("Skipping Script Execution for the System user.");
|
||||
return;
|
||||
}
|
||||
NodeRef personRef = this.personService.getPerson(userName);
|
||||
NodeRef homeSpaceRef = (NodeRef)nodeService.getProperty(personRef, ContentModel.PROP_HOMEFOLDER);
|
||||
|
||||
|
||||
// When a script is executed as a system user, there will no person or home space for providing to the scripting framework.
|
||||
NodeRef personRef = this.personService.getPersonOrNull(userName);
|
||||
NodeRef homeSpaceRef = personRef == null ? null : (NodeRef)nodeService.getProperty(personRef, ContentModel.PROP_HOMEFOLDER);
|
||||
|
||||
// the default scripting model provides access to well known objects and searching
|
||||
// facilities - it also provides basic create/update/delete/copy/move services
|
||||
Map<String, Object> model = this.serviceRegistry.getScriptService().buildDefaultModel(
|
||||
@@ -207,6 +200,7 @@ public class ScriptActionExecuter extends ActionExecuterAbstractBase
|
||||
/**
|
||||
* @see org.alfresco.repo.action.ParameterizedItemAbstractBase#addParameterDefinitions(java.util.List)
|
||||
*/
|
||||
@Override
|
||||
protected void addParameterDefinitions(List<ParameterDefinition> paramList)
|
||||
{
|
||||
if (scriptLocation == null)
|
||||
|
@@ -1077,6 +1077,12 @@ public class CustomModelServiceImpl implements CustomModelService
|
||||
|
||||
@Override
|
||||
public NodeRef createDownloadNode(final String modelFileName, boolean withAssociatedForm)
|
||||
{
|
||||
return createDownloadNode(modelFileName, withAssociatedForm, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public NodeRef createDownloadNode(final String modelFileName, boolean withAssociatedForm, String downloadNodeName)
|
||||
{
|
||||
List<NodeRef> nodesToBeDownloaded = new ArrayList<>(2);
|
||||
|
||||
@@ -1137,7 +1143,7 @@ public class CustomModelServiceImpl implements CustomModelService
|
||||
|
||||
try
|
||||
{
|
||||
NodeRef archiveNodeRef = downloadService.createDownload(nodesToBeDownloaded.toArray(new NodeRef[nodesToBeDownloaded.size()]), false);
|
||||
NodeRef archiveNodeRef = downloadService.createDownload(nodesToBeDownloaded.toArray(new NodeRef[0]), false, downloadNodeName);
|
||||
|
||||
if (logger.isDebugEnabled())
|
||||
{
|
||||
|
@@ -28,12 +28,14 @@ package org.alfresco.repo.download;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
import org.alfresco.model.ContentModel;
|
||||
import org.alfresco.repo.download.cannedquery.DownloadEntity;
|
||||
import org.alfresco.repo.transaction.RetryingTransactionHelper;
|
||||
import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
|
||||
import org.alfresco.service.cmr.download.DownloadService;
|
||||
import org.alfresco.service.cmr.download.DownloadStatus;
|
||||
import org.alfresco.service.cmr.repository.NodeRef;
|
||||
import org.alfresco.service.cmr.repository.NodeService;
|
||||
import org.alfresco.util.ParameterCheck;
|
||||
|
||||
/**
|
||||
@@ -50,6 +52,7 @@ public class DownloadServiceImpl implements DownloadService {
|
||||
private ActionServiceHelper actionServiceHelper;
|
||||
private DownloadStorage downloadStorage;
|
||||
private RetryingTransactionHelper transactionHelper;
|
||||
private NodeService nodeService;
|
||||
|
||||
// Dependency setters
|
||||
public void setActionServiceHelper(ActionServiceHelper actionServiceHelper)
|
||||
@@ -66,9 +69,19 @@ public class DownloadServiceImpl implements DownloadService {
|
||||
{
|
||||
this.downloadStorage = downloadStorage;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
public void setNodeService(NodeService nodeService)
|
||||
{
|
||||
this.nodeService = nodeService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public NodeRef createDownload(final NodeRef[] requestedNodes, final boolean recursive) {
|
||||
return createDownload(requestedNodes, recursive, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public NodeRef createDownload(final NodeRef[] requestedNodes, final boolean recursive, String downloadNodeName) {
|
||||
ParameterCheck.mandatory("nodeRefs", requestedNodes);
|
||||
if (requestedNodes.length < 1)
|
||||
{
|
||||
@@ -91,7 +104,12 @@ public class DownloadServiceImpl implements DownloadService {
|
||||
{
|
||||
downloadStorage.addNodeToDownload(downloadNode, node);
|
||||
}
|
||||
|
||||
|
||||
if (downloadNodeName != null)
|
||||
{
|
||||
nodeService.setProperty(downloadNode, ContentModel.PROP_NAME, downloadNodeName);
|
||||
}
|
||||
|
||||
return downloadNode;
|
||||
}
|
||||
}, false, true);
|
||||
|
@@ -38,6 +38,12 @@ import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.context.ApplicationEvent;
|
||||
import org.springframework.extensions.surf.util.AbstractLifecycleBean;
|
||||
|
||||
import org.alfresco.repo.domain.node.NodeDAO;
|
||||
import org.alfresco.repo.domain.node.TransactionEntity;
|
||||
import org.alfresco.repo.event.v1.model.DataAttributes;
|
||||
@@ -81,11 +87,6 @@ import org.alfresco.service.transaction.TransactionService;
|
||||
import org.alfresco.util.PropertyCheck;
|
||||
import org.alfresco.util.TriPredicate;
|
||||
import org.alfresco.util.transaction.TransactionListenerAdapter;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.context.ApplicationEvent;
|
||||
import org.springframework.extensions.surf.util.AbstractLifecycleBean;
|
||||
|
||||
/**
|
||||
* Generates events and sends them to an event topic.
|
||||
@@ -93,8 +94,8 @@ import org.springframework.extensions.surf.util.AbstractLifecycleBean;
|
||||
* @author Jamal Kaabi-Mofrad
|
||||
*/
|
||||
public class EventGenerator extends AbstractLifecycleBean implements InitializingBean, EventSupportedPolicies,
|
||||
ChildAssociationEventSupportedPolicies,
|
||||
PeerAssociationEventSupportedPolicies
|
||||
ChildAssociationEventSupportedPolicies,
|
||||
PeerAssociationEventSupportedPolicies
|
||||
{
|
||||
private static final Log LOGGER = LogFactory.getLog(EventGenerator.class);
|
||||
|
||||
@@ -354,7 +355,7 @@ public class EventGenerator extends AbstractLifecycleBean implements Initializin
|
||||
}
|
||||
|
||||
protected ChildAssociationEventConsolidator createChildAssociationEventConsolidator(
|
||||
ChildAssociationRef childAssociationRef)
|
||||
ChildAssociationRef childAssociationRef)
|
||||
{
|
||||
return new ChildAssociationEventConsolidator(childAssociationRef, nodeResourceHelper);
|
||||
}
|
||||
@@ -417,8 +418,7 @@ public class EventGenerator extends AbstractLifecycleBean implements Initializin
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the {@link NodeEventConsolidator} for the supplied {@code nodeRef} from
|
||||
* the current transaction context.
|
||||
* @return the {@link NodeEventConsolidator} for the supplied {@code nodeRef} from the current transaction context.
|
||||
*/
|
||||
protected NodeEventConsolidator getEventConsolidator(NodeRef nodeRef)
|
||||
{
|
||||
@@ -438,7 +438,6 @@ public class EventGenerator extends AbstractLifecycleBean implements Initializin
|
||||
return eventConsolidator;
|
||||
}
|
||||
|
||||
|
||||
protected Consolidators getTxnConsolidators(Object resourceKey)
|
||||
{
|
||||
Consolidators consolidators = AlfrescoTransactionSupport.getResource(resourceKey);
|
||||
@@ -451,8 +450,7 @@ public class EventGenerator extends AbstractLifecycleBean implements Initializin
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the {@link ChildAssociationEventConsolidator} for the supplied {@code childAssociationRef} from
|
||||
* the current transaction context.
|
||||
* @return the {@link ChildAssociationEventConsolidator} for the supplied {@code childAssociationRef} from the current transaction context.
|
||||
*/
|
||||
private ChildAssociationEventConsolidator getEventConsolidator(ChildAssociationRef childAssociationRef)
|
||||
{
|
||||
@@ -473,8 +471,7 @@ public class EventGenerator extends AbstractLifecycleBean implements Initializin
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the {@link PeerAssociationEventConsolidator} for the supplied {@code peerAssociationRef} from
|
||||
* the current transaction context.
|
||||
* @return the {@link PeerAssociationEventConsolidator} for the supplied {@code peerAssociationRef} from the current transaction context.
|
||||
*/
|
||||
private PeerAssociationEventConsolidator getEventConsolidator(AssociationRef peerAssociationRef)
|
||||
{
|
||||
@@ -507,10 +504,10 @@ public class EventGenerator extends AbstractLifecycleBean implements Initializin
|
||||
protected EventInfo getEventInfo(String user)
|
||||
{
|
||||
return new EventInfo().setTimestamp(getCurrentTransactionTimestamp())
|
||||
.setId(UUID.randomUUID().toString())
|
||||
.setTxnId(AlfrescoTransactionSupport.getTransactionId())
|
||||
.setPrincipal(user)
|
||||
.setSource(URI.create("/" + descriptorService.getCurrentRepositoryDescriptor().getId()));
|
||||
.setId(UUID.randomUUID().toString())
|
||||
.setTxnId(AlfrescoTransactionSupport.getTransactionId())
|
||||
.setPrincipal(user)
|
||||
.setSource(URI.create("/" + descriptorService.getCurrentRepositoryDescriptor().getId()));
|
||||
}
|
||||
|
||||
private ZonedDateTime getCurrentTransactionTimestamp()
|
||||
@@ -523,13 +520,12 @@ public class EventGenerator extends AbstractLifecycleBean implements Initializin
|
||||
private static ChildAssociationRef childAssociationWithoutParentOf(ChildAssociationRef childAssociationRef)
|
||||
{
|
||||
return new ChildAssociationRef(
|
||||
null,
|
||||
null,
|
||||
childAssociationRef.getQName(),
|
||||
childAssociationRef.getChildRef(),
|
||||
childAssociationRef.isPrimary(),
|
||||
childAssociationRef.getNthSibling()
|
||||
);
|
||||
null,
|
||||
null,
|
||||
childAssociationRef.getQName(),
|
||||
childAssociationRef.getChildRef(),
|
||||
childAssociationRef.isPrimary(),
|
||||
childAssociationRef.getNthSibling());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -546,7 +542,10 @@ public class EventGenerator extends AbstractLifecycleBean implements Initializin
|
||||
@Override
|
||||
protected void onShutdown(ApplicationEvent applicationEvent)
|
||||
{
|
||||
//NOOP
|
||||
if (eventSender != null)
|
||||
{
|
||||
eventSender.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
protected class EventTransactionListener extends TransactionListenerAdapter
|
||||
@@ -586,8 +585,7 @@ public class EventGenerator extends AbstractLifecycleBean implements Initializin
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if a node transaction is not only active, but also committed with modifications.
|
||||
* This means that a {@link TransactionEntity} object was created.
|
||||
* @return true if a node transaction is not only active, but also committed with modifications. This means that a {@link TransactionEntity} object was created.
|
||||
*/
|
||||
protected boolean isTransactionCommitted()
|
||||
{
|
||||
@@ -601,7 +599,8 @@ public class EventGenerator extends AbstractLifecycleBean implements Initializin
|
||||
try
|
||||
{
|
||||
sendEvents();
|
||||
} catch (Exception e)
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
// Must consume the exception to protect other TransactionListeners
|
||||
LOGGER.error("Unexpected error while sending repository events", e);
|
||||
@@ -650,14 +649,19 @@ public class EventGenerator extends AbstractLifecycleBean implements Initializin
|
||||
/**
|
||||
* Handles all kinds of events and sends them within dedicated transaction.
|
||||
*
|
||||
* @param entityReference - reference to an entity (e.g. node, child association, peer association)
|
||||
* @param eventConsolidator - object encapsulating events occurred in a transaction
|
||||
* @param entityToEventEligibilityVerifier - allows to verify if entity is eligible to generate an even. If null no verification is necessary
|
||||
* @param <REF> - entity reference type (e.g. {@link NodeRef}, {@link AssociationRef}, {@link ChildAssociationRef})
|
||||
* @param <CON> - event consolidator type - extension of {@link EventConsolidator}
|
||||
* @param entityReference
|
||||
* - reference to an entity (e.g. node, child association, peer association)
|
||||
* @param eventConsolidator
|
||||
* - object encapsulating events occurred in a transaction
|
||||
* @param entityToEventEligibilityVerifier
|
||||
* - allows to verify if entity is eligible to generate an even. If null no verification is necessary
|
||||
* @param <REF>
|
||||
* - entity reference type (e.g. {@link NodeRef}, {@link AssociationRef}, {@link ChildAssociationRef})
|
||||
* @param <CON>
|
||||
* - event consolidator type - extension of {@link EventConsolidator}
|
||||
*/
|
||||
private <REF extends EntityRef, CON extends EventConsolidator<REF, ? extends Resource>> void sendEvent(
|
||||
final REF entityReference, final CON eventConsolidator, final TriPredicate<REF, CON, EventInfo> entityToEventEligibilityVerifier)
|
||||
private <REF extends EntityRef, CON extends EventConsolidator<REF, ? extends Resource>> void sendEvent(
|
||||
final REF entityReference, final CON eventConsolidator, final TriPredicate<REF, CON, EventInfo> entityToEventEligibilityVerifier)
|
||||
{
|
||||
final EventInfo eventInfo = getEventInfo(AuthenticationUtil.getFullyAuthenticatedUser());
|
||||
if (isSendingEventBeforeCommitRequired())
|
||||
@@ -676,16 +680,22 @@ public class EventGenerator extends AbstractLifecycleBean implements Initializin
|
||||
/**
|
||||
* Creates events from various kinds of entities.
|
||||
*
|
||||
* @param entityReference - reference to an entity (e.g. node, child association, peer association)
|
||||
* @param eventConsolidator - object encapsulating events occurred in a transaction
|
||||
* @param eventInfo - object holding the event information
|
||||
* @param entityToEventEligibilityVerifier - allows to verify if entity is eligible to generate an even. If null no verification is necessary
|
||||
* @param <REF> - entity reference type (e.g. {@link NodeRef}, {@link AssociationRef}, {@link ChildAssociationRef})
|
||||
* @param <CON> - event consolidator type - extension of {@link EventConsolidator}
|
||||
* @param entityReference
|
||||
* - reference to an entity (e.g. node, child association, peer association)
|
||||
* @param eventConsolidator
|
||||
* - object encapsulating events occurred in a transaction
|
||||
* @param eventInfo
|
||||
* - object holding the event information
|
||||
* @param entityToEventEligibilityVerifier
|
||||
* - allows to verify if entity is eligible to generate an even. If null no verification is necessary
|
||||
* @param <REF>
|
||||
* - entity reference type (e.g. {@link NodeRef}, {@link AssociationRef}, {@link ChildAssociationRef})
|
||||
* @param <CON>
|
||||
* - event consolidator type - extension of {@link EventConsolidator}
|
||||
*/
|
||||
private <REF extends EntityRef, CON extends EventConsolidator<REF, ? extends Resource>> Optional<RepoEvent<?>> createEvent(
|
||||
final REF entityReference, final CON eventConsolidator, final EventInfo eventInfo,
|
||||
final TriPredicate<REF, CON, EventInfo> entityToEventEligibilityVerifier)
|
||||
final REF entityReference, final CON eventConsolidator, final EventInfo eventInfo,
|
||||
final TriPredicate<REF, CON, EventInfo> entityToEventEligibilityVerifier)
|
||||
{
|
||||
if (eventConsolidator.isTemporaryEntity())
|
||||
{
|
||||
@@ -719,8 +729,8 @@ public class EventGenerator extends AbstractLifecycleBean implements Initializin
|
||||
if (LOGGER.isTraceEnabled())
|
||||
{
|
||||
LOGGER.trace("EventFilter - Excluding node: '" + nodeReference + "' of type: '"
|
||||
+ ((nodeType == null) ? "Unknown' " : nodeType.toPrefixString())
|
||||
+ "' created by: " + user);
|
||||
+ ((nodeType == null) ? "Unknown' " : nodeType.toPrefixString())
|
||||
+ "' created by: " + user);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -747,8 +757,8 @@ public class EventGenerator extends AbstractLifecycleBean implements Initializin
|
||||
if (LOGGER.isTraceEnabled())
|
||||
{
|
||||
LOGGER.trace("EventFilter - Excluding child association: '" + childAssociationReference + "' of type: '"
|
||||
+ ((childAssocType == null) ? "Unknown' " : childAssocType.toPrefixString())
|
||||
+ "' created by: " + user);
|
||||
+ ((childAssocType == null) ? "Unknown' " : childAssocType.toPrefixString())
|
||||
+ "' created by: " + user);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -757,8 +767,8 @@ public class EventGenerator extends AbstractLifecycleBean implements Initializin
|
||||
if (LOGGER.isTraceEnabled())
|
||||
{
|
||||
LOGGER.trace("EventFilter - Excluding primary child association: '" + childAssociationReference + "' of type: '"
|
||||
+ ((childAssocType == null) ? "Unknown' " : childAssocType.toPrefixString())
|
||||
+ "' created by: " + user);
|
||||
+ ((childAssocType == null) ? "Unknown' " : childAssocType.toPrefixString())
|
||||
+ "' created by: " + user);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -804,7 +814,7 @@ public class EventGenerator extends AbstractLifecycleBean implements Initializin
|
||||
{
|
||||
if (peerAssocs == null)
|
||||
{
|
||||
peerAssocs = new LinkedHashMap<>(29);
|
||||
peerAssocs = new LinkedHashMap<>(29);
|
||||
}
|
||||
return peerAssocs;
|
||||
}
|
||||
|
@@ -37,17 +37,26 @@ public interface EventSender
|
||||
{
|
||||
/**
|
||||
* Accepts a callback function creating an event and sends this event to specified destination.
|
||||
* @param eventProducer - callback function that creates an event
|
||||
*
|
||||
* @param eventProducer
|
||||
* - callback function that creates an event
|
||||
*/
|
||||
void accept(Callable<Optional<RepoEvent<?>>> eventProducer);
|
||||
|
||||
/**
|
||||
* It's called right after event sender instantiation (see {@link org.alfresco.repo.event2.EventSenderFactoryBean}).
|
||||
* It might be used to initialize the sender implementation.
|
||||
* It's called right after event sender instantiation (see {@link org.alfresco.repo.event2.EventSenderFactoryBean}). It might be used to initialize the sender implementation.
|
||||
*/
|
||||
default void initialize()
|
||||
{
|
||||
//no initialization by default
|
||||
// no initialization by default
|
||||
}
|
||||
|
||||
/**
|
||||
* It's called when the application context is closing, allowing {@link org.alfresco.repo.event2.EventGenerator} to perform cleanup operations.
|
||||
*/
|
||||
default void destroy()
|
||||
{
|
||||
// no destruction by default
|
||||
}
|
||||
|
||||
default boolean shouldParticipateInTransaction()
|
||||
|
@@ -997,7 +997,7 @@ public class LockServiceImpl implements LockService,
|
||||
}
|
||||
|
||||
// Never return a null LockState
|
||||
Assert.notNull(lockState);
|
||||
Assert.notNull(lockState, "The lockState should not be null");
|
||||
return lockState;
|
||||
}
|
||||
|
||||
|
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
* #L%
|
||||
@@ -30,10 +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.repo.search.QueryParserException;
|
||||
import org.apache.commons.httpclient.Header;
|
||||
import org.apache.commons.httpclient.HttpClient;
|
||||
import org.apache.commons.httpclient.HttpStatus;
|
||||
@@ -47,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";
|
||||
@@ -85,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<String> 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);
|
||||
@@ -98,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)
|
||||
@@ -139,8 +140,11 @@ public abstract class AbstractSolrQueryHTTPClient
|
||||
|
||||
Reader reader = new BufferedReader(new InputStreamReader(post.getResponseBodyAsStream(), post.getResponseCharSet()));
|
||||
// TODO - replace with streaming-based solution e.g. SimpleJSON ContentHandler
|
||||
JSONObject json = new JSONObject(new JSONTokener(reader));
|
||||
return json;
|
||||
return new JSONObject(new JSONTokener(reader));
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
throw new HttpClientException("[%s] %s".formatted(this.getClass().getSimpleName(), e.getMessage()), e);
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
@@ -2,7 +2,7 @@
|
||||
* #%L
|
||||
* Alfresco Repository
|
||||
* %%
|
||||
* Copyright (C) 2005 - 2016 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
|
||||
@@ -23,133 +23,127 @@
|
||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||
* #L%
|
||||
*/
|
||||
package org.alfresco.repo.security.permissions;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import org.springframework.aop.IntroductionAdvisor;
|
||||
import org.springframework.aop.framework.ProxyFactory;
|
||||
import org.springframework.aop.support.DefaultIntroductionAdvisor;
|
||||
import org.springframework.aop.support.DelegatingIntroductionInterceptor;
|
||||
|
||||
/**
|
||||
* Interface for collection-based results that describe permission filtering
|
||||
* behaviour around cut-off limits.
|
||||
*
|
||||
* @author Derek Hulley
|
||||
* @since 4.0
|
||||
*/
|
||||
public interface PermissionCheckCollection<T>
|
||||
{
|
||||
/**
|
||||
* Get the desired number of results. Permission checks can stop once the number of
|
||||
* return objects reaches this number.
|
||||
*
|
||||
* @return the number of results desired
|
||||
*/
|
||||
int getTargetResultCount();
|
||||
|
||||
/**
|
||||
* Get the maximum time for permission checks to execute before cutting the results off.
|
||||
* <br/>Zero: Ignore this value.
|
||||
*
|
||||
* @return the time allowed for permission checks before cutoff
|
||||
*/
|
||||
long getCutOffAfterTimeMs();
|
||||
|
||||
/**
|
||||
* Get the maximum number of permission checks to perform before cutting the results off
|
||||
*
|
||||
* @return the maximum number of permission checks before cutoff
|
||||
*/
|
||||
int getCutOffAfterCount();
|
||||
|
||||
/**
|
||||
* Helper 'introduction' to allow simple addition of the {@link PermissionCheckCollection} interface to
|
||||
* existing collections.
|
||||
*
|
||||
* @param <T> the type of the <code>Collection</code> in use
|
||||
*
|
||||
* @author Derek Hulley
|
||||
* @since 4.0
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
public static class PermissionCheckCollectionMixin<T> extends DelegatingIntroductionInterceptor implements PermissionCheckCollection<T>
|
||||
{
|
||||
private final int targetResultCount;
|
||||
private final long cutOffAfterTimeMs;
|
||||
private final int cutOffAfterCount;
|
||||
|
||||
private PermissionCheckCollectionMixin(int targetResultCount, long cutOffAfterTimeMs, int cutOffAfterCount)
|
||||
{
|
||||
super();
|
||||
this.targetResultCount = targetResultCount;
|
||||
this.cutOffAfterTimeMs = cutOffAfterTimeMs;
|
||||
this.cutOffAfterCount = cutOffAfterCount;
|
||||
if (cutOffAfterTimeMs <= 0)
|
||||
{
|
||||
cutOffAfterTimeMs = 0;
|
||||
}
|
||||
if (cutOffAfterCount <= 0)
|
||||
{
|
||||
cutOffAfterCount = 0;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getTargetResultCount()
|
||||
{
|
||||
return targetResultCount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getCutOffAfterTimeMs()
|
||||
{
|
||||
return cutOffAfterTimeMs;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCutOffAfterCount()
|
||||
{
|
||||
return cutOffAfterCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to create a {@link PermissionCheckCollection} from an existing <code>Collection</code>
|
||||
*
|
||||
* @param <TT> the type of the <code>Collection</code>
|
||||
* @param collection the <code>Collection</code> to proxy
|
||||
* @param targetResultCount the desired number of results or default to the collection size
|
||||
* @param cutOffAfterTimeMs the number of milliseconds to wait before cut-off or zero to use the system default
|
||||
* time-based cut-off.
|
||||
* @param cutOffAfterCount the number of permission checks to process before cut-off or zero to use the system default
|
||||
* count-based cut-off.
|
||||
* @return a <code>Collection</code> of the same type but including the
|
||||
* {@link PermissionCheckCollection} interface
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static final <TT> Collection<TT> create(
|
||||
Collection<TT> collection,
|
||||
int targetResultCount, long cutOffAfterTimeMs, int cutOffAfterCount)
|
||||
{
|
||||
if (targetResultCount <= 0)
|
||||
{
|
||||
targetResultCount = collection.size();
|
||||
}
|
||||
// Create the mixin
|
||||
DelegatingIntroductionInterceptor mixin = new PermissionCheckCollectionMixin<Integer>(
|
||||
targetResultCount,
|
||||
cutOffAfterTimeMs,
|
||||
cutOffAfterCount);
|
||||
// Create the advisor
|
||||
IntroductionAdvisor advisor = new DefaultIntroductionAdvisor(mixin, PermissionCheckCollection.class);
|
||||
// Proxy
|
||||
ProxyFactory pf = new ProxyFactory(collection);
|
||||
pf.addAdvisor(advisor);
|
||||
Object proxiedObject = pf.getProxy();
|
||||
|
||||
// Done
|
||||
return (Collection<TT>) proxiedObject;
|
||||
}
|
||||
}
|
||||
}
|
||||
package org.alfresco.repo.security.permissions;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import org.springframework.aop.IntroductionAdvisor;
|
||||
import org.springframework.aop.support.DefaultIntroductionAdvisor;
|
||||
import org.springframework.aop.support.DelegatingIntroductionInterceptor;
|
||||
|
||||
/**
|
||||
* Interface for collection-based results that describe permission filtering behaviour around cut-off limits.
|
||||
*
|
||||
* @author Derek Hulley
|
||||
* @since 4.0
|
||||
*/
|
||||
public interface PermissionCheckCollection<T>
|
||||
{
|
||||
/**
|
||||
* Get the desired number of results. Permission checks can stop once the number of return objects reaches this number.
|
||||
*
|
||||
* @return the number of results desired
|
||||
*/
|
||||
int getTargetResultCount();
|
||||
|
||||
/**
|
||||
* Get the maximum time for permission checks to execute before cutting the results off. <br/>
|
||||
* Zero: Ignore this value.
|
||||
*
|
||||
* @return the time allowed for permission checks before cutoff
|
||||
*/
|
||||
long getCutOffAfterTimeMs();
|
||||
|
||||
/**
|
||||
* Get the maximum number of permission checks to perform before cutting the results off
|
||||
*
|
||||
* @return the maximum number of permission checks before cutoff
|
||||
*/
|
||||
int getCutOffAfterCount();
|
||||
|
||||
/**
|
||||
* Helper 'introduction' to allow simple addition of the {@link PermissionCheckCollection} interface to existing collections.
|
||||
*
|
||||
* @param <T>
|
||||
* the type of the <code>Collection</code> in use
|
||||
*
|
||||
* @author Derek Hulley
|
||||
* @since 4.0
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
class PermissionCheckCollectionMixin<T> extends DelegatingIntroductionInterceptor implements PermissionCheckCollection<T>
|
||||
{
|
||||
private final int targetResultCount;
|
||||
private final long cutOffAfterTimeMs;
|
||||
private final int cutOffAfterCount;
|
||||
|
||||
private PermissionCheckCollectionMixin(int targetResultCount, long cutOffAfterTimeMs, int cutOffAfterCount)
|
||||
{
|
||||
super();
|
||||
this.targetResultCount = targetResultCount;
|
||||
this.cutOffAfterTimeMs = cutOffAfterTimeMs;
|
||||
this.cutOffAfterCount = cutOffAfterCount;
|
||||
if (cutOffAfterTimeMs <= 0)
|
||||
{
|
||||
cutOffAfterTimeMs = 0;
|
||||
}
|
||||
if (cutOffAfterCount <= 0)
|
||||
{
|
||||
cutOffAfterCount = 0;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getTargetResultCount()
|
||||
{
|
||||
return targetResultCount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getCutOffAfterTimeMs()
|
||||
{
|
||||
return cutOffAfterTimeMs;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCutOffAfterCount()
|
||||
{
|
||||
return cutOffAfterCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to create a {@link PermissionCheckCollection} from an existing <code>Collection</code>
|
||||
*
|
||||
* @param <TT>
|
||||
* the type of the <code>Collection</code>
|
||||
* @param collection
|
||||
* the <code>Collection</code> to proxy
|
||||
* @param targetResultCount
|
||||
* the desired number of results or default to the collection size
|
||||
* @param cutOffAfterTimeMs
|
||||
* the number of milliseconds to wait before cut-off or zero to use the system default time-based cut-off.
|
||||
* @param cutOffAfterCount
|
||||
* the number of permission checks to process before cut-off or zero to use the system default count-based cut-off.
|
||||
* @return a <code>Collection</code> of the same type but including the {@link PermissionCheckCollection} interface
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <TT> Collection<TT> create(
|
||||
Collection<TT> collection,
|
||||
int targetResultCount, long cutOffAfterTimeMs, int cutOffAfterCount)
|
||||
{
|
||||
if (targetResultCount <= 0)
|
||||
{
|
||||
targetResultCount = collection.size();
|
||||
}
|
||||
// Create the mixin
|
||||
DelegatingIntroductionInterceptor mixin = new PermissionCheckCollectionMixin<>(
|
||||
targetResultCount,
|
||||
cutOffAfterTimeMs,
|
||||
cutOffAfterCount);
|
||||
// Create the advisor
|
||||
IntroductionAdvisor advisor = new DefaultIntroductionAdvisor(mixin, PermissionCheckCollection.class);
|
||||
// Create Proxy
|
||||
return (Collection<TT>) ProxyFactoryUtils.createProxy(collection, advisor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -2,161 +2,160 @@
|
||||
* #%L
|
||||
* Alfresco Repository
|
||||
* %%
|
||||
* Copyright (C) 2005 - 2016 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 <http://www.gnu.org/licenses/>.
|
||||
* #L%
|
||||
*/
|
||||
package org.alfresco.repo.security.permissions;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import org.springframework.aop.IntroductionAdvisor;
|
||||
import org.springframework.aop.framework.ProxyFactory;
|
||||
import org.springframework.aop.support.DefaultIntroductionAdvisor;
|
||||
import org.springframework.aop.support.DelegatingIntroductionInterceptor;
|
||||
|
||||
/**
|
||||
* Interface for collection-based results that carry extra information
|
||||
* about the state of permission cut-offs.
|
||||
*
|
||||
* @author Derek Hulley
|
||||
* @since 4.0
|
||||
*/
|
||||
public interface PermissionCheckedCollection<T>
|
||||
{
|
||||
/**
|
||||
* Check if the results have been truncated by permission check limits.
|
||||
*
|
||||
* @return <tt>true</tt> - if the results (usually a collection) have been
|
||||
* cut off by permission check limits
|
||||
*/
|
||||
boolean isCutOff();
|
||||
|
||||
/**
|
||||
* Get the number of objects in the original (unfiltered) collection that did
|
||||
* <b>not</b> have any permission checks.
|
||||
*
|
||||
* @return number of entries from the original collection that were not checked
|
||||
*/
|
||||
int sizeUnchecked();
|
||||
|
||||
/**
|
||||
* Get the number of objects in the original (unfiltered) collection.
|
||||
*
|
||||
* @return number of entries in the original, pre-checked collection
|
||||
*/
|
||||
int sizeOriginal();
|
||||
|
||||
/**
|
||||
* Helper 'introduction' to allow simple addition of the {@link PermissionCheckedCollection} interface to
|
||||
* existing collections.
|
||||
*
|
||||
* @param <T> the type of the <code>Collection</code> in use
|
||||
*
|
||||
* @author Derek Hulley
|
||||
* @since 4.0
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
public static class PermissionCheckedCollectionMixin<T> extends DelegatingIntroductionInterceptor implements PermissionCheckedCollection<T>
|
||||
{
|
||||
private final boolean isCutOff;
|
||||
private final int sizeUnchecked;
|
||||
private final int sizeOriginal;
|
||||
private PermissionCheckedCollectionMixin(boolean isCutOff, int sizeUnchecked, int sizeOriginal)
|
||||
{
|
||||
super();
|
||||
this.isCutOff = isCutOff;
|
||||
this.sizeUnchecked = sizeUnchecked;
|
||||
this.sizeOriginal = sizeOriginal;
|
||||
}
|
||||
@Override
|
||||
public boolean isCutOff()
|
||||
{
|
||||
return isCutOff;
|
||||
}
|
||||
@Override
|
||||
public int sizeUnchecked()
|
||||
{
|
||||
return sizeUnchecked;
|
||||
}
|
||||
@Override
|
||||
public int sizeOriginal()
|
||||
{
|
||||
return sizeOriginal;
|
||||
}
|
||||
/**
|
||||
* Helper method to create a {@link PermissionCheckedCollection} from an existing <code>Collection</code>
|
||||
* by applying the same values as present on a potentially permission-checked source. If the
|
||||
* existing checked source is <b>NOT</b> permission-checked, then the collection will not be
|
||||
* decorated.
|
||||
*
|
||||
* @param <TT> the type of the <code>Collection</code>
|
||||
* @param collection the <code>Collection</code> to proxy
|
||||
* @param checkedSource a collection that might implement {@link PermissionCheckedCollection}
|
||||
* @return a <code>Collection</code> of the same type but including the
|
||||
* {@link PermissionCheckedCollection} interface
|
||||
*/
|
||||
public static final <TT> Collection<TT> create(
|
||||
Collection<TT> collection, Collection<?> checkedSource)
|
||||
{
|
||||
if (checkedSource instanceof PermissionCheckedCollection)
|
||||
{
|
||||
PermissionCheckedCollection<?> source = (PermissionCheckedCollection<?>) checkedSource;
|
||||
return create(collection, source.isCutOff(), source.sizeUnchecked(), source.sizeOriginal());
|
||||
}
|
||||
else
|
||||
{
|
||||
return collection;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Helper method to create a {@link PermissionCheckedCollection} from an existing <code>Collection</code>
|
||||
*
|
||||
* @param <TT> the type of the <code>Collection</code>
|
||||
* @param collection the <code>Collection</code> to proxy
|
||||
* @param isCutOff <tt>true</tt> if permission checking was cut off before completion
|
||||
* @param sizeUnchecked number of entries from the original collection that were not checked
|
||||
* @param sizeOriginal number of entries in the original, pre-checked collection
|
||||
* @return a <code>Collection</code> of the same type but including the
|
||||
* {@link PermissionCheckedCollection} interface
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static final <TT> Collection<TT> create(
|
||||
Collection<TT> collection,
|
||||
boolean isCutOff, int sizeUnchecked, int sizeOriginal)
|
||||
{
|
||||
// Create the mixin
|
||||
DelegatingIntroductionInterceptor mixin = new PermissionCheckedCollectionMixin<Integer>(
|
||||
isCutOff,
|
||||
sizeUnchecked,
|
||||
sizeOriginal
|
||||
);
|
||||
// Create the advisor
|
||||
IntroductionAdvisor advisor = new DefaultIntroductionAdvisor(mixin, PermissionCheckedCollection.class);
|
||||
// Proxy
|
||||
ProxyFactory pf = new ProxyFactory(collection);
|
||||
pf.addAdvisor(advisor);
|
||||
Object proxiedObject = pf.getProxy();
|
||||
|
||||
// Done
|
||||
return (Collection<TT>) proxiedObject;
|
||||
}
|
||||
}
|
||||
}
|
||||
package org.alfresco.repo.security.permissions;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import org.springframework.aop.IntroductionAdvisor;
|
||||
import org.springframework.aop.support.DefaultIntroductionAdvisor;
|
||||
import org.springframework.aop.support.DelegatingIntroductionInterceptor;
|
||||
|
||||
/**
|
||||
* Interface for collection-based results that carry extra information about the state of permission cut-offs.
|
||||
*
|
||||
* @author Derek Hulley
|
||||
* @since 4.0
|
||||
*/
|
||||
public interface PermissionCheckedCollection<T>
|
||||
{
|
||||
/**
|
||||
* Check if the results have been truncated by permission check limits.
|
||||
*
|
||||
* @return <tt>true</tt> - if the results (usually a collection) have been cut off by permission check limits
|
||||
*/
|
||||
boolean isCutOff();
|
||||
|
||||
/**
|
||||
* Get the number of objects in the original (unfiltered) collection that did <b>not</b> have any permission checks.
|
||||
*
|
||||
* @return number of entries from the original collection that were not checked
|
||||
*/
|
||||
int sizeUnchecked();
|
||||
|
||||
/**
|
||||
* Get the number of objects in the original (unfiltered) collection.
|
||||
*
|
||||
* @return number of entries in the original, pre-checked collection
|
||||
*/
|
||||
int sizeOriginal();
|
||||
|
||||
/**
|
||||
* Helper 'introduction' to allow simple addition of the {@link PermissionCheckedCollection} interface to existing collections.
|
||||
*
|
||||
* @param <T>
|
||||
* the type of the <code>Collection</code> in use
|
||||
*
|
||||
* @author Derek Hulley
|
||||
* @since 4.0
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
class PermissionCheckedCollectionMixin<T> extends DelegatingIntroductionInterceptor implements PermissionCheckedCollection<T>
|
||||
{
|
||||
private final boolean isCutOff;
|
||||
private final int sizeUnchecked;
|
||||
private final int sizeOriginal;
|
||||
|
||||
private PermissionCheckedCollectionMixin(boolean isCutOff, int sizeUnchecked, int sizeOriginal)
|
||||
{
|
||||
super();
|
||||
this.isCutOff = isCutOff;
|
||||
this.sizeUnchecked = sizeUnchecked;
|
||||
this.sizeOriginal = sizeOriginal;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCutOff()
|
||||
{
|
||||
return isCutOff;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int sizeUnchecked()
|
||||
{
|
||||
return sizeUnchecked;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int sizeOriginal()
|
||||
{
|
||||
return sizeOriginal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to create a {@link PermissionCheckedCollection} from an existing <code>Collection</code> by applying the same values as present on a potentially permission-checked source. If the existing checked source is <b>NOT</b> permission-checked, then the collection will not be decorated.
|
||||
*
|
||||
* @param <TT>
|
||||
* the type of the <code>Collection</code>
|
||||
* @param collection
|
||||
* the <code>Collection</code> to proxy
|
||||
* @param checkedSource
|
||||
* a collection that might implement {@link PermissionCheckedCollection}
|
||||
* @return a <code>Collection</code> of the same type but including the {@link PermissionCheckedCollection} interface
|
||||
*/
|
||||
public static <TT> Collection<TT> create(
|
||||
Collection<TT> collection, Collection<?> checkedSource)
|
||||
{
|
||||
if (checkedSource instanceof PermissionCheckedCollection)
|
||||
{
|
||||
PermissionCheckedCollection<?> source = (PermissionCheckedCollection<?>) checkedSource;
|
||||
return create(collection, source.isCutOff(), source.sizeUnchecked(), source.sizeOriginal());
|
||||
}
|
||||
else
|
||||
{
|
||||
return collection;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to create a {@link PermissionCheckedCollection} from an existing <code>Collection</code>
|
||||
*
|
||||
* @param <TT>
|
||||
* the type of the <code>Collection</code>
|
||||
* @param collection
|
||||
* the <code>Collection</code> to proxy
|
||||
* @param isCutOff
|
||||
* <tt>true</tt> if permission checking was cut off before completion
|
||||
* @param sizeUnchecked
|
||||
* number of entries from the original collection that were not checked
|
||||
* @param sizeOriginal
|
||||
* number of entries in the original, pre-checked collection
|
||||
* @return a <code>Collection</code> of the same type but including the {@link PermissionCheckedCollection} interface
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <TT> Collection<TT> create(
|
||||
Collection<TT> collection,
|
||||
boolean isCutOff, int sizeUnchecked, int sizeOriginal)
|
||||
{
|
||||
// Create the mixin
|
||||
DelegatingIntroductionInterceptor mixin = new PermissionCheckedCollectionMixin<>(
|
||||
isCutOff,
|
||||
sizeUnchecked,
|
||||
sizeOriginal);
|
||||
// Create the advisor
|
||||
IntroductionAdvisor advisor = new DefaultIntroductionAdvisor(mixin, PermissionCheckedCollection.class);
|
||||
// Create Proxy
|
||||
return (Collection<TT>) ProxyFactoryUtils.createProxy(collection, advisor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* #%L
|
||||
* Alfresco Repository
|
||||
* %%
|
||||
* Copyright (C) 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 <http://www.gnu.org/licenses/>.
|
||||
* #L%
|
||||
*/
|
||||
package org.alfresco.repo.security.permissions;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Deque;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.aop.IntroductionAdvisor;
|
||||
import org.springframework.aop.framework.ProxyFactory;
|
||||
|
||||
class ProxyFactoryUtils
|
||||
{
|
||||
private ProxyFactoryUtils()
|
||||
{}
|
||||
|
||||
/**
|
||||
* Delegate creation of {@link ProxyFactory} and proxy to have control over it in one place.
|
||||
*
|
||||
* @param collection
|
||||
* given collection for ProxyFactory.
|
||||
* @param advisor
|
||||
* given advisor for ProxyFactory.
|
||||
* @return the proxy object.
|
||||
*/
|
||||
protected static Object createProxy(Collection<?> collection, IntroductionAdvisor advisor)
|
||||
{
|
||||
ProxyFactory pf = new ProxyFactory(collection);
|
||||
pf.addAdvisor(advisor);
|
||||
if (pf.isInterfaceProxied(List.class) && pf.isInterfaceProxied(Deque.class))
|
||||
{
|
||||
pf.removeInterface(Deque.class);
|
||||
}
|
||||
return pf.getProxy();
|
||||
}
|
||||
}
|
@@ -40,7 +40,7 @@ import org.alfresco.service.cmr.repository.NodeRef;
|
||||
public interface DownloadService
|
||||
{
|
||||
/**
|
||||
* Start the creation of a downlaodable archive file containing the content
|
||||
* Start the creation of a downloadable archive file containing the content
|
||||
* from the given nodeRefs.
|
||||
*
|
||||
* Implementations are expected to do this asynchronously, with clients
|
||||
@@ -50,10 +50,27 @@ public interface DownloadService
|
||||
* extended in the future, to support additional archive types.
|
||||
*
|
||||
* @param nodeRefs NodeRefs of content to be added to the archive file
|
||||
* @param recusirsive Recurse into container nodes
|
||||
* @param recursive Recurse into container nodes
|
||||
* @return Reference to node which will eventually contain the archive file
|
||||
*/
|
||||
public NodeRef createDownload(NodeRef[] nodeRefs, boolean recusirsive);
|
||||
NodeRef createDownload(NodeRef[] nodeRefs, boolean recursive);
|
||||
|
||||
/**
|
||||
* Start the creation of a downloadable archive file containing the content
|
||||
* from the given nodeRefs.
|
||||
*
|
||||
* Implementations are expected to do this asynchronously, with clients
|
||||
* using the returned NodeRef to check on progress.
|
||||
|
||||
* Initially, only zip files will be supported, however this could be
|
||||
* extended in the future, to support additional archive types.
|
||||
*
|
||||
* @param nodeRefs NodeRefs of content to be added to the archive file
|
||||
* @param recursive Recurse into container nodes
|
||||
* @param downloadNodeName Download node name
|
||||
* @return Reference to node which will eventually contain the archive file
|
||||
*/
|
||||
NodeRef createDownload(NodeRef[] nodeRefs, boolean recursive, String downloadNodeName);
|
||||
|
||||
/**
|
||||
* Get the status of the of the download identified by downloadNode.
|
||||
|
@@ -115,6 +115,7 @@
|
||||
<property name="actionServiceHelper" ref="downloadActionServiceHelper"/>
|
||||
<property name="downloadStorage" ref="downloadStorage"/>
|
||||
<property name="transactionHelper" ref="retryingTransactionHelper"/>
|
||||
<property name="nodeService" ref="NodeService"/>
|
||||
</bean>
|
||||
|
||||
<bean id="downloadCleanerSchedulerAccessor" class="org.springframework.scheduling.quartz.SchedulerAccessorBean">
|
||||
|
@@ -349,6 +349,29 @@ public class DownloadServiceIntegrationTest
|
||||
validateEntries(entryNames, allEntries, true);
|
||||
}
|
||||
|
||||
@Test public void createDownloadWithName() throws IOException, InterruptedException
|
||||
{
|
||||
// Initiate the download
|
||||
final NodeRef downloadNode = DOWNLOAD_SERVICE.createDownload(new NodeRef[] {rootFile}, false, "test.zip");
|
||||
Assert.assertNotNull(downloadNode);
|
||||
|
||||
testNodes.addNodeRef(downloadNode);
|
||||
|
||||
// Validate that the download node has been persisted correctly.
|
||||
TRANSACTION_HELPER.doInTransaction(new RetryingTransactionCallback<Object>()
|
||||
{
|
||||
|
||||
@Override
|
||||
public Object execute() throws Throwable
|
||||
{
|
||||
Map<QName, Serializable> properties = NODE_SERVICE.getProperties(downloadNode);
|
||||
Assert.assertEquals(Boolean.FALSE, properties.get(DownloadModel.PROP_RECURSIVE));
|
||||
Assert.assertEquals("test.zip",properties.get(ContentModel.PROP_NAME));
|
||||
return null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void validateEntries(final Set<String> entryNames, final Set<String> expectedEntries, boolean onlyExpected)
|
||||
{
|
||||
Set<String> copy = new TreeSet<String>(entryNames);
|
||||
|
@@ -1,155 +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 <http://www.gnu.org/licenses/>.
|
||||
* #L%
|
||||
*/
|
||||
package org.alfresco.repo.search.impl.solr;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.fail;
|
||||
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 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 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());
|
||||
}
|
||||
|
||||
/** 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 <http://www.gnu.org/licenses/>.
|
||||
* #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());
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user