/*******************************************************************************
* Copyright 2009, 2010 Innovation Gate GmbH. All Rights Reserved.
*
* This file is part of the OpenWGA server platform.
*
* OpenWGA 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.
*
* In addition, a special exception is granted by the copyright holders
* of OpenWGA called "OpenWGA plugin exception". You should have received
* a copy of this exception along with OpenWGA in file COPYING.
* If not, see <http://www.openwga.com/gpl-plugin-exception>.
*
* OpenWGA 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 OpenWGA in file COPYING.
* If not, see <http://www.gnu.org/licenses/>.
******************************************************************************/
package de.innovationgate.wgpublisher.webtml;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import de.innovationgate.utils.cache.CacheException;
import de.innovationgate.utils.WGUtils;
import de.innovationgate.webgate.api.WGAPIException;
import de.innovationgate.webgate.api.WGDatabase;
import de.innovationgate.wgpublisher.WGACore;
import de.innovationgate.wgpublisher.expressions.ExpressionEngine;
import de.innovationgate.wgpublisher.expressions.ExpressionEngineFactory;
import de.innovationgate.wgpublisher.expressions.ExpressionResult;
import de.innovationgate.wgpublisher.webtml.utils.TMLException;
import de.innovationgate.wgpublisher.webtml.utils.TMLOption;
public class Range extends Base {
private String cachekey;
private String cachelatency;
private String exprlanguage;
private String waittimeout;
private String defaultxpl;
private String labelcontainer;
private String labelfile;
private String tmlscope;
public static class Status extends BaseTagStatus {
private String _currentCacheKey;
private String _currentCacheId;
private int cacheLatency = 0;
private boolean putCache;
private boolean servedStale = false;
private Date cacheDate;
@Override
public Object getTagInfo(String name) throws WGAPIException {
if (name.equals("cachekey")) {
return this._currentCacheKey;
}
else if (name.equals("cacheused")) {
return new Boolean(!this.putCache);
}
else if (name.equals("cachestale")) {
return new Boolean(this.servedStale);
}
return super.getTagInfo(name);
}
}
@Override
protected BaseTagStatus createTagStatus() {
return new Status();
}
private static Map currentlyEvaluatedCaches = Collections.synchronizedMap(new HashMap());
public void tmlEndTag() throws TMLException {
Status status = (Status) getStatus();
if (status.putCache == true) {
try {
int latency = 0;
getCore().getWebTMLCache().putCache(getTMLContext().db().getDbReference(), status._currentCacheId, status._currentCacheKey, this.getResultString(), status.cacheDate, status.cacheLatency);
}
catch (CacheException e) {
getTMLContext().getlog().error("Exception writing WebTML cache data for key " + getTMLContext().db().getDbReference() + "/" + status._currentCacheId + "/" + status._currentCacheKey, e);
}
}
}
/**
* @see de.innovationgate.wgpublisher.webtml.Base#tmlStartTag()
*/
public void tmlStartTag() throws TMLException {
Status status = (Status) getStatus();
String latencyStr = getCachelatency();
if (!WGUtils.isEmpty(latencyStr)) {
status.cacheLatency = stringToInteger(latencyStr, 0) * 60;
}
// Initialize
status._currentCacheKey = null;
status._currentCacheId = getId();
status.putCache = false;
status.cacheDate = null;
status.servedStale = false;
// Set default xpl
String xpl = this.getDefaultxpl();
if (xpl != null) {
status.setOption(Base.OPTION_DEFAULT_XPLANGUAGE, xpl, null);
}
// Set label information
String labelContainer = getLabelcontainer();
if (labelContainer != null) {
status.setOption(Base.OPTION_DEFAULT_LABELCONTAINER + getTMLContext().getDesignDBKey(), labelContainer, null);
}
String labelFile = getLabelfile();
if (labelFile != null) {
status.setOption(Base.OPTION_DEFAULT_LABELFILE + getTMLContext().getDesignDBKey(), labelFile, null);
}
// Set scope information
String scope = getTmlscope();
if (scope != null) {
status.setOption(Base.OPTION_WEBTML_SCOPE, scope, TMLOption.SCOPE_GLOBAL);
}
// WebTML Cache functionality
String currentCacheby = this.getCachekey();
if (currentCacheby != null) {
if (status._currentCacheId == null) {
this.addWarning("To use cache, the range tag must have a unique id");
return;
}
// Eval cache key
if (!currentCacheby.equals("")) {
ExpressionEngine engine = ExpressionEngineFactory.getEngine(this.getExprlanguage());
ExpressionResult result = engine.evaluateExpression(currentCacheby, this.getTMLContext(), ExpressionEngine.TYPE_EXPRESSION, null);
if (result.isError()) {
addExpressionWarning(currentCacheby, result);
return;
}
status._currentCacheKey = String.valueOf(result.getResult());
}
else {
status._currentCacheKey = "";
}
// In browser interface we add a custom value to the cache key, so BI caches and Non-Bi caches differ (B000059DA)
if (status.isBrowserInterface()) {
status._currentCacheKey = status._currentCacheKey + "###BROWSER-INTERFACE";
}
WGDatabase db = this.getTMLContext().content().getDatabase();
// Look if the current cache is in evaluation right now, wait for 10 seconds
int sleepTimes = 0;
String cacheLockKey = buildCacheLockKey();
int waitTimeout = 60;
try {
waitTimeout = Integer.parseInt(getWaittimeout());
}
catch (NumberFormatException e) {
addWarning("Cannot parse waittimeout as number: " + getWaittimeout());
}
if (currentlyEvaluatedCaches.containsKey(cacheLockKey)) {
// If we are allowed to serve stale data while the cache processes, we serve the current content of the cache
boolean serveStaleData = db.getBooleanAttribute(WGACore.DBATTRIB_WEBTMLCACHE_SERVESTALEDATA, true);
if (serveStaleData) {
String content = null;
try {
content = getCore().getWebTMLCache().getCache(db.getDbReference(), status._currentCacheId, status._currentCacheKey, new Date(Long.MIN_VALUE));
}
catch (CacheException e) {
getTMLContext().getlog().error("Exception retrieving WebTML cache data for key " + getTMLContext().db().getDbReference() + "/" + status._currentCacheId + "/" + status._currentCacheKey, e);
}
if (content != null) {
// Tell all ranges above us to have a cache latency of 1 minute, so cached stale data is updated after this time
Range.Status ancestor = status;
while ((ancestor = (Range.Status) ancestor.getAncestorTag(Range.class)) != null) {
ancestor.cacheLatency = 1;
}
status._currentCacheKey = null;
status._currentCacheId = null;
status.servedStale = true;
this.setEvalBody(false);
this.setResult(content);
return;
}
}
// Else we wait until cache calculation is completed or we run into the timeout
while (currentlyEvaluatedCaches.containsKey(cacheLockKey)) {
Long cacheTime = (Long) currentlyEvaluatedCaches.get(cacheLockKey);
if( cacheTime != null ){
long cacheTimeout = cacheTime.longValue() + (1000 * 60 * 15);
if (System.currentTimeMillis() > cacheTimeout) {
log.warn("Former cache creation of key '" + status._currentCacheKey + "' took more than 15 minutes. Removing synchronisation lock now");
currentlyEvaluatedCaches.remove(cacheLockKey);
break;
}
}
try {
Thread.sleep(1000);
}
catch (InterruptedException e) {
}
sleepTimes++;
if (sleepTimes > waitTimeout) {
log.warn("Waiting for cache creation of key '" + cacheLockKey + "' timed out after " + waitTimeout + " second(s). Tag is canceled.");
this.addWarning("Waiting for cache creation of key '" + cacheLockKey + "' timed out after " + waitTimeout + " second(s). Tag is canceled.", true);
this.setEvalBody(false);
this.setCancelTag(true);
this.setResult("This section is currently recalculated. Please try again later.");
return;
}
}
}
currentlyEvaluatedCaches.put(cacheLockKey, new Long(System.currentTimeMillis()));
// Try to retrieve cache
Date testedDate = getCore().getDeployer().getLastChangedOrDeployed(db);
String content = null;
try {
content = getCore().getWebTMLCache().getCache(db.getDbReference(), status._currentCacheId, status._currentCacheKey, testedDate);
}
catch (CacheException e) {
getTMLContext().getlog().error("Exception retrieving WebTML cache data for key " + getTMLContext().db().getDbReference() + "/" + status._currentCacheId + "/" + status._currentCacheKey, e);
}
if (content != null) {
Range.currentlyEvaluatedCaches.remove(cacheLockKey);
status._currentCacheId = null;
this.setEvalBody(false);
this.setResult(content);
}
else {
status.putCache = true;
status.cacheDate = testedDate;
if (log.isDebugEnabled()) {
log.debug("Trying to evaluate cache for key '" + cacheLockKey + "'");
}
}
}
}
private String buildCacheLockKey() {
Status status = (Status) getStatus();
return getTMLContext().db().getDbReference() + "//" + status._currentCacheId + "//" + status._currentCacheKey;
}
public static void clearCacheLocks() {
Range.currentlyEvaluatedCaches.clear();
}
/**
* Returns the cacheby.
*
* @return String
*/
public String getCachekey() {
return this.getTagAttributeValue("cachekey", cachekey, null);
}
/**
* Sets the cacheby.
*
* @param cacheby
* The cacheby to set
*/
public void setCachekey(String cacheby) {
this.cachekey = cacheby;
}
/**
* Returns the exprlanguage.
*
* @return String
*/
public String getExprlanguage() {
return this.getTagAttributeValue("exprlanguage", exprlanguage, this.getDefaultExpressionLanguage());
}
public String getXplanguage() {
return this.getExprlanguage();
}
public void setXplanguage(String xpl) {
this.setExprlanguage(xpl);
}
/**
* Sets the exprlanguage.
*
* @param exprlanguage
* The exprlanguage to set
*/
public void setExprlanguage(String exprlanguage) {
this.exprlanguage = exprlanguage;
}
/**
* Returns the defaultxpl.
*
* @return String
*/
public String getDefaultxpl() {
return this.getTagAttributeValue("defaultxpl", defaultxpl, null);
}
/**
* Sets the defaultxpl.
*
* @param defaultxpl
* The defaultxpl to set
*/
public void setDefaultxpl(String defaultxpl) {
this.defaultxpl = defaultxpl;
}
/**
* @return Returns the labelcontainer.
*/
public String getLabelcontainer() {
return getTagAttributeValue("labelcontainer", labelcontainer, null);
}
/**
* @param labelcontainer The labelcontainer to set.
*/
public void setLabelcontainer(String labelcontainer) {
this.labelcontainer = labelcontainer;
}
/**
* @return Returns the labelfile.
*/
public String getLabelfile() {
return getTagAttributeValue("labelfile", labelfile, null);
}
/**
* @param labelfile The labelfile to set.
*/
public void setLabelfile(String labelfile) {
this.labelfile = labelfile;
}
public static Map getCurrentlyEvaluatedCaches() {
return currentlyEvaluatedCaches;
}
protected void tmlCleanup() {
Status status = (Status) getStatus();
if (status.putCache == true && status._currentCacheKey != null) {
Long time = (Long) Range.currentlyEvaluatedCaches.remove(buildCacheLockKey());
if (time == null) {
addWarning("Could not remove cache lock key '" + buildCacheLockKey() + "' because it was not registered anymore");
}
}
}
public String getWaittimeout() {
return getTagAttributeValue("waittimeout", waittimeout, "60");
}
public void setWaittimeout(String waittimeout) {
this.waittimeout = waittimeout;
}
public String getCachelatency() {
return getTagAttributeValue("cachelatency",cachelatency, null);
}
public void setCachelatency(String cachelatency) {
this.cachelatency = cachelatency;
}
public String getTmlscope() {
return getTagAttributeValue("tmlscope", tmlscope, null);
}
public void setTmlscope(String tmlscope) {
this.tmlscope = tmlscope;
}
}