/**
* Copyright (C) 2010-2017 Structr GmbH
*
* This file is part of Structr <http://structr.org>.
*
* Structr 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.
*
* Structr 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 Structr. If not, see <http://www.gnu.org/licenses/>.
*/
package org.structr.core.parser;
import java.util.Random;
import org.apache.commons.lang3.StringUtils;
import org.structr.common.error.FrameworkException;
import org.structr.core.GraphObject;
import org.structr.core.Services;
import org.structr.schema.action.ActionContext;
/**
*
*
*/
public class CacheExpression extends Expression {
public static final String ERROR_MESSAGE_CACHE = "Usage: ${cache(key, timeout, valueExpression)}. Example: ${cache('value', 60, GET('http://rate-limited-URL.com'))}";
private Expression keyExpression = null;
private Expression timeoutExpression = null;
private Expression valueExpression = null;
public CacheExpression() {
super("cache");
}
@Override
public void add(final Expression expression) throws FrameworkException {
// first expression is the if condition
if (this.keyExpression == null) {
this.keyExpression = expression;
} else if (this.timeoutExpression == null) {
this.timeoutExpression = expression;
} else if (this.valueExpression == null) {
this.valueExpression = expression;
} else {
throw new FrameworkException(422, "Invalid cache() expression in builtin function: too many parameters.");
}
expression.parent = this;
expression.level = this.level + 1;
}
@Override
public Object evaluate(final ActionContext ctx, final GraphObject entity) throws FrameworkException {
if (keyExpression == null) {
return "Error: cache(): key expression may not be empty.";
}
final Object keyObject = keyExpression.evaluate(ctx, entity);
if (keyObject == null) {
return "Error: cache(): key may not be empty.";
}
final String key = keyObject.toString();
if (StringUtils.isBlank(key)) {
return "Error: cache(): key may not be empty.";
}
if (timeoutExpression == null) {
return "Error: cache(): timeout expression may not be empty.";
}
final Object timeoutValue = timeoutExpression.evaluate(ctx, entity);
if (timeoutValue == null || !(timeoutValue instanceof Number)) {
return "Error: cache(): timeout must be non-empty and a number.";
}
if (valueExpression == null) {
return "Error: cache(): value expression may not be empty.";
}
final long timeout = ((Number)timeoutValue).longValue();
// get or create new cached value
final Services services = Services.getInstance();
CachedValue cachedValue = (CachedValue)services.getAttribute(key);
if (cachedValue == null) {
cachedValue = new CachedValue(timeout);
services.setAttribute(key, cachedValue);
} else {
cachedValue.setTimeoutSeconds(timeout);
}
// refresh value from value expression (this is the only place the value expression is evaluated)
if (cachedValue.isExpired()) {
cachedValue.refresh(valueExpression.evaluate(ctx, entity));
}
return cachedValue.getValue();
}
private static final class CachedValue {
private Random random = new Random(System.currentTimeMillis());
private Object value = null;
private long timeoutSeconds = 0L;
private long timeout = 0L;
public CachedValue(final long timeoutSeconds) {
setTimeoutSeconds(timeoutSeconds);
}
public final void setTimeoutSeconds(final long timeoutSeconds) {
this.timeoutSeconds = timeoutSeconds;
}
public final Object getValue() {
return value;
}
public final boolean isExpired() {
return System.currentTimeMillis() > timeout;
}
public final void refresh(final Object value) {
this.timeout = System.currentTimeMillis() + ((timeoutSeconds + random.nextInt(10)) * 1000);
this.value = value;
}
}
@Override
public Object transform(final ActionContext ctx, final GraphObject entity, final Object source) throws FrameworkException {
return source;
}
}