diff --git a/NOTICE.txt b/NOTICE.txt index 5e21c374141..c8cfdaa1a45 100644 --- a/NOTICE.txt +++ b/NOTICE.txt @@ -11,7 +11,7 @@ Licenses used by Enonic XP (full license texts are provided after the list of li Apache Ignite Apache License 2.0 - https://apacheignite.readme.io/v2.3/docs/license + https://apacheignite.readme.io/docs/license Apache Tika Apache License 2.0 diff --git a/gradle/java.gradle b/gradle/java.gradle index 2a6879fd152..feab6d1937d 100644 --- a/gradle/java.gradle +++ b/gradle/java.gradle @@ -20,6 +20,7 @@ configurations { ext { felixVersion = '6.0.1' jettyVersion = '9.4.21.v20190926' + igniteVersion = '2.7.6' } dependencies { diff --git a/modules/core/core-ignite/build.gradle b/modules/core/core-ignite/build.gradle index a76466c3afc..ca0c29a8b79 100644 --- a/modules/core/core-ignite/build.gradle +++ b/modules/core/core-ignite/build.gradle @@ -2,9 +2,8 @@ apply from: "$rootDir/gradle/osgi.gradle" dependencies { compile project( ':core:core-api' ) - compile 'org.apache.ignite:ignite-core:2.3.0' - compile 'org.apache.ignite:ignite-osgi:2.3.0' - compile 'javax.cache:cache-api:1.0.0' + compile "org.apache.ignite:ignite-core:${igniteVersion}" + compile "org.apache.ignite:ignite-osgi:${igniteVersion}" testCompile project( path: ':core:core-api', configuration: 'testOutput' ) } diff --git a/modules/repack/repack-javax-cache-api/build.gradle b/modules/repack/repack-javax-cache-api/build.gradle deleted file mode 100644 index 0024137ed6b..00000000000 --- a/modules/repack/repack-javax-cache-api/build.gradle +++ /dev/null @@ -1,11 +0,0 @@ -apply from: "$rootDir/gradle/osgi.gradle" - -dependencies { - compile 'javax.cache:cache-api:1.0.0' -} - -jar { - bnd( 'Bundle-Name': 'Javax Cache-API', - 'Export-Package': 'javax.cache.*;version=1.0', - 'Import-Package': '*;resolution:=optional' ) -} \ No newline at end of file diff --git a/modules/runtime/build.gradle b/modules/runtime/build.gradle index b53ff829365..5aeca10ccf3 100644 --- a/modules/runtime/build.gradle +++ b/modules/runtime/build.gradle @@ -65,7 +65,7 @@ addBundle( project( ':repack:repack-elasticsearch' ), 8 ) addBundle( project( ':repack:repack-resteasy' ), 8 ) addBundle( project( ':repack:repack-attoparser' ), 8 ) addBundle( project( ':repack:repack-okhttp' ), 8 ) -addBundle( project( ':repack:repack-javax-cache-api' ), 8 ) +addBundle( 'javax.cache:cache-api:1.1.1' , 8 ) addBundle( 'org.apache.ignite:ignite-core:2.3.0', 8 ) addBundle( 'org.apache.ignite:ignite-osgi:2.3.0', 8 ) addBundle( 'org.apache.ignite:ignite-web:2.3.0', 8 ) @@ -87,7 +87,6 @@ addBundle( project( ':core:core-image' ), 22 ) addBundle( project( ':core:core-export' ), 22 ) addBundle( project( ':core:core-mail' ), 22 ) addBundle( project( ':core:core-elasticsearch' ), 22 ) -//TODO Java10 //addBundle( project( ':core:core-ignite' ), 22 ) addBundle( project( ':core:core-content' ), 22 ) addBundle( project( ':core:core-site' ), 22 ) diff --git a/modules/web/web-session/build.gradle b/modules/web/web-session/build.gradle index 2dd696f953d..579b01bed47 100644 --- a/modules/web/web-session/build.gradle +++ b/modules/web/web-session/build.gradle @@ -3,10 +3,8 @@ apply from: "$rootDir/gradle/osgi.gradle" dependencies { compile project( ':web:web-api' ) compile "org.eclipse.jetty:jetty-server:${jettyVersion}" - compile 'org.apache.ignite:ignite-core:2.3.0' - compile 'org.apache.ignite:ignite-osgi:2.3.0' - compile 'org.apache.ignite:ignite-web:2.3.0' - compile 'javax.cache:cache-api:1.0.0' + compile "org.apache.ignite:ignite-core:${igniteVersion}" + compile "org.apache.ignite:ignite-osgi:${igniteVersion}" testCompile project( path: ':web:web-api', configuration: 'testOutput' ) } diff --git a/modules/web/web-session/src/main/java/com/enonic/xp/web/session/impl/FilterConfigImpl.java b/modules/web/web-session/src/main/java/com/enonic/xp/web/session/impl/FilterConfigImpl.java deleted file mode 100644 index 8618d92e10b..00000000000 --- a/modules/web/web-session/src/main/java/com/enonic/xp/web/session/impl/FilterConfigImpl.java +++ /dev/null @@ -1,61 +0,0 @@ -package com.enonic.xp.web.session.impl; - -import java.util.Collections; -import java.util.Enumeration; -import java.util.HashMap; -import java.util.Map; - -import javax.servlet.FilterConfig; -import javax.servlet.ServletContext; - -import static org.apache.ignite.cache.websession.WebSessionFilter.WEB_SES_MAX_RETRIES_ON_FAIL_NAME_PARAM; -import static org.apache.ignite.cache.websession.WebSessionFilter.WEB_SES_NAME_PARAM; - -final class FilterConfigImpl - implements FilterConfig -{ - private final FilterConfig delegate; - - private final Map config; - - FilterConfigImpl( final FilterConfig delegate ) - { - this.delegate = delegate; - this.config = new HashMap<>(); - } - - @Override - public String getFilterName() - { - return this.delegate.getFilterName(); - } - - @Override - public ServletContext getServletContext() - { - return this.delegate.getServletContext(); - } - - @Override - public String getInitParameter( final String name ) - { - return this.config.get( name ); - } - - @Override - public Enumeration getInitParameterNames() - { - return Collections.enumeration( this.config.keySet() ); - } - - void populate( final WebSessionConfig config ) - { - this.config.put( WEB_SES_NAME_PARAM, "enonic-xp-ignite-instance" ); - this.config.put( WEB_SES_MAX_RETRIES_ON_FAIL_NAME_PARAM, String.valueOf( config.retries() ) ); - } - - void populate( final String key, final String value ) - { - this.config.put( key, value ); - } -} diff --git a/modules/web/web-session/src/main/java/com/enonic/xp/web/session/impl/IgniteSessionDataStore.java b/modules/web/web-session/src/main/java/com/enonic/xp/web/session/impl/IgniteSessionDataStore.java index 05d00665bd7..02b28d1e99f 100644 --- a/modules/web/web-session/src/main/java/com/enonic/xp/web/session/impl/IgniteSessionDataStore.java +++ b/modules/web/web-session/src/main/java/com/enonic/xp/web/session/impl/IgniteSessionDataStore.java @@ -37,7 +37,7 @@ public final class IgniteSessionDataStore private Ignite ignite; - private IgniteCache igniteCache; + private IgniteCache igniteCache; private ConcurrentHashMap defaultCache = new ConcurrentHashMap<>(); @@ -93,15 +93,15 @@ public SessionData load( String id ) public SessionData doLoad( final String cacheKey ) { - final IgniteCache igniteCache = this.igniteCache; + final IgniteCache igniteCache = this.igniteCache; if ( igniteCache == null ) { return defaultCache.get( cacheKey ); } else { - final SessionDataWrapper sessionDataWrapper = igniteCache.get( cacheKey ); - return sessionDataWrapper == null ? null : sessionDataWrapper.getSessionData(); + final SessionData sessionDataWrapper = igniteCache.get( cacheKey ); + return sessionDataWrapper == null ? null : sessionDataWrapper; } } @@ -110,7 +110,7 @@ public boolean delete( String id ) throws Exception { final String cacheKey = getCacheKey( id ); - final IgniteCache igniteCache = this.igniteCache; + final IgniteCache igniteCache = this.igniteCache; if ( igniteCache == null ) { return defaultCache.remove( cacheKey ) != null; @@ -133,18 +133,18 @@ public void doStore( String id, SessionData data, long lastSaveTime ) throws Exception { final String cacheKey = getCacheKey( id ); - final IgniteCache igniteCache = this.igniteCache; + final IgniteCache igniteCache = this.igniteCache; if ( igniteCache == null ) { defaultCache.put( cacheKey, data ); } else { - asyncPut( cacheKey, new SessionDataWrapper( data ), webSessionConfig.write_timeout() ); + asyncPut( cacheKey, data, webSessionConfig.write_timeout() ); } } - private void asyncPut( final String cacheKey, final SessionDataWrapper data, final int timeoutMillis ) + private void asyncPut( final String cacheKey, final SessionData data, final int timeoutMillis ) throws IgniteException { final IgniteFuture future = igniteCache.putAsync( cacheKey, data ); @@ -280,4 +280,4 @@ public synchronized void removeIgnite( final Ignite ignite ) this.ignite = null; this.igniteCache = null; } -} \ No newline at end of file +} diff --git a/modules/web/web-session/src/main/java/com/enonic/xp/web/session/impl/SessionCacheConfigFactory.java b/modules/web/web-session/src/main/java/com/enonic/xp/web/session/impl/SessionCacheConfigFactory.java index 172ff2addd1..da76702e1ed 100644 --- a/modules/web/web-session/src/main/java/com/enonic/xp/web/session/impl/SessionCacheConfigFactory.java +++ b/modules/web/web-session/src/main/java/com/enonic/xp/web/session/impl/SessionCacheConfigFactory.java @@ -3,18 +3,18 @@ import org.apache.ignite.cache.CacheAtomicityMode; import org.apache.ignite.cache.CacheMode; import org.apache.ignite.cache.CacheWriteSynchronizationMode; -import org.apache.ignite.cache.eviction.lru.LruEvictionPolicy; import org.apache.ignite.configuration.CacheConfiguration; +import org.eclipse.jetty.server.session.SessionData; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -class SessionCacheConfigFactory +public class SessionCacheConfigFactory { private final static Logger LOG = LoggerFactory.getLogger( SessionCacheConfigFactory.class ); - static CacheConfiguration create( final String cacheName, final WebSessionConfig config ) + public static CacheConfiguration create( final String cacheName, final WebSessionConfig config ) { - final CacheConfiguration cacheConfig = new CacheConfiguration<>(); + final CacheConfiguration cacheConfig = new CacheConfiguration<>(); cacheConfig.setAtomicityMode( CacheAtomicityMode.ATOMIC ); cacheConfig.setWriteSynchronizationMode( getWriteSyncMode( config.write_sync_mode() ) ); @@ -27,22 +27,22 @@ static CacheConfiguration create( final String cache private static CacheWriteSynchronizationMode getWriteSyncMode( final String writeSyncMode ) { - switch ( writeSyncMode.toLowerCase() ) + String writeSyncModeLowercase = writeSyncMode.toLowerCase(); + switch ( writeSyncModeLowercase ) { case "full": return CacheWriteSynchronizationMode.FULL_SYNC; - case "primary": return CacheWriteSynchronizationMode.PRIMARY_SYNC; case "async": return CacheWriteSynchronizationMode.FULL_ASYNC; default: - LOG.warn( "Unknown write sync mode: " + writeSyncMode.toLowerCase() + ", using default: " + "FULL_SYNC" ); + LOG.warn( "Unknown write sync mode: " + writeSyncModeLowercase + ", using default: " + "full" ); return CacheWriteSynchronizationMode.FULL_SYNC; } } - private static void setCacheMode( final WebSessionConfig config, final CacheConfiguration cacheConfig ) + private static void setCacheMode( final WebSessionConfig config, final CacheConfiguration cacheConfig ) { switch ( config.cache_mode().toLowerCase() ) { @@ -61,13 +61,4 @@ private static void setCacheMode( final WebSessionConfig config, final CacheConf cacheConfig.setCacheMode( CacheMode.REPLICATED ); } } - - private static void setEvictionPolicy( final WebSessionConfig config, final CacheConfiguration cacheConfig ) - { - final LruEvictionPolicy evictPlc = new LruEvictionPolicy(); - evictPlc.setMaxSize( config.eviction_max_size() ); - - cacheConfig.setEvictionPolicy( evictPlc ); - } - } diff --git a/modules/web/web-session/src/main/java/com/enonic/xp/web/session/impl/cluster/IgniteSessionDataStore.java b/modules/web/web-session/src/main/java/com/enonic/xp/web/session/impl/cluster/IgniteSessionDataStore.java new file mode 100644 index 00000000000..a78c76e5b99 --- /dev/null +++ b/modules/web/web-session/src/main/java/com/enonic/xp/web/session/impl/cluster/IgniteSessionDataStore.java @@ -0,0 +1,207 @@ +package com.enonic.xp.web.session.impl.cluster; + +import java.util.Set; +import java.util.stream.Collectors; + +import javax.cache.Cache; + +import org.apache.ignite.Ignite; +import org.eclipse.jetty.server.session.AbstractSessionDataStore; +import org.eclipse.jetty.server.session.SessionData; +import org.eclipse.jetty.server.session.SessionDataStore; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; +import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Reference; +import org.osgi.service.component.annotations.ReferenceCardinality; + +import com.enonic.xp.web.session.impl.SessionCacheConfigFactory; +import com.enonic.xp.web.session.impl.WebSessionConfig; + +/** + * Session data stored in Ignite + */ +@Component(immediate = true, service = SessionDataStore.class, configurationPid = "com.enonic.xp.web.session") +public class IgniteSessionDataStore + extends AbstractSessionDataStore +{ + private static final String WEB_SESSION_CACHE = "com.enonic.xp.webSessionCache"; + + private static final Logger LOG = Log.getLogger( IgniteSessionDataStore.class ); + + private Cache igniteCache; + + private Ignite ignite; + + @Activate + public void activate( final WebSessionConfig config ) + { + setSavePeriodSec( config.session_save_period() ); + igniteCache = ignite.getOrCreateCache( SessionCacheConfigFactory.create( WEB_SESSION_CACHE, config ) ); + } + + @Reference(cardinality = ReferenceCardinality.OPTIONAL) + public void setIgnite( final Ignite ignite ) + { + this.ignite = ignite; + } + + @Override + public SessionData doLoad( String id ) + { + if ( LOG.isDebugEnabled() ) + { + LOG.debug( "Loading session {} from Ignite", id ); + } + if ( igniteCache == null ) + { + if ( LOG.isDebugEnabled() ) + { + LOG.debug( "Ignite cache is not set" ); + } + + return null; + } + + return igniteCache.get( getCacheKey( id ) ); + } + + @Override + public boolean delete( String id ) + { + if ( LOG.isDebugEnabled() ) + { + LOG.debug( "Deleting session {} from Ignite", id ); + } + + if ( igniteCache == null ) + { + if ( LOG.isDebugEnabled() ) + { + LOG.debug( "Ignite cache is not set" ); + } + return false; + } + + return igniteCache.remove( getCacheKey( id ) ); + } + + @Override + public void doStore( String id, SessionData data, long lastSaveTime ) + { + if ( LOG.isDebugEnabled() ) + { + LOG.debug( "Store session {} in Ignite", id ); + } + if ( igniteCache == null ) + { + if ( LOG.isDebugEnabled() ) + { + LOG.debug( "Ignite cache is not set" ); + } + return; + } + igniteCache.put( getCacheKey( id ), data ); + } + + @Override + public boolean isPassivating() + { + return true; + } + + @Override + public Set doGetExpired( Set candidates ) + { + return candidates.stream().filter( this::checkCandidate ).collect( Collectors.toSet() ); + } + + private boolean checkCandidate( final String candidate ) + { + long now = System.currentTimeMillis(); + + if ( LOG.isDebugEnabled() ) + { + LOG.debug( "Checking expiry for candidate {}", candidate ); + } + + final SessionData data; + try + { + data = load( candidate ); + } + catch ( Exception e ) + { + LOG.warn( "Session {} load threw Exception. Expire it.", candidate ); + return true; + } + + if ( data == null ) + { + if ( LOG.isDebugEnabled() ) + { + LOG.debug( "Session {} does not exist", candidate ); + } + return true; + } + else + { + if ( _context.getWorkerName().equals( data.getLastNode() ) ) + { + //we are its manager, add it to the expired set if it is expired now + if ( ( data.getExpiry() > 0 ) && data.getExpiry() <= now ) + { + data.calcAndSetExpiry(); + if ( LOG.isDebugEnabled() ) + { + LOG.debug( "Session {} managed by {} is expired", data.getId(), _context.getWorkerName() ); + } + return true; + } + } + else + { + //if we are not the session's manager, only expire it iff: + // this is our first expiryCheck and the session expired a long time ago + //or + //the session expired at least one graceperiod ago + if ( _lastExpiryCheckTime <= 0 ) + { + return ( data.getExpiry() > 0 ) && data.getExpiry() < ( now - ( 1000L * ( 3 * _gracePeriodSec ) ) ); + } + else + { + return ( data.getExpiry() > 0 ) && data.getExpiry() < ( now - ( 1000L * _gracePeriodSec ) ); + } + } + } + return false; + } + + @Override + public boolean exists( String id ) + throws Exception + { + final SessionData sd = load( id ); + if ( sd == null ) + { + return false; + } + + long expiry = sd.getExpiry(); + if ( expiry <= 0 ) + { + return true; //never expires + } + else + { + return expiry > System.currentTimeMillis(); //not expired yet + } + } + + private String getCacheKey( String id ) + { + return String.join( "_", _context.getCanonicalContextPath(), _context.getVhost(), id ); + } +} diff --git a/settings.gradle b/settings.gradle index a7a4940e1a5..fd9ac2e1417 100644 --- a/settings.gradle +++ b/settings.gradle @@ -20,7 +20,7 @@ include 'core:core-auth' include 'core:core-macro' include 'core:core-task' include 'core:core-cluster' -//include 'core:core-ignite' +include 'core:core-ignite' include 'server:server-config' include 'server:server-deploy' @@ -87,7 +87,6 @@ include 'repack:repack-attoparser' include 'repack:repack-elasticsearch' include 'repack:repack-okhttp' include 'repack:repack-resteasy' -include 'repack:repack-javax-cache-api' include 'runtime' include 'docs'