Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
70 commits
Select commit Hold shift + click to select a range
048ac7b
feat(api costing): this is a poc of how we could do api accounting - …
wezell Aug 1, 2025
9438b3a
Merge branch 'main' into issue-32886-request-cost
wezell Aug 1, 2025
8264def
feat(api costing): this is a poc of how we could do api accounting - …
wezell Aug 4, 2025
f5d596e
feat(api costing): this is a poc of how we could do api accounting - …
wezell Aug 4, 2025
bce8b27
Merge branch 'main' into issue-32886-request-cost
wezell Oct 7, 2025
2ce603a
feat(cost):
wezell Oct 13, 2025
acfaa80
feat(cost):
wezell Oct 13, 2025
078472e
feat(cost):
wezell Oct 13, 2025
4c171fa
feat(cost):
wezell Oct 13, 2025
20aa314
feat(cost):
wezell Oct 14, 2025
fa9391a
feat(cost):
wezell Oct 14, 2025
9b94bb6
feat(cost):
wezell Oct 14, 2025
caab4c7
Merge branch 'main' into issue-32886-request-cost
wezell Oct 14, 2025
809fcd6
Merge remote-tracking branch 'origin/main' into issue-32886-request-cost
wezell Oct 15, 2025
61118b5
feat(cost):
wezell Oct 15, 2025
049c210
Merge remote-tracking branch 'origin/main' into issue-32886-request-cost
wezell Oct 15, 2025
d817362
feat(cost):
wezell Oct 15, 2025
2c1950d
feat(cost):
wezell Oct 15, 2025
caa7d45
feat(cost):
wezell Oct 15, 2025
ef7ac77
feat(cost):
wezell Oct 16, 2025
92a7f81
feat(cost):
wezell Oct 16, 2025
90c5fd2
feat(cost):
wezell Oct 17, 2025
d8a468c
feat(cost):
wezell Oct 17, 2025
429e8ae
feat(cost):
wezell Oct 17, 2025
db14f38
feat(cost):
wezell Oct 18, 2025
2073df1
feat(cost):
wezell Oct 18, 2025
9d168d0
feat(cost):
wezell Oct 18, 2025
3580d91
Merge branch 'main' into issue-32886-request-cost
wezell Oct 18, 2025
dd9fa0b
feat(cost):
wezell Oct 20, 2025
a6b6b61
Merge branch 'issue-32886-request-cost' of github.com:dotCMS/core int…
wezell Oct 20, 2025
4558f5a
feat(cost):
wezell Oct 20, 2025
47e10a8
Enhance dot-edit-content layout with view state management and conten…
wezell Nov 3, 2025
309e9c1
feat(cost): Enhance RequestCostApi with accounting improvements and n…
wezell Nov 3, 2025
4ac13a2
feat(cost):
wezell Nov 3, 2025
b827dd5
feat(cost):
wezell Nov 3, 2025
7a58e00
Merge remote-tracking branch 'origin/main' into issue-32886-request-cost
wezell Nov 4, 2025
6275267
Merge branch 'main' into issue-32886-request-cost
wezell Nov 7, 2025
0736e31
Merge remote-tracking branch 'origin/main' into issue-32886-request-cost
wezell Nov 11, 2025
cafd10f
Merge branch 'main' into issue-32886-request-cost
wezell Nov 14, 2025
45119da
Merge branch 'main' into issue-32886-request-cost
wezell Nov 25, 2025
c023850
Update MainSuite3a.java
wezell Nov 25, 2025
0813cc2
Merge remote-tracking branch 'origin/main' into issue-32886-request-cost
wezell Nov 25, 2025
d24b8b6
feat(cost): Enhance RequestCostApi with accounting improvements and n…
wezell Nov 25, 2025
1e7e330
Merge branch 'main' into issue-32886-request-cost
wezell Dec 15, 2025
3763983
Merge remote-tracking branch 'origin/main' into issue-32886-request-cost
wezell Dec 16, 2025
5218754
feat(cost): adding LeakyTokenBucket for rate limiting.
wezell Dec 17, 2025
b4910b3
Merge branch 'issue-32886-request-cost' of github.com:dotCMS/core int…
wezell Dec 17, 2025
a01a819
feat(cost): adding LeakyTokenBucket for rate limiting. Does nothing b…
wezell Dec 17, 2025
971dba4
Merge remote-tracking branch 'origin/main' into issue-32886-request-cost
wezell Dec 17, 2025
43780f8
feat(cost): CDI LeakyTokenBucket for rate limiting. Also accounting h…
wezell Dec 22, 2025
7ab030b
feat(cost): CDI LeakyTokenBucket for rate limiting. Also accounting h…
wezell Dec 22, 2025
81b86f4
feat(cost): CDI LeakyTokenBucket for rate limiting. Also accounting h…
wezell Dec 22, 2025
9141183
feat(cost): CDI LeakyTokenBucket for rate limiting. Also accounting h…
wezell Dec 23, 2025
82ca929
feat(cost): CDI LeakyTokenBucket for rate limiting. Also accounting h…
wezell Dec 23, 2025
2d74f63
Merge remote-tracking branch 'origin/main' into issue-32886-request-cost
wezell Dec 23, 2025
231eaa9
Merge branch 'main' into issue-32886-request-cost
wezell Jan 5, 2026
fda1b27
feat(cost): CDI LeakyTokenBucket for rate limiting. Also accounting h…
wezell Jan 5, 2026
95e613f
feat(cost): fixing postman tests
wezell Jan 5, 2026
ee1fa28
feat(cost): fixing postman tests
wezell Jan 5, 2026
29d425c
feat(cost): fixing Unit tests
wezell Jan 5, 2026
7746860
Merge branch 'main' into issue-32886-request-cost
wezell Jan 5, 2026
bb7e408
feat(cost): fixing Postman tests
wezell Jan 6, 2026
28c2b5e
feat(cost): adding logging when rate limit is hit.
wezell Jan 6, 2026
b63bd3a
feat(cost): adding request totals
wezell Jan 6, 2026
8d04507
feat(cost): adding request totals
wezell Jan 6, 2026
82c5136
feat(cost): Logging
wezell Jan 6, 2026
00d9d1d
feat(cost): Logging
wezell Jan 6, 2026
a438547
feat(cost): Logging
wezell Jan 6, 2026
ab10d41
feat(cost): Adding ability to configure Rate Limit at runtime
wezell Jan 7, 2026
c1e3387
feat(cost): Adding ability to configure Rate Limit at runtime
wezell Jan 7, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,11 +1,24 @@
package com.dotcms.business.bytebuddy;

