Skip to content

Commit

Permalink
#226 Cache abstraction currently handles the 2 documented usecases
Browse files Browse the repository at this point in the history
  • Loading branch information
Thorsten Marx committed Aug 22, 2024
1 parent 191f2f4 commit 5d4d4e3
Show file tree
Hide file tree
Showing 21 changed files with 437 additions and 34 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package com.github.thmarx.cms.api.cache;

/*-
* #%L
* cms-api
* %%
* Copyright (C) 2023 - 2024 Marx-Software
* %%
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this program. If not, see
* <http://www.gnu.org/licenses/gpl-3.0.html>.
* #L%
*/

import java.io.Serializable;
import java.time.Duration;
import java.util.function.Function;
import lombok.RequiredArgsConstructor;

/**
*
* @author t.marx
*/
@RequiredArgsConstructor
public class CacheManager {
private final CacheProvider cacheProvider;

public <K extends Serializable, V extends Serializable> ICache<K, V> get (String name, CacheConfig config) {
return cacheProvider.getCache(name, config);
}

public <K extends Serializable, V extends Serializable> ICache<K, V> get (String name, CacheConfig config, Function<K, V> loader) {
return cacheProvider.getCache(name, config, loader);
}

public record CacheConfig (Long maxSize, Duration lifeTime) {
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package com.github.thmarx.cms.api.cache;

/*-
* #%L
* cms-api
* %%
* Copyright (C) 2023 - 2024 Marx-Software
* %%
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this program. If not, see
* <http://www.gnu.org/licenses/gpl-3.0.html>.
* #L%
*/

import java.io.Serializable;
import java.util.function.Function;

/**
*
* @author t.marx
* @param <K>
* @param <V>
*/
public interface CacheProvider<K extends Serializable, V extends Serializable> {

ICache<K, V> getCache (String name, CacheManager.CacheConfig config);

ICache<K, V> getCache (String name, CacheManager.CacheConfig config, Function<K, V> loader);
}
44 changes: 44 additions & 0 deletions cms-api/src/main/java/com/github/thmarx/cms/api/cache/ICache.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package com.github.thmarx.cms.api.cache;

/*-
* #%L
* cms-api
* %%
* Copyright (C) 2023 - 2024 Marx-Software
* %%
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this program. If not, see
* <http://www.gnu.org/licenses/gpl-3.0.html>.
* #L%
*/

import java.io.Serializable;

/**
*
* @author t.marx
* @param <K>
* @param <V>
*/
public interface ICache<K extends Serializable, V extends Serializable> {

void put (K key, V value);

V get (K key);

boolean contains (K key);

void invalidate ();

void invalidate (K key);
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@

import com.github.thmarx.cms.api.db.cms.ReadOnlyFile;
import java.io.IOException;
import java.io.Serializable;
import java.util.Map;

/**
Expand All @@ -38,7 +39,7 @@ public interface ContentParser {

Map<String, Object> parseMeta(final ReadOnlyFile contentFile) throws IOException;

record ContentRecord(String content, String meta) {}
record ContentRecord(String content, String meta) implements Serializable {}

record Content(String content, Map<String, Object> meta) {}
record Content(String content, Map<String, Object> meta) implements Serializable {}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,12 @@
* #L%
*/

import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.thmarx.cms.api.ServerContext;
import com.github.thmarx.cms.api.cache.CacheManager;
import com.github.thmarx.cms.api.cache.ICache;
import com.github.thmarx.cms.api.db.cms.ReadOnlyFile;
import com.google.common.base.Strings;
import com.google.inject.Inject;
import java.io.IOException;
import java.time.Duration;
import java.util.Collections;
Expand All @@ -43,25 +44,27 @@
@Slf4j
public class DefaultContentParser implements com.github.thmarx.cms.api.content.ContentParser{

private final Cache<String, Content> contentCache;
private final ICache<String, Content> contentCache;

public DefaultContentParser() {
var builder = Caffeine.newBuilder()
.expireAfterWrite(Duration.ofMinutes(1));
@Inject
public DefaultContentParser(final CacheManager cacheManager) {
if (ServerContext.IS_DEV) {
builder.maximumSize(0);
contentCache = cacheManager.get("contentCache",
new CacheManager.CacheConfig(10l, Duration.ofMinutes(1)));
} else {
contentCache = cacheManager.get("contentCache",
new CacheManager.CacheConfig(0l, Duration.ofMinutes(1)));
}
contentCache = builder.build();
}

public void clearCache() {
contentCache.invalidateAll();
contentCache.invalidate();
}

@Override
public Content parse(final ReadOnlyFile contentFile) throws IOException {
final String filename = contentFile.toAbsolutePath().toString();
var cached = contentCache.getIfPresent(filename);
var cached = contentCache.get(filename);
if (cached != null) {
return cached;
}
Expand Down
4 changes: 4 additions & 0 deletions cms-core/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@
<artifactId>lombok</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
</dependency>
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package com.github.thmarx.cms.core.cache;

/*-
* #%L
* cms-core
* %%
* Copyright (C) 2023 - 2024 Marx-Software
* %%
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this program. If not, see
* <http://www.gnu.org/licenses/gpl-3.0.html>.
* #L%
*/

import com.github.benmanes.caffeine.cache.Cache;
import lombok.RequiredArgsConstructor;
import com.github.thmarx.cms.api.cache.ICache;
import java.io.Serializable;
import java.util.function.Function;

/**
*
* @author t.marx
* @param <K>
* @param <V>
*/
@RequiredArgsConstructor
public class LocalCache<K extends Serializable, V extends Serializable> implements ICache<K, V> {

private final Cache<K,V> wrappedCache;
private final Function<K, V> loader;

@Override
public void put(K key, V value) {
wrappedCache.put(key, value);
}

@Override
public V get(K key) {
if (!contains(key)) {
var value = loader.apply(key);
if (value != null) {
wrappedCache.put(key, value);
}
}
return wrappedCache.getIfPresent(key);
}

@Override
public boolean contains(K key) {
return wrappedCache.getIfPresent(key) != null;
}

@Override
public void invalidate() {
wrappedCache.invalidateAll();
}

@Override
public void invalidate(K key) {
wrappedCache.invalidate(key);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package com.github.thmarx.cms.core.cache;

/*-
* #%L
* cms-core
* %%
* Copyright (C) 2023 - 2024 Marx-Software
* %%
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this program. If not, see
* <http://www.gnu.org/licenses/gpl-3.0.html>.
* #L%
*/

import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.thmarx.cms.api.cache.CacheManager;
import com.github.thmarx.cms.api.cache.CacheProvider;
import com.github.thmarx.cms.api.cache.ICache;
import java.io.Serializable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.function.Function;

/**
*
* @author t.marx
*/
public class LocalCacheProvider<K extends Serializable, V extends Serializable> implements CacheProvider<K, V> {

private final ConcurrentMap<String, ICache<K, V>> caches = new ConcurrentHashMap<>();

private Cache<K,V> buildCache (CacheManager.CacheConfig config) {
var cache = Caffeine.newBuilder();

if (config.maxSize() != null) {
cache.maximumSize(config.maxSize());
}
if (config.lifeTime() != null) {
cache.expireAfterAccess(config.lifeTime());
cache.expireAfterWrite(config.lifeTime());
}

return cache.build();
}

@Override
public ICache<K, V> getCache(String name, CacheManager.CacheConfig config) {
if (!caches.containsKey(name)) {
caches.putIfAbsent(name, new LocalCache(
buildCache(config),
(key) -> null));
}
return caches.get(name);
}

@Override
public ICache<K, V> getCache(String name, CacheManager.CacheConfig config, Function<K, V> loader) {
if (!caches.containsKey(name)) {
caches.putIfAbsent(name, new LocalCache(
buildCache(config),
loader));
}
return caches.get(name);
}

}
Loading

0 comments on commit 5d4d4e3

Please sign in to comment.