-
Notifications
You must be signed in to change notification settings - Fork 467
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Issue 29711 analytics matcher layer (#29755)
This is an implementation for the matcher layer on analytics
- Loading branch information
Showing
17 changed files
with
786 additions
and
17 deletions.
There are no files selected for viewing
105 changes: 105 additions & 0 deletions
105
dotCMS/src/main/java/com/dotcms/analytics/track/AnalyticsTrackWebInterceptor.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
package com.dotcms.analytics.track; | ||
|
||
import com.dotcms.filters.interceptor.Result; | ||
import com.dotcms.filters.interceptor.WebInterceptor; | ||
import com.dotcms.jitsu.EventLogSubmitter; | ||
import com.dotcms.util.CollectionsUtils; | ||
import com.dotcms.util.WhiteBlackList; | ||
import com.dotmarketing.util.Config; | ||
import com.dotmarketing.util.Logger; | ||
import com.liferay.util.StringPool; | ||
|
||
import javax.servlet.http.HttpServletRequest; | ||
import javax.servlet.http.HttpServletResponse; | ||
import java.io.IOException; | ||
import java.util.Map; | ||
import java.util.Optional; | ||
import java.util.concurrent.ConcurrentHashMap; | ||
import java.util.function.Predicate; | ||
|
||
/** | ||
* Web Interceptor to track analytics | ||
* @author jsanca | ||
*/ | ||
public class AnalyticsTrackWebInterceptor implements WebInterceptor { | ||
|
||
private final static Map<String, RequestMatcher> requestMatchersMap = new ConcurrentHashMap<>(); | ||
|
||
private final EventLogSubmitter submitter; | ||
|
||
/// private static final String[] DEFAULT_BLACKLISTED_PROPS = new String[]{"^/api/*"}; | ||
private static final String[] DEFAULT_BLACKLISTED_PROPS = new String[]{StringPool.BLANK}; | ||
private final WhiteBlackList whiteBlackList = new WhiteBlackList.Builder() | ||
.addWhitePatterns(Config.getStringArrayProperty("ANALYTICS_WHITELISTED_KEYS", | ||
new String[]{StringPool.BLANK})) // allows everything | ||
.addBlackPatterns(CollectionsUtils.concat(Config.getStringArrayProperty( // except this | ||
"ANALYTICS_BLACKLISTED_KEYS", new String[]{}), DEFAULT_BLACKLISTED_PROPS)).build(); | ||
|
||
public AnalyticsTrackWebInterceptor() { | ||
|
||
submitter = new EventLogSubmitter(); | ||
addRequestMatcher( | ||
new PagesAndUrlMapsRequestMatcher(), | ||
new FilesRequestMatcher(), | ||
new RulesRedirectsRequestMatcher(), | ||
new VanitiesRequestMatcher()); | ||
} | ||
|
||
/** | ||
* Add a request matchers | ||
* @param requestMatchers | ||
*/ | ||
public static void addRequestMatcher(final RequestMatcher... requestMatchers) { | ||
for (final RequestMatcher matcher : requestMatchers) { | ||
requestMatchersMap.put(matcher.getId(), matcher); | ||
} | ||
} | ||
|
||
/** | ||
* Remove a request matcher by id | ||
* @param requestMatcherId | ||
*/ | ||
public static void removeRequestMatcher(final String requestMatcherId) { | ||
|
||
requestMatchersMap.remove(requestMatcherId); | ||
} | ||
|
||
@Override | ||
public Result intercept(final HttpServletRequest request, final HttpServletResponse response) throws IOException { | ||
|
||
if (whiteBlackList.isAllowed(request.getRequestURI())) { | ||
final Optional<RequestMatcher> matcherOpt = this.anyMatcher(request, response, RequestMatcher::runBeforeRequest); | ||
if (matcherOpt.isPresent()) { | ||
|
||
Logger.debug(this, () -> "intercept, Matched: " + matcherOpt.get().getId() + " request: " + request.getRequestURI()); | ||
//fireNextStep(request, response); | ||
} | ||
} | ||
|
||
return Result.NEXT; | ||
} | ||
|
||
@Override | ||
public boolean afterIntercept(final HttpServletRequest request, final HttpServletResponse response) { | ||
|
||
if (whiteBlackList.isAllowed(request.getRequestURI())) { | ||
final Optional<RequestMatcher> matcherOpt = this.anyMatcher(request, response, RequestMatcher::runAfterRequest); | ||
if (matcherOpt.isPresent()) { | ||
|
||
Logger.debug(this, () -> "afterIntercept, Matched: " + matcherOpt.get().getId() + " request: " + request.getRequestURI()); | ||
//fireNextStep(request, response); | ||
} | ||
} | ||
|
||
return true; | ||
} | ||
|
||
private Optional<RequestMatcher> anyMatcher(final HttpServletRequest request, final HttpServletResponse response, Predicate<? super RequestMatcher> filterRequest) { | ||
|
||
return requestMatchersMap.values().stream() | ||
.filter(filterRequest) | ||
.filter(matcher -> matcher.match(request, response)) | ||
.findFirst(); | ||
} | ||
|
||
} |
48 changes: 48 additions & 0 deletions
48
dotCMS/src/main/java/com/dotcms/analytics/track/FilesRequestMatcher.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
package com.dotcms.analytics.track; | ||
|
||
import com.dotcms.visitor.filter.characteristics.Character; | ||
import com.dotcms.visitor.filter.characteristics.CharacterWebAPI; | ||
import com.dotmarketing.business.web.WebAPILocator; | ||
import com.dotmarketing.filters.CMSFilter; | ||
|
||
import javax.servlet.http.HttpServletRequest; | ||
import javax.servlet.http.HttpServletResponse; | ||
import java.util.Objects; | ||
|
||
/** | ||
* Matcher for pages or files | ||
* @author jsanca | ||
*/ | ||
public class FilesRequestMatcher implements RequestMatcher { | ||
|
||
private final CharacterWebAPI characterWebAPI; | ||
|
||
public FilesRequestMatcher() { | ||
this(WebAPILocator.getCharacterWebAPI()); | ||
} | ||
|
||
public FilesRequestMatcher(final CharacterWebAPI characterWebAPI) { | ||
this.characterWebAPI = characterWebAPI; | ||
} | ||
|
||
@Override | ||
public boolean runBeforeRequest() { | ||
return true; | ||
} | ||
|
||
@Override | ||
public boolean match(final HttpServletRequest request, final HttpServletResponse response) { | ||
|
||
final Character character = this.characterWebAPI.getOrCreateCharacter(request, response); | ||
if (Objects.nonNull(character)) { | ||
|
||
final CMSFilter.IAm iAm = (CMSFilter.IAm) character.getMap(). | ||
getOrDefault("iAm", CMSFilter.IAm.NOTHING_IN_THE_CMS); | ||
|
||
// should we have a fallback when nothing is returned??? | ||
return iAm == CMSFilter.IAm.FILE; | ||
} | ||
|
||
return false; | ||
} | ||
} |
48 changes: 48 additions & 0 deletions
48
dotCMS/src/main/java/com/dotcms/analytics/track/PagesAndUrlMapsRequestMatcher.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
package com.dotcms.analytics.track; | ||
|
||
import com.dotcms.visitor.filter.characteristics.Character; | ||
import com.dotcms.visitor.filter.characteristics.CharacterWebAPI; | ||
import com.dotmarketing.business.web.WebAPILocator; | ||
import com.dotmarketing.filters.CMSFilter; | ||
|
||
import javax.servlet.http.HttpServletRequest; | ||
import javax.servlet.http.HttpServletResponse; | ||
import java.util.Objects; | ||
|
||
/** | ||
* Matcher for pages or files | ||
* @author jsanca | ||
*/ | ||
public class PagesAndUrlMapsRequestMatcher implements RequestMatcher { | ||
|
||
private final CharacterWebAPI characterWebAPI; | ||
|
||
public PagesAndUrlMapsRequestMatcher() { | ||
this(WebAPILocator.getCharacterWebAPI()); | ||
} | ||
|
||
public PagesAndUrlMapsRequestMatcher(final CharacterWebAPI characterWebAPI) { | ||
this.characterWebAPI = characterWebAPI; | ||
} | ||
|
||
@Override | ||
public boolean runBeforeRequest() { | ||
return true; | ||
} | ||
|
||
@Override | ||
public boolean match(final HttpServletRequest request, final HttpServletResponse response) { | ||
|
||
final Character character = this.characterWebAPI.getOrCreateCharacter(request, response); | ||
if (Objects.nonNull(character)) { | ||
|
||
final CMSFilter.IAm iAm = (CMSFilter.IAm) character.getMap(). | ||
getOrDefault("iAm", CMSFilter.IAm.NOTHING_IN_THE_CMS); | ||
|
||
// should we have a fallback when nothing is returned??? | ||
return iAm == CMSFilter.IAm.PAGE; // this captures also url maps | ||
} | ||
|
||
return false; | ||
} | ||
} |
126 changes: 126 additions & 0 deletions
126
dotCMS/src/main/java/com/dotcms/analytics/track/RequestMatcher.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,126 @@ | ||
package com.dotcms.analytics.track; | ||
|
||
import com.dotmarketing.util.Config; | ||
import com.dotmarketing.util.RegEX; | ||
import io.vavr.control.Try; | ||
|
||
import javax.servlet.http.HttpServletRequest; | ||
import javax.servlet.http.HttpServletResponse; | ||
import java.io.UnsupportedEncodingException; | ||
import java.net.URLDecoder; | ||
import java.util.Objects; | ||
import java.util.Set; | ||
|
||
/** | ||
* Matcher to include the tracking for analytics of some request. | ||
* | ||
* @author jsanca | ||
*/ | ||
public interface RequestMatcher { | ||
|
||
|
||
String CHARSET = Try.of(()->Config.getStringProperty("CHARSET", "UTF-8")).getOrElse("UTF-8"); | ||
|
||
/** | ||
* Return true if match the request with the patterns and methods | ||
* @param request {@link HttpServletRequest} | ||
* @param response {@link HttpServletResponse} | ||
* @return boolean true if the request match the patterns and methods | ||
*/ | ||
default boolean match(final HttpServletRequest request, final HttpServletResponse response) { | ||
|
||
final Set<String> patterns = getMatcherPatterns(); | ||
final Set<String> methods = getAllowedMethods(); | ||
return Objects.nonNull(patterns) && !patterns.isEmpty() && | ||
Objects.nonNull(methods) && !methods.isEmpty() && | ||
isAllowedMethod (methods, request.getMethod()) && | ||
match(request, response, patterns); | ||
} | ||
|
||
/** | ||
* Match the request with the patterns | ||
* @param request {@link HttpServletRequest} | ||
* @param response {@link HttpServletResponse} | ||
* @param patterns Set of patterns | ||
* @return boolean true if any of the patterns match the request | ||
*/ | ||
default boolean match(final HttpServletRequest request, final HttpServletResponse response, final Set<String> patterns) { | ||
|
||
final String requestURI = request.getRequestURI(); | ||
return patterns.stream().anyMatch(pattern -> match(requestURI, pattern)); | ||
} | ||
|
||
/** | ||
* Means the matcher should run before request | ||
* @return boolean | ||
*/ | ||
default boolean runBeforeRequest() { | ||
return false; | ||
} | ||
|
||
/** | ||
* Means the matcher should run after request | ||
* @return boolean | ||
*/ | ||
default boolean runAfterRequest() { | ||
return false; | ||
} | ||
/** | ||
* Match the request URI with the pattern | ||
* @param requestURI String | ||
* @param pattern String | ||
* @return boolean true if the pattern match the request URI | ||
*/ | ||
default boolean match (final String requestURI, final String pattern) { | ||
|
||
String uftUri = null; | ||
|
||
try { | ||
|
||
uftUri = URLDecoder.decode(requestURI, CHARSET); | ||
} catch (UnsupportedEncodingException e) { | ||
|
||
uftUri = requestURI; | ||
} | ||
|
||
return RegEX.containsCaseInsensitive(uftUri, pattern.trim()); | ||
} // match. | ||
|
||
/** | ||
* Determinate if the method is allowed | ||
* @param methods Set of methods | ||
* @param method String current request method | ||
* @return boolean true if the method is allowed | ||
*/ | ||
default boolean isAllowedMethod(final Set<String> methods, final String method) { | ||
|
||
return methods.contains(method); | ||
} | ||
|
||
/** | ||
* Returns a set of patterns for the matcher | ||
* @return Set by default empty | ||
*/ | ||
default Set<String> getMatcherPatterns() { | ||
|
||
return Set.of(); | ||
} | ||
|
||
/** | ||
* Returns the request methods allowed for this matcher. | ||
* @return Set by default empty | ||
*/ | ||
default Set<String> getAllowedMethods() { | ||
|
||
return Set.of(); | ||
} | ||
|
||
/** | ||
* Return an id for the Matcher, by default returns the class name. | ||
* @return | ||
*/ | ||
default String getId() { | ||
|
||
return this.getClass().getName(); | ||
} | ||
} |
24 changes: 24 additions & 0 deletions
24
dotCMS/src/main/java/com/dotcms/analytics/track/RulesRedirectsRequestMatcher.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
package com.dotcms.analytics.track; | ||
|
||
import javax.servlet.http.HttpServletRequest; | ||
import javax.servlet.http.HttpServletResponse; | ||
import java.util.Objects; | ||
|
||
/** | ||
* Matcher for vanity urls and rules redirect | ||
* @author jsanca | ||
*/ | ||
public class RulesRedirectsRequestMatcher implements RequestMatcher { | ||
|
||
@Override | ||
public boolean runAfterRequest() { | ||
return true; | ||
} | ||
|
||
@Override | ||
public boolean match(final HttpServletRequest request, final HttpServletResponse response) { | ||
|
||
final String ruleRedirect = response.getHeader("X-DOT-SendRedirectRuleAction"); | ||
return Objects.nonNull(ruleRedirect) && "true".equals(ruleRedirect); | ||
} | ||
} |
27 changes: 27 additions & 0 deletions
27
dotCMS/src/main/java/com/dotcms/analytics/track/VanitiesRequestMatcher.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
package com.dotcms.analytics.track; | ||
|
||
import com.dotmarketing.filters.Constants; | ||
|
||
import javax.servlet.http.HttpServletRequest; | ||
import javax.servlet.http.HttpServletResponse; | ||
import java.util.Objects; | ||
|
||
/** | ||
* Matcher for vanity urls and rules redirect | ||
* @author jsanca | ||
*/ | ||
public class VanitiesRequestMatcher implements RequestMatcher { | ||
|
||
@Override | ||
public boolean runAfterRequest() { | ||
return true; | ||
} | ||
|
||
@Override | ||
public boolean match(final HttpServletRequest request, final HttpServletResponse response) { | ||
|
||
final Object vanityHasRun = request.getAttribute(Constants.VANITY_URL_OBJECT); | ||
|
||
return Objects.nonNull(vanityHasRun); | ||
} | ||
} |
Oops, something went wrong.