import static net.bytebuddy.matcher.ElementMatchers.declaresMethod;
import static net.bytebuddy.matcher.ElementMatchers.isAnnotatedWith;
import static net.bytebuddy.matcher.ElementMatchers.isMethod;
import static net.bytebuddy.matcher.ElementMatchers.isSynthetic;
import static net.bytebuddy.matcher.ElementMatchers.nameStartsWith;

import com.dotcms.business.CloseDB;
import com.dotcms.business.CloseDBIfOpened;
import com.dotcms.business.ExternalTransaction;
import com.dotcms.business.WrapInTransaction;
import com.dotcms.cost.RequestCost;
import com.dotcms.cost.RequestCostAdvice;
import com.dotcms.util.EnterpriseFeature;
import com.dotcms.util.LogTime;
import java.lang.annotation.Annotation;
import java.lang.instrument.Instrumentation;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import net.bytebuddy.agent.ByteBuddyAgent;
import net.bytebuddy.agent.builder.AgentBuilder;
import net.bytebuddy.asm.Advice;
Expand All @@ -17,21 +30,9 @@
import net.bytebuddy.matcher.ElementMatcher;
import net.bytebuddy.matcher.ElementMatchers;
import net.bytebuddy.utility.JavaModule;

import java.lang.annotation.Annotation;
import java.lang.instrument.Instrumentation;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import static net.bytebuddy.matcher.ElementMatchers.declaresMethod;
import static net.bytebuddy.matcher.ElementMatchers.isAnnotatedWith;
import static net.bytebuddy.matcher.ElementMatchers.isMethod;
import static net.bytebuddy.matcher.ElementMatchers.isSynthetic;
import static net.bytebuddy.matcher.ElementMatchers.nameStartsWith;

