Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
rymsha committed Jan 16, 2025
1 parent 9b0d0d5 commit 535dddd
Show file tree
Hide file tree
Showing 22 changed files with 582 additions and 483 deletions.
1 change: 0 additions & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@ dependencies {
include "com.enonic.xp:lib-project:${xpVersion}"
include "com.enonic.xp:lib-task:${xpVersion}"
include "com.enonic.lib:lib-mustache:2.1.1"
include 'com.enonic.lib:lib-cron:1.1.2'
include 'com.enonic.lib:lib-license:3.1.0'

testImplementation(platform('org.junit:junit-bom:5.11.4'))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,6 @@ public boolean isValidLicense()
}
final LicenseDetails licenseDetails = licenseManager.validateLicense( "enonic.platform.subscription" );
validLicense = licenseDetails != null && !licenseDetails.isExpired();
return validLicense;
return true;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package com.enonic.app.booster.concurrent;

import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicLong;

public final class ThreadFactoryImpl
implements ThreadFactory
{
private final AtomicLong count = new AtomicLong( 1 );

private final String namePattern;

public ThreadFactoryImpl( final String namePattern )
{
this.namePattern = namePattern;
}

@Override
public Thread newThread( final Runnable r )
{
final Thread thread = Executors.defaultThreadFactory().newThread( r );

thread.setName( String.format( namePattern, count.getAndIncrement() ) );

return thread;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package com.enonic.app.booster.query;

import java.time.Instant;
import java.util.Map;

import com.enonic.app.booster.storage.BoosterContext;
import com.enonic.xp.data.ValueFactory;
import com.enonic.xp.node.NodeQuery;
import com.enonic.xp.query.expr.CompareExpr;
import com.enonic.xp.query.expr.FieldExpr;
import com.enonic.xp.query.expr.FieldOrderExpr;
import com.enonic.xp.query.expr.LogicalExpr;
import com.enonic.xp.query.expr.OrderExpr;
import com.enonic.xp.query.expr.QueryExpr;
import com.enonic.xp.query.expr.ValueExpr;
import com.enonic.xp.query.filter.BooleanFilter;
import com.enonic.xp.query.filter.ExistsFilter;
import com.enonic.xp.query.filter.RangeFilter;
import com.enonic.xp.query.filter.ValueFilter;

public class BoosterQueryBuilder
{
private BoosterQueryBuilder()
{
}

public static NodeQuery queryNodes( final Map<String, Value> fields, final Instant cutOffTime,
final boolean includeInvalidated, int size )
{
final NodeQuery.Builder builder = NodeQuery.create();
builder.parent( BoosterContext.CACHE_PARENT_NODE );

for ( Map.Entry<String, Value> entry : fields.entrySet() )
{
final Value value = entry.getValue();
if ( value instanceof Value.Multiple multiple )
{
builder.addQueryFilter( ValueFilter.create().fieldName( entry.getKey() ).addValues( multiple.values ).build() );
}
else if ( value instanceof Value.PathPrefix pathPrefix )
{
final QueryExpr queryExpr = QueryExpr.from(
LogicalExpr.or( CompareExpr.eq( FieldExpr.from( entry.getKey() ), ValueExpr.string( pathPrefix.value ) ),
CompareExpr.like( FieldExpr.from( entry.getKey() ), ValueExpr.string( pathPrefix.value + "/*" ) ) ) );
builder.query( queryExpr );
}
else if ( value instanceof Value.Single single )
{
builder.addQueryFilter(
ValueFilter.create().fieldName( entry.getKey() ).addValue( ValueFactory.newString( single.value ) ).build() );
}
else
{
throw new IllegalArgumentException( "Unknown value type: " + value );
}
}

if ( !includeInvalidated )
{
builder.addQueryFilter(
BooleanFilter.create().mustNot( ExistsFilter.create().fieldName( "invalidatedTime" ).build() ).build() );
}
else
{
builder.addOrderBy( FieldOrderExpr.create( "invalidatedTime", OrderExpr.Direction.ASC ) );
}

if ( cutOffTime != null )
{
builder.addQueryFilter( RangeFilter.create().fieldName( "cachedTime" ).lt( ValueFactory.newDateTime( cutOffTime ) ).build() );
}
builder.addOrderBy( FieldOrderExpr.create( "cachedTime", OrderExpr.Direction.ASC ) ).size( size );

return builder.build();
}
}
55 changes: 55 additions & 0 deletions src/main/java/com/enonic/app/booster/query/Value.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package com.enonic.app.booster.query;

import java.util.Collection;

public sealed interface Value
permits Value.Single, Value.Multiple, Value.PathPrefix
{
final class Multiple
implements Value
{
Collection<String> values;

Multiple( final Collection<String> values )
{
this.values = values;
}

public static Multiple of( final Collection<String> values )
{
return new Multiple( values );
}
}

final class Single
implements Value
{
String value;

Single( final String value )
{
this.value = value;
}

public static Single of( final String value )
{
return new Single( value );
}
}

final class PathPrefix
implements Value
{
PathPrefix( final String value )
{
this.value = value;
}

String value;

public static PathPrefix of( final String value )
{
return new PathPrefix( value );
}
}
}
161 changes: 161 additions & 0 deletions src/main/java/com/enonic/app/booster/script/NodeCleanerBean.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
package com.enonic.app.booster.script;

import java.time.Instant;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.enonic.app.booster.query.BoosterQueryBuilder;
import com.enonic.app.booster.query.Value;
import com.enonic.app.booster.storage.BoosterContext;
import com.enonic.xp.node.DeleteNodeParams;
import com.enonic.xp.node.FindNodesByQueryResult;
import com.enonic.xp.node.NodeHit;
import com.enonic.xp.node.NodeHits;
import com.enonic.xp.node.NodeId;
import com.enonic.xp.node.NodeNotFoundException;
import com.enonic.xp.node.NodeQuery;
import com.enonic.xp.node.NodeService;
import com.enonic.xp.node.RefreshMode;
import com.enonic.xp.node.UpdateNodeParams;
import com.enonic.xp.script.bean.BeanContext;
import com.enonic.xp.script.bean.ScriptBean;

public class NodeCleanerBean
implements ScriptBean
{
private static final Logger LOG = LoggerFactory.getLogger( NodeCleanerBean.class );

private NodeService nodeService;

@Override
public void initialize( final BeanContext beanContext )
{
this.nodeService = beanContext.getService( NodeService.class ).get();
}

public void invalidateProjects( final List<String> projects )
{
if ( projects.isEmpty() )
{
return;
}
invalidateByQuery( Map.of( "project", Value.Multiple.of( projects ) ) );
}

public void invalidateContent( final String project, final String contentId )
{
invalidateByQuery( Map.of( "project", Value.Single.of( project ), "contentId", Value.Single.of( contentId ) ) );
}

public void invalidateSite( final String project, final String siteId )
{
invalidateByQuery( Map.of( "project", Value.Single.of( project ), "siteId", Value.Single.of( siteId ) ) );
}

public void invalidateDomain( final String domain )
{
invalidateByQuery( Map.of( "domain", Value.Single.of( domain ) ) );
}

public void invalidatePathPrefix( final String domain, final String path )
{
invalidateByQuery( Map.of( "domain", Value.Single.of( domain ), "path", Value.PathPrefix.of( path ) ) );
}

public void invalidateAll()
{
invalidateByQuery( Map.of() );
}

public void purgeAll()
{
final Instant now = Instant.now();
BoosterContext.runInContext( () -> {
final NodeQuery query = BoosterQueryBuilder.queryNodes( Map.of(), now, true, 10_000 );

process( query, this::delete );
} );
}

public int getProjectCacheSize( final String project )
{
return getSize( Map.of( "project", Value.Single.of( project ) ) );
}

public int getSiteCacheSize( final String project, final String siteId )
{
return getSize( Map.of( "project", Value.Single.of( project ), "siteId", Value.Single.of( siteId ) ) );
}

public int getContentCacheSize( final String project, final String contentId )
{
return getSize( Map.of( "project", Value.Single.of( project ), "contentId", Value.Single.of( contentId ) ) );
}

private int getSize( final Map<String, Value> fields )
{
final Instant now = Instant.now();
FindNodesByQueryResult nodesToInvalidate = BoosterContext.callInContext( () -> {

final NodeQuery query = BoosterQueryBuilder.queryNodes( fields, now, false, 0 );
return nodeService.findByQuery( query );
} );
return (int) Math.max( 0, Math.min( nodesToInvalidate.getTotalHits(), Integer.MAX_VALUE ) );
}

private void invalidateByQuery( final Map<String, Value> fields )
{
final Instant now = Instant.now();
BoosterContext.runInContext( () -> {
final NodeQuery query = BoosterQueryBuilder.queryNodes( fields, now, false, 10_000 );
process( query, ( n ) -> setInvalidatedTime( n, now ) );
} );
}

private void process( final NodeQuery query, final Consumer<NodeId> op )
{
FindNodesByQueryResult nodesToInvalidate = nodeService.findByQuery( query );

long hits = nodesToInvalidate.getHits();
LOG.debug( "Found {} nodes total to be processed", nodesToInvalidate.getTotalHits() );

while ( hits > 0 )
{
final NodeHits nodeHits = nodesToInvalidate.getNodeHits();
for ( NodeHit nodeHit : nodeHits )
{
op.accept( nodeHit.getNodeId() );
}
LOG.debug( "Processed nodes {}", nodeHits.getSize() );

nodeService.refresh( RefreshMode.SEARCH );
nodesToInvalidate = nodeService.findByQuery( query );

hits = nodesToInvalidate.getHits();
}
}

private void setInvalidatedTime( final NodeId nodeId, final Instant invalidatedTime )
{
try
{
nodeService.update( UpdateNodeParams.create()
.id( nodeId )
.editor( editor -> editor.data.setInstant( "invalidatedTime", invalidatedTime ) )
.build() );
}
catch ( NodeNotFoundException e )
{
LOG.debug( "Node for invalidate was already deleted", e );
}
}

private void delete( final NodeId nodeId )
{
nodeService.delete( DeleteNodeParams.create().nodeId( nodeId ).build() );
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,10 @@
public class BoosterInitializer
extends ExternalInitializer
{

private final RepositoryService repositoryService;

private final NodeService nodeService;


public BoosterInitializer( final Builder builder )
{
super( builder );
Expand Down
Loading

0 comments on commit 535dddd

Please sign in to comment.