mirror of
https://github.com/DSpace/DSpace.git
synced 2025-10-10 03:23:13 +00:00
[CST-18963] Refactors matomo event handler to track bitstream view
This commit is contained in:
@@ -12,8 +12,10 @@ import java.io.InputStream;
|
|||||||
import java.sql.SQLException;
|
import java.sql.SQLException;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import jakarta.annotation.Nullable;
|
import jakarta.annotation.Nullable;
|
||||||
import org.apache.commons.collections4.CollectionUtils;
|
import org.apache.commons.collections4.CollectionUtils;
|
||||||
@@ -496,4 +498,14 @@ public class BitstreamServiceImpl extends DSpaceObjectServiceImpl<Bitstream> imp
|
|||||||
public Long getLastModified(Bitstream bitstream) throws IOException {
|
public Long getLastModified(Bitstream bitstream) throws IOException {
|
||||||
return bitstreamStorageService.getLastModified(bitstream);
|
return bitstreamStorageService.getLastModified(bitstream);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isInBundle(Bitstream bitstream, java.util.Collection<String> bundleNames) throws SQLException {
|
||||||
|
Set<String> bundles =
|
||||||
|
bitstream.getBundles()
|
||||||
|
.stream()
|
||||||
|
.map(Bundle::getName)
|
||||||
|
.collect(Collectors.toSet());
|
||||||
|
return bundleNames.stream().anyMatch(bundles::contains);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -235,4 +235,14 @@ public interface BitstreamService extends DSpaceObjectService<Bitstream>, DSpace
|
|||||||
*/
|
*/
|
||||||
@Nullable
|
@Nullable
|
||||||
Long getLastModified(Bitstream bitstream) throws IOException;
|
Long getLastModified(Bitstream bitstream) throws IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the given bitstream is inside one of the bundle
|
||||||
|
*
|
||||||
|
* @param bitstream bitstream to verify
|
||||||
|
* @param bundleNames names of the bundles to serch for
|
||||||
|
* @return true if is in one of the bundles, false otherwise
|
||||||
|
* @throws SQLException
|
||||||
|
*/
|
||||||
|
boolean isInBundle(Bitstream bitstream, java.util.Collection<String> bundleNames) throws SQLException;
|
||||||
}
|
}
|
||||||
|
@@ -11,6 +11,7 @@ import java.sql.SQLException;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
@@ -23,8 +24,8 @@ import org.apache.commons.lang.StringUtils;
|
|||||||
import org.apache.logging.log4j.LogManager;
|
import org.apache.logging.log4j.LogManager;
|
||||||
import org.apache.logging.log4j.Logger;
|
import org.apache.logging.log4j.Logger;
|
||||||
import org.dspace.content.Bitstream;
|
import org.dspace.content.Bitstream;
|
||||||
import org.dspace.content.Bundle;
|
|
||||||
import org.dspace.content.factory.ContentServiceFactory;
|
import org.dspace.content.factory.ContentServiceFactory;
|
||||||
|
import org.dspace.content.service.BitstreamService;
|
||||||
import org.dspace.core.Constants;
|
import org.dspace.core.Constants;
|
||||||
import org.dspace.core.Context;
|
import org.dspace.core.Context;
|
||||||
import org.dspace.google.client.GoogleAnalyticsClient;
|
import org.dspace.google.client.GoogleAnalyticsClient;
|
||||||
@@ -57,6 +58,9 @@ public class GoogleAsyncEventListener extends AbstractUsageEventListener {
|
|||||||
@Autowired
|
@Autowired
|
||||||
private ClientInfoService clientInfoService;
|
private ClientInfoService clientInfoService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private BitstreamService bitstreamService;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private List<GoogleAnalyticsClient> googleAnalyticsClients;
|
private List<GoogleAnalyticsClient> googleAnalyticsClients;
|
||||||
|
|
||||||
@@ -181,25 +185,35 @@ public class GoogleAsyncEventListener extends AbstractUsageEventListener {
|
|||||||
*/
|
*/
|
||||||
private boolean isContentBitstream(UsageEvent usageEvent) {
|
private boolean isContentBitstream(UsageEvent usageEvent) {
|
||||||
// check if event is a VIEW event and object is a Bitstream
|
// check if event is a VIEW event and object is a Bitstream
|
||||||
if (usageEvent.getAction() == UsageEvent.Action.VIEW
|
if (!isBitstreamView(usageEvent)) {
|
||||||
&& usageEvent.getObject().getType() == Constants.BITSTREAM) {
|
return false;
|
||||||
// check if bitstream belongs to a configured bundle
|
|
||||||
List<String> allowedBundles = List.of(configurationService
|
|
||||||
.getArrayProperty("google-analytics.bundles", new String[]{Constants.CONTENT_BUNDLE_NAME}));
|
|
||||||
if (allowedBundles.contains("none")) {
|
|
||||||
// GA events for bitstream views were turned off in config
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
List<String> bitstreamBundles;
|
|
||||||
try {
|
|
||||||
bitstreamBundles = ((Bitstream) usageEvent.getObject())
|
|
||||||
.getBundles().stream().map(Bundle::getName).collect(Collectors.toList());
|
|
||||||
} catch (SQLException e) {
|
|
||||||
throw new RuntimeException(e.getMessage(), e);
|
|
||||||
}
|
|
||||||
return allowedBundles.stream().anyMatch(bitstreamBundles::contains);
|
|
||||||
}
|
}
|
||||||
return false;
|
// check if bitstream belongs to a configured bundle
|
||||||
|
Set<String> allowedBundles =
|
||||||
|
Set.of(
|
||||||
|
configurationService.getArrayProperty(
|
||||||
|
"google-analytics.bundles",
|
||||||
|
new String[]{Constants.CONTENT_BUNDLE_NAME}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
if (allowedBundles.contains("none")) {
|
||||||
|
// GA events for bitstream views were turned off in config
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return isInBundle((Bitstream) usageEvent.getObject(), allowedBundles);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isInBundle(Bitstream bitstream, Set<String> allowedBundles) {
|
||||||
|
try {
|
||||||
|
return this.bitstreamService.isInBundle(bitstream, allowedBundles);
|
||||||
|
} catch (SQLException e) {
|
||||||
|
throw new RuntimeException(e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isBitstreamView(UsageEvent usageEvent) {
|
||||||
|
return usageEvent.getAction() == UsageEvent.Action.VIEW
|
||||||
|
&& usageEvent.getObject().getType() == Constants.BITSTREAM;
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isGoogleAnalyticsKeyNotConfigured() {
|
private boolean isGoogleAnalyticsKeyNotConfigured() {
|
||||||
|
@@ -7,10 +7,15 @@
|
|||||||
*/
|
*/
|
||||||
package org.dspace.matomo;
|
package org.dspace.matomo;
|
||||||
|
|
||||||
|
import java.sql.SQLException;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
import org.apache.logging.log4j.LogManager;
|
import org.apache.logging.log4j.LogManager;
|
||||||
import org.apache.logging.log4j.Logger;
|
import org.apache.logging.log4j.Logger;
|
||||||
|
import org.dspace.content.Bitstream;
|
||||||
|
import org.dspace.content.service.BitstreamService;
|
||||||
|
import org.dspace.core.Constants;
|
||||||
import org.dspace.services.ConfigurationService;
|
import org.dspace.services.ConfigurationService;
|
||||||
import org.dspace.services.model.Event;
|
import org.dspace.services.model.Event;
|
||||||
import org.dspace.usage.AbstractUsageEventListener;
|
import org.dspace.usage.AbstractUsageEventListener;
|
||||||
@@ -28,14 +33,17 @@ public class MatomoEventListener extends AbstractUsageEventListener {
|
|||||||
private static final Logger log = LogManager.getLogger(MatomoEventListener.class);
|
private static final Logger log = LogManager.getLogger(MatomoEventListener.class);
|
||||||
|
|
||||||
private final ConfigurationService configurationService;
|
private final ConfigurationService configurationService;
|
||||||
|
private final BitstreamService bitstreamService;
|
||||||
private final List<MatomoUsageEventHandler> matomoUsageEventHandlers;
|
private final List<MatomoUsageEventHandler> matomoUsageEventHandlers;
|
||||||
|
|
||||||
public MatomoEventListener(
|
public MatomoEventListener(
|
||||||
@Autowired List<MatomoUsageEventHandler> matomoUsageEventHandlers,
|
@Autowired List<MatomoUsageEventHandler> matomoUsageEventHandlers,
|
||||||
@Autowired ConfigurationService configurationService
|
@Autowired ConfigurationService configurationService,
|
||||||
|
@Autowired BitstreamService bitstreamService
|
||||||
) {
|
) {
|
||||||
this.matomoUsageEventHandlers = matomoUsageEventHandlers;
|
this.matomoUsageEventHandlers = matomoUsageEventHandlers;
|
||||||
this.configurationService = configurationService;
|
this.configurationService = configurationService;
|
||||||
|
this.bitstreamService = bitstreamService;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -49,6 +57,10 @@ public class MatomoEventListener extends AbstractUsageEventListener {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!isContentBitstream(usageEvent)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (log.isDebugEnabled()) {
|
if (log.isDebugEnabled()) {
|
||||||
log.debug("Usage event received {}", event.getName());
|
log.debug("Usage event received {}", event.getName());
|
||||||
}
|
}
|
||||||
@@ -64,4 +76,48 @@ public class MatomoEventListener extends AbstractUsageEventListener {
|
|||||||
return this.configurationService.getBooleanProperty("matomo.enabled", false);
|
return this.configurationService.getBooleanProperty("matomo.enabled", false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verifies if the usage event is a content bitstream view event, by checking if:
|
||||||
|
* <ul>
|
||||||
|
* <li>the usage event is a view event</li>
|
||||||
|
* <li>the object of the usage event is a bitstream</li>
|
||||||
|
* <li>the bitstream belongs to one of the configured bundles (fallback: ORIGINAL bundle)</li>
|
||||||
|
* </ul>
|
||||||
|
*/
|
||||||
|
private boolean isContentBitstream(UsageEvent usageEvent) {
|
||||||
|
// check if event is a VIEW event and object is a Bitstream
|
||||||
|
if (!isBitstreamView(usageEvent)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// check if bitstream belongs to a configured bundle
|
||||||
|
Set<String> allowedBundles = getTrackedBundles();
|
||||||
|
if (allowedBundles.contains("none")) {
|
||||||
|
// events for bitstream views were turned off in config
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return isInBundle(((Bitstream) usageEvent.getObject()), allowedBundles);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Set<String> getTrackedBundles() {
|
||||||
|
return Set.of(
|
||||||
|
configurationService.getArrayProperty(
|
||||||
|
"matomo.track.bundles",
|
||||||
|
new String[] {Constants.CONTENT_BUNDLE_NAME}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected boolean isInBundle(Bitstream bitstream, Set<String> allowedBundles) {
|
||||||
|
try {
|
||||||
|
return this.bitstreamService.isInBundle(bitstream, allowedBundles);
|
||||||
|
} catch (SQLException e) {
|
||||||
|
throw new RuntimeException(e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isBitstreamView(UsageEvent usageEvent) {
|
||||||
|
return usageEvent.getAction() == UsageEvent.Action.VIEW
|
||||||
|
&& usageEvent.getObject().getType() == Constants.BITSTREAM;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -7,9 +7,15 @@
|
|||||||
*/
|
*/
|
||||||
package org.dspace.matomo;
|
package org.dspace.matomo;
|
||||||
|
|
||||||
|
import java.sql.SQLException;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
import org.dspace.AbstractUnitTest;
|
import org.dspace.AbstractUnitTest;
|
||||||
|
import org.dspace.content.Bitstream;
|
||||||
|
import org.dspace.content.Item;
|
||||||
|
import org.dspace.content.service.BitstreamService;
|
||||||
|
import org.dspace.core.Constants;
|
||||||
import org.dspace.services.ConfigurationService;
|
import org.dspace.services.ConfigurationService;
|
||||||
import org.dspace.usage.UsageEvent;
|
import org.dspace.usage.UsageEvent;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
@@ -25,13 +31,15 @@ public class MatomoEventListenerTest extends AbstractUnitTest {
|
|||||||
MatomoSyncEventHandler matomoHandler2;
|
MatomoSyncEventHandler matomoHandler2;
|
||||||
@Mock
|
@Mock
|
||||||
ConfigurationService configurationService;
|
ConfigurationService configurationService;
|
||||||
|
@Mock
|
||||||
|
BitstreamService bitstreamService;
|
||||||
|
|
||||||
MatomoEventListener matomoEventListener;
|
MatomoEventListener matomoEventListener;
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void setUp() throws Exception {
|
public void setUp() throws Exception {
|
||||||
matomoEventListener =
|
matomoEventListener =
|
||||||
new MatomoEventListener(List.of(matomoHandler1, matomoHandler2), configurationService);
|
new MatomoEventListener(List.of(matomoHandler1, matomoHandler2), configurationService, bitstreamService);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -46,17 +54,60 @@ public class MatomoEventListenerTest extends AbstractUnitTest {
|
|||||||
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testHandleEvent() {
|
public void testDontHandleGenericViewEventWithMatomoEnabled() {
|
||||||
UsageEvent event = Mockito.mock(UsageEvent.class);
|
UsageEvent event = Mockito.mock(UsageEvent.class);
|
||||||
|
Mockito.when(event.getAction()).thenReturn(UsageEvent.Action.VIEW);
|
||||||
|
Mockito.when(event.getObject()).thenReturn(Mockito.spy(Item.class));
|
||||||
|
|
||||||
Mockito.when(configurationService.getBooleanProperty("matomo.enabled", false))
|
Mockito.when(configurationService.getBooleanProperty("matomo.enabled", false))
|
||||||
.thenReturn(true);
|
.thenReturn(true);
|
||||||
|
|
||||||
matomoEventListener.receiveEvent(event);
|
matomoEventListener.receiveEvent(event);
|
||||||
|
|
||||||
|
Mockito.verifyNoInteractions(matomoHandler1);
|
||||||
|
Mockito.verifyNoInteractions(matomoHandler2);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testHandleBitstreamViewEvent() throws SQLException {
|
||||||
|
UsageEvent event = Mockito.mock(UsageEvent.class);
|
||||||
|
Mockito.when(event.getAction()).thenReturn(UsageEvent.Action.VIEW);
|
||||||
|
|
||||||
|
Bitstream bitstream = Mockito.spy(Bitstream.class);
|
||||||
|
Mockito.when(bitstreamService.isInBundle(Mockito.eq(bitstream), Mockito.eq(Set.of(Constants.CONTENT_BUNDLE_NAME))))
|
||||||
|
.thenReturn(true);
|
||||||
|
|
||||||
|
Mockito.when(event.getObject()).thenReturn(bitstream);
|
||||||
|
|
||||||
|
Mockito.when(configurationService.getBooleanProperty(Mockito.eq("matomo.enabled"), Mockito.eq(false)))
|
||||||
|
.thenReturn(true);
|
||||||
|
Mockito.when(configurationService.getArrayProperty(Mockito.eq("matomo.track.bundles"), Mockito.any()))
|
||||||
|
.thenReturn(new String[] { });
|
||||||
|
|
||||||
|
matomoEventListener.receiveEvent(event);
|
||||||
|
|
||||||
|
Mockito.verifyNoInteractions(matomoHandler1);
|
||||||
|
Mockito.verifyNoInteractions(matomoHandler2);
|
||||||
|
|
||||||
|
// none bundle, will skip processing
|
||||||
|
Mockito.when(configurationService.getArrayProperty(Mockito.eq("matomo.track.bundles"), Mockito.any()))
|
||||||
|
.thenReturn(new String[] {"none"});
|
||||||
|
|
||||||
|
matomoEventListener.receiveEvent(event);
|
||||||
|
|
||||||
|
Mockito.verifyNoMoreInteractions(matomoHandler1);
|
||||||
|
Mockito.verifyNoMoreInteractions(matomoHandler2);
|
||||||
|
|
||||||
|
// default ( original bundle only ) then proceed with the invocation
|
||||||
|
Mockito.when(configurationService.getArrayProperty(Mockito.eq("matomo.track.bundles"), Mockito.any()))
|
||||||
|
.thenReturn(new String[] { Constants.CONTENT_BUNDLE_NAME });
|
||||||
|
|
||||||
|
matomoEventListener.receiveEvent(event);
|
||||||
|
|
||||||
Mockito.verify(matomoHandler1, Mockito.times(1)).handleEvent(event);
|
Mockito.verify(matomoHandler1, Mockito.times(1)).handleEvent(event);
|
||||||
Mockito.verify(matomoHandler2, Mockito.times(1)).handleEvent(event);
|
Mockito.verify(matomoHandler2, Mockito.times(1)).handleEvent(event);
|
||||||
Mockito.verifyNoMoreInteractions(matomoHandler1, matomoHandler2);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@@ -6,6 +6,9 @@
|
|||||||
matomo.enabled = false
|
matomo.enabled = false
|
||||||
# Configured `siteid` inside the matomo dashboard
|
# Configured `siteid` inside the matomo dashboard
|
||||||
matomo.request.siteid = 1
|
matomo.request.siteid = 1
|
||||||
|
# Specifies bitstream's bundle that will be tracked ( default is ORIGINAL )
|
||||||
|
# Add 'none' to disable the tracking for bitstreams
|
||||||
|
# matomo.track.bundles = ORIGINAL
|
||||||
|
|
||||||
#---------------------------------------------------------------#
|
#---------------------------------------------------------------#
|
||||||
#----------------MATOMO CLIENTS CONFIGURATION-------------------#
|
#----------------MATOMO CLIENTS CONFIGURATION-------------------#
|
||||||
|
Reference in New Issue
Block a user