/**
* Initializes ByteBuddy to handle transactional annotations. This replaces AspectJ functionality
* and injects at runtime. This should be initialized as early as possible and before any methods
Expand All @@ -51,7 +52,8 @@ public class ByteBuddyFactory {
CloseDBIfOpened.class, CloseDBIfOpenedAdvice.class,
LogTime.class, LogTimeAdvice.class,
EnterpriseFeature.class, EnterpriseFeatureAdvice.class,
ExternalTransaction.class, ExternalTransactionAdvice.class
ExternalTransaction.class, ExternalTransactionAdvice.class,
RequestCost.class, RequestCostAdvice.class
);


Expand Down Expand Up @@ -104,7 +106,6 @@ public ClassFileLocator classFileLocator(final ClassLoader classLoader, final Ja
for (String packageElement : packageIgnore)
ignoresByName = ignoresByName.or(nameStartsWith(packageElement));


// will filter by name first and then by annotation
ElementMatcher.Junction<TypeDefinition> classMatcher = selectByName.and(hasAnnotatedMethods());

Expand All @@ -122,12 +123,32 @@ public ClassFileLocator classFileLocator(final ClassLoader classLoader, final Ja
.transform((builder, typeDescription, classLoader, module, protectionDomain) -> {
DynamicType.Builder<?> newBuilder = builder;
for (Map.Entry<Class<? extends Annotation>, Class<?>> entry : adviceMap.entrySet()) {
newBuilder = getAdvice(entry.getKey(), entry.getValue()).transform(newBuilder, typeDescription, classLoader, module, protectionDomain);
newBuilder = getAdvice(entry.getKey(), entry.getValue()).transform(newBuilder,
typeDescription, classLoader, module, protectionDomain);
}
return newBuilder;
})

.installOn(inst);

// Explicitly retransform VelocityUtil if it's already loaded
try {
Class<?> velocityUtilClass = Class.forName("com.dotcms.rendering.velocity.util.VelocityUtil", false,
Thread.currentThread().getContextClassLoader());
if (velocityUtilClass != null) {
LOGGER.info("VelocityUtil was already loaded, retransforming it now");
inst.retransformClasses(velocityUtilClass);
}
} catch (ClassNotFoundException e) {
LOGGER.debug("VelocityUtil not yet loaded, will be transformed when loaded");
} catch (Exception e) {
LOGGER.warn("Could not retransform VelocityUtil", e);
}





LOGGER.info("ByteBuddy Initialized");
} catch (Exception e) {
LOGGER.error("Error Initializing ByteBuddy", e);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
package com.dotcms.cms.login;

import static com.dotcms.util.CollectionsUtils.list;
import static com.dotmarketing.util.Constants.DONT_RESPECT_FRONT_END_ROLES;
import static com.dotmarketing.util.Constants.RESPECT_FRONT_END_ROLES;
import static com.dotmarketing.util.CookieUtil.createJsonWebTokenCookie;

import com.dotcms.api.system.event.message.MessageSeverity;
import com.dotcms.api.system.event.message.MessageType;
import com.dotcms.api.system.event.message.SystemMessageEventUtil;
Expand All @@ -10,6 +15,8 @@
import com.dotcms.auth.providers.jwt.factories.ApiTokenAPI;
import com.dotcms.business.CloseDBIfOpened;
import com.dotcms.concurrent.DotConcurrentFactory;
import com.dotcms.cost.RequestCost;
import com.dotcms.cost.RequestPrices.Price;
import com.dotcms.enterprise.LicenseUtil;
import com.dotcms.exception.ExceptionUtil;
import com.dotcms.repackage.com.google.common.annotations.VisibleForTesting;
Expand All @@ -21,12 +28,10 @@
import com.dotmarketing.business.PermissionAPI;
import com.dotmarketing.business.UserAPI;
import com.dotmarketing.business.web.UserWebAPI;
import com.dotmarketing.cms.factories.PublicEncryptionFactory;
import com.dotmarketing.cms.login.factories.LoginFactory;
import com.dotmarketing.exception.DotDataException;
import com.dotmarketing.exception.DotSecurityException;
import com.dotmarketing.util.Config;
import com.dotmarketing.util.CookieUtil;
import com.dotmarketing.util.DateUtil;
import com.dotmarketing.util.Logger;
import com.dotmarketing.util.SecurityLogger;
Expand All @@ -52,13 +57,6 @@
import com.liferay.portal.util.WebKeys;
import com.liferay.util.InstancePool;
import io.vavr.Lazy;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.Serializable;
import java.text.ParseException;
import java.text.SimpleDateFormat;
Expand All @@ -70,11 +68,12 @@
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import static com.dotcms.util.CollectionsUtils.list;
import static com.dotmarketing.util.Constants.DONT_RESPECT_FRONT_END_ROLES;
import static com.dotmarketing.util.Constants.RESPECT_FRONT_END_ROLES;
import static com.dotmarketing.util.CookieUtil.createJsonWebTokenCookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
* Login Service Factory that allows developers to inject custom login services.
Expand Down Expand Up @@ -270,6 +269,7 @@ public boolean doBackEndLogin(String userId,

@CloseDBIfOpened
@Override
@RequestCost(Price.LOGIN_USERNAME_PASS)
public boolean doActionLogin(String userId,
final String password,
final boolean rememberMe,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
import com.dotcms.contenttype.business.StoryBlockReferenceResult;
import com.dotcms.contenttype.model.type.BaseContentType;
import com.dotcms.contenttype.model.type.ContentType;
import com.dotcms.cost.RequestCost;
import com.dotcms.cost.RequestPrices.Price;
import com.dotcms.enterprise.license.LicenseManager;
import com.dotcms.exception.ExceptionUtil;
import com.dotcms.notifications.bean.NotificationLevel;
Expand Down Expand Up @@ -1810,6 +1812,9 @@ SearchHits cachedIndexSearch(final SearchRequest searchRequest) {
return optionalHits.get();
}
try {
APILocator.getRequestCostAPI()
.incrementCost(Price.ES_QUERY, ESContentFactoryImpl.class, "cachedIndexSearch",
new Object[]{searchRequest});
SearchResponse response = RestHighLevelClientProvider.getInstance().getClient().search(searchRequest, RequestOptions.DEFAULT);
SearchHits hits = response.getHits();
if(shouldQueryCache()) {
Expand Down Expand Up @@ -1860,13 +1865,19 @@ private boolean shouldQueryCache(final String exceptionMsg) {
* @param countRequest
* @return
*/
@RequestCost(Price.ES_CACHE)
Long cachedIndexCount(final CountRequest countRequest) {

final Optional<Long> optionalCount = shouldQueryCache() ? queryCache.get(countRequest) : Optional.empty();
if(optionalCount.isPresent()) {
return optionalCount.get();
}
try {

APILocator.getRequestCostAPI().incrementCost(Price.ES_COUNT, ESContentFactoryImpl.class, "cachedIndexCount",
new Object[]{countRequest});


final CountResponse response = RestHighLevelClientProvider.getInstance().getClient().count(countRequest, RequestOptions.DEFAULT);
final long count = response.getCount();
if(shouldQueryCache()) {
Expand Down Expand Up @@ -1899,8 +1910,7 @@ Long cachedIndexCount(final CountRequest countRequest) {
}
}




@Override
protected SearchHits indexSearch(final String query, final int limit, final int offset, String sortBy) {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@
import com.dotcms.contenttype.transform.contenttype.ContentTypeTransformer;
import com.dotcms.contenttype.transform.contenttype.StructureTransformer;
import com.dotcms.contenttype.transform.field.LegacyFieldTransformer;
import com.dotcms.cost.RequestCost;
import com.dotcms.cost.RequestPrices.Price;
import com.dotcms.exception.ExceptionUtil;
import com.dotcms.featureflag.FeatureFlagName;
import com.dotcms.notifications.bean.NotificationLevel;
Expand Down Expand Up @@ -224,16 +226,17 @@
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.activation.MimeType;
import javax.annotation.Nullable;
import javax.servlet.http.HttpServletRequest;
import javax.validation.constraints.NotNull;
import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang.StringUtils;
import org.elasticsearch.action.search.SearchPhaseExecutionException;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;


/**
* Implementation class for the {@link ContentletAPI} interface.
Expand Down Expand Up @@ -421,6 +424,7 @@ public Contentlet find(final String inode, final User user, final boolean respec
* @throws DotDataException
* @throws DotSecurityException
*/
@RequestCost(Price.CONTENT_FROM_CACHE)
@CloseDBIfOpened
@Override
public Contentlet find(final String inode, final User user, final boolean respectFrontendRoles, boolean ignoreBlockEditor)
Expand Down Expand Up @@ -1863,6 +1867,7 @@ private IHTMLPage loadPageByIdentifier(String ident, boolean live, User user,
return loadPageByIdentifier(ident, live, 0L, user, frontRoles);
}

@RequestCost(Price.CONTENT_GET_REFERENCES)
@CloseDBIfOpened
@Override
public List<Map<String, Object>> getContentletReferences(final Contentlet contentlet,
Expand Down Expand Up @@ -2294,6 +2299,7 @@ List<Contentlet> filterRelatedContent(Contentlet contentlet, Relationship rel,
}
}

@RequestCost(Price.CONTENT_GET_RELATED)
@Override
public List<Contentlet> getRelatedContent(final Contentlet contentlet, final Relationship rel,
final User user,
Expand Down Expand Up @@ -2335,6 +2341,7 @@ public List<Contentlet> getRelatedContent(final Contentlet contentlet, final Rel
* permissions.
* @throws DotDataException An error occurred when interacting with the data source.
*/
@RequestCost(Price.CONTENT_GET_RELATED)
private List<Contentlet> getRelatedChildren(final Contentlet contentlet, final Relationship rel,
final User user, final boolean respectFrontendRoles, final int limitParam,
final int offset)
Expand Down Expand Up @@ -2430,6 +2437,7 @@ private List<Contentlet> getRelatedChildren(final Contentlet contentlet, final R
* permissions.
* @throws DotDataException An error occurred when interacting with the data source.
*/
@RequestCost(Price.CONTENT_GET_RELATED)
private List<Contentlet> getRelatedParents(final Contentlet contentlet, final Relationship rel,
final User user, final boolean respectFrontendRoles, int limitParam, final int offset)
throws DotSecurityException, DotDataException {
Expand Down Expand Up @@ -2666,6 +2674,7 @@ private Optional<Boolean> checkAndRunDeleteAsWorkflow(final Contentlet contentle
return Optional.empty();
}

@RequestCost(Price.CONTENT_DELETE)
@Override
public boolean delete(final Contentlet contentlet, final User user,
final boolean respectFrontendRoles) throws DotDataException, DotSecurityException {
Expand Down Expand Up @@ -2874,6 +2883,7 @@ public boolean destroy(final Contentlet contentlet, final User user,
}
}

@RequestCost(Price.CONTENT_DELETE)
@WrapInTransaction
@Override
public boolean destroy(final List<Contentlet> contentlets, final User user,
Expand Down Expand Up @@ -4651,6 +4661,7 @@ public List<Contentlet> getRelatedContent(final Contentlet contentlet,
pullByParents, limit, offset, sortBy, -1, null);
}

@RequestCost(Price.CONTENT_GET_RELATED)
@CloseDBIfOpened
@Override
public List<Contentlet> getRelatedContent(final Contentlet contentlet,
Expand Down Expand Up @@ -7249,6 +7260,7 @@ public List<Contentlet> findAllVersions(Identifier identifier, boolean bringOldV
return contentlets;
}


@CloseDBIfOpened
@Override
public List<Contentlet> findAllVersions(final SearchCriteria searchCriteria) throws DotSecurityException, DotDataException {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
package com.dotcms.contenttype.business;

import static com.dotmarketing.util.Constants.DONT_RESPECT_FRONT_END_ROLES;

import com.dotcms.api.web.HttpServletRequestThreadLocal;
import com.dotcms.business.CloseDBIfOpened;
import com.dotcms.content.business.json.ContentletJsonHelper;
import com.dotcms.contenttype.model.field.BinaryField;
import com.dotcms.contenttype.model.field.Field;
import com.dotcms.contenttype.model.field.StoryBlockField;
import com.dotcms.cost.RequestCost;
import com.dotcms.cost.RequestPrices.Price;
import com.dotcms.exception.ExceptionUtil;
import com.dotcms.rendering.velocity.viewtools.content.util.ContentUtils;
import com.dotcms.util.ConversionUtils;
Expand All @@ -29,18 +33,15 @@
import com.liferay.util.StringPool;
import io.vavr.Lazy;
import io.vavr.control.Try;
import org.apache.commons.lang3.mutable.MutableBoolean;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;

import static com.dotmarketing.util.Constants.DONT_RESPECT_FRONT_END_ROLES;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.lang3.mutable.MutableBoolean;

/**
* Implementation class for the {@link StoryBlockAPI}.
Expand Down Expand Up @@ -168,6 +169,7 @@ private int getCurrentDepthValue(final HttpServletRequest request) {
: Integer.parseInt(MAX_RELATIONSHIP_DEPTH.get());
}

@RequestCost(Price.BLOCK_EDITOR_HYDRATION)
@CloseDBIfOpened
@Override
@SuppressWarnings("unchecked")
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package com.dotcms.contenttype.business;

import com.dotcms.cost.RequestCost;
import com.dotcms.cost.RequestPrices.Price;

/**
* Holds the result of updating any Contentlet references in a Story Block field so that it can be handled correctly in
* upper layers of the system.
Expand All @@ -20,7 +23,7 @@ public class StoryBlockReferenceResult {
* @param value The Contentlet object with the updated properties. If it DID NOT have to be updated, then the
* original Contentlet must be set.
*/

@RequestCost(Price.CONTENT_GET_RELATED)
public StoryBlockReferenceResult(final boolean refreshed, final Object value) {
this.refreshed = refreshed;
this.value = value;
Expand Down
Loading
Loading