/*******************************************************************************
* 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;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.LineNumberReader;
import java.io.StringReader;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.Lock;
import org.apache.commons.transaction.locking.GenericLock;
import org.apache.commons.transaction.locking.GenericLockManager;
import org.apache.commons.transaction.locking.LockManager;
import org.apache.commons.transaction.locking.MultiLevelLock;
import org.apache.commons.transaction.util.Log4jLogger;
import org.apache.log4j.Logger;
import de.innovationgate.utils.ReplaceProcessor;
import de.innovationgate.utils.WGUtils;
import de.innovationgate.webgate.api.WGAPIException;
import de.innovationgate.webgate.api.WGDatabase;
import de.innovationgate.webgate.api.WGDatabaseEventListener;
import de.innovationgate.webgate.api.WGDocument;
import de.innovationgate.webgate.api.WGTMLModule;
import de.innovationgate.wga.common.Constants;
import de.innovationgate.wga.common.beans.csconfig.v1.MediaKey;
import de.innovationgate.wga.config.DesignReference;
import de.innovationgate.wgpublisher.design.WGADesignManager;
import de.innovationgate.wgpublisher.design.db.DBDesignSource;
public class WGPDeployer implements WGACoreEventListener {
public class TMLTagsPreProcessor implements ReplaceProcessor {
private int lineNumber = 0;
/*
* (Kein Javadoc)
*
* @see
* de.innovationgate.utils.ReplaceProcessor#replace(java.lang.String,
* int, int, java.io.Writer)
*/
public int replace(String text, int from, int to, Writer out) throws IOException {
// Find end position of tagname
int tagnameEndPos;
int nextSpacePos = text.indexOf(" ", from);
int closeTagPos = text.indexOf(">", from);
int closeEndTagPos = text.indexOf("/>", from);
tagnameEndPos = Integer.MAX_VALUE;
if (nextSpacePos != -1 && nextSpacePos < tagnameEndPos) {
tagnameEndPos = nextSpacePos;
}
if (closeTagPos != -1 && closeTagPos < tagnameEndPos) {
tagnameEndPos = closeTagPos;
}
if (closeEndTagPos != -1 && closeEndTagPos < tagnameEndPos) {
tagnameEndPos = closeEndTagPos;
}
// if no end found (maybe syntax error) just put anything out
if (tagnameEndPos == Integer.MAX_VALUE) {
out.write("<tml:");
return to;
}
// Look if we have the shortened tml:include syntax
String tagname = text.substring(from, tagnameEndPos);
String localName = tagname.substring(5);
String refName = null;
String designdbName = null;
if (localName.startsWith("[") && localName.endsWith("]")) {
refName = localName.substring(1, localName.length() - 1);
int slashPos = refName.indexOf("/");
if (slashPos != -1) {
designdbName = refName.substring(0, slashPos);
refName = refName.substring(slashPos + 1);
}
tagname = "<tml:include";
}
// Write tagname and additional attributes
out.write(tagname);
out.write(" sourceline=\"");
out.write(String.valueOf(lineNumber));
out.write("\"");
if (refName != null) {
out.write(" ref=\"");
out.write(refName);
out.write("\"");
}
if (designdbName != null) {
out.write(" designdb=\"");
out.write(designdbName);
out.write("\"");
}
return tagnameEndPos;
}
/**
* @return
*/
public int getLineNumber() {
return lineNumber;
}
/**
* @param i
*/
public void setLineNumber(int i) {
lineNumber = i;
}
}
public class TMLCloseTagsPreProcessor implements ReplaceProcessor {
public int replace(String text, int from, int to, Writer out) throws IOException {
// Find end position of tagname
int tagnameEndPos = text.indexOf(">", from);
// if no end found (maybe syntax error) just put the source string
// out
if (tagnameEndPos == -1) {
out.write("</tml:");
return to;
}
// Look if we have the shortened tml:include syntax
String tagname = text.substring(from, tagnameEndPos);
String localName = tagname.substring(6);
String refName = null;
if (localName.startsWith("[") && localName.endsWith("]")) {
tagname = "</tml:include";
}
// Write tagname and additional attributes
out.write(tagname);
return tagnameEndPos;
}
}
class DeployJob {
private WGDatabase _db;
private boolean _initialBuild;
private boolean _done = false;
public DeployJob(WGDatabase db, boolean initialBuild) {
_db = db;
_initialBuild = initialBuild;
}
/**
* @return
*/
public WGDatabase getDb() {
return _db;
}
/**
* @return
*/
public boolean isInitialBuild() {
return _initialBuild;
}
/**
* @return
*/
public boolean isDone() {
return _done;
}
/**
* @param b
*/
public void setDone(boolean b) {
_done = b;
}
}
class PPTagsPreProcessor implements ReplaceProcessor {
private WGDatabase _db;
private WGACore _core;
private WGTMLModule _mod;
private boolean _enabled = true;
public PPTagsPreProcessor(WGACore core, WGTMLModule mod) {
_core = core;
_db = mod.getDatabase();
_mod = mod;
}
/**
* @see de.innovationgate.utils.ReplaceProcessor#replace(String, int,
* int, Writer)
*/
public int replace(String text, int from, int to, Writer out) throws IOException {
// First get all data
int endPos = text.indexOf("%}", to);
if (endPos == -1) {
out.write(text);
return text.length();
}
// If disabled, simply put code out
if (_enabled == false) {
out.write(text.substring(from, to));
return to;
}
String call = text.substring(from + 2, endPos);
int nameDelimiterPos = call.indexOf(":");
String name = null;
List<Object> params = new ArrayList<Object>();
if (nameDelimiterPos != -1) {
name = call.substring(0, nameDelimiterPos).trim().toLowerCase();
params.addAll(WGUtils.deserializeCollection(call.substring(nameDelimiterPos + 1), ";"));
}
else {
name = call;
}
// Preprocessor commands
if (name.equals("disablePP")) {
_enabled = false;
}
else if (name.equals("label")) {
out.write("<tml:label key=\"" + params.get(0) + "\"/>");
}
return endPos + 2;
}
}
public static final String FOLDER_DYNAMIC_RESOURCES = "dynamic";
private WGACore _core;
private LockManager _lockManager = new GenericLockManager(1, new Log4jLogger(Logger.getLogger("wga.deployer.locking")));
protected static final Logger LOG = Logger.getLogger("wga.deployer");
private Map<String, DeployedLayout> _layoutMappings = new ConcurrentHashMap<String, DeployedLayout>();
private Map<String, DeployedLayout> _layoutsByFileName = new ConcurrentHashMap<String, DeployedLayout>();
private File _resourcesFolder;
public WGPDeployer(WGACore core) {
_core = core;
}
public void shutdown() {
if (_resourcesFolder.exists()) {
LOG.info("Deleting deployed TML modules");
WGUtils.delTree(_resourcesFolder);
_resourcesFolder = null;
}
_core.removeEventListener(this);
}
private synchronized DeployedLayout deployTML(WGTMLModule tmlLib) throws DeployerException {
if (tmlLib == null) {
throw new DeployerException("Tried to deploy tml module \"null\". Maybe a tml module is not accessible");
}
DesignReference ref;
try {
ref = WGADesignManager.createDesignReference(tmlLib);
}
catch (WGAPIException e1) {
throw new DeployerException("Error retrieving design reference for " + tmlLib.getDocumentKey(), e1);
}
String databaseKey = (String) tmlLib.getDatabase().getDbReference();
String layoutKey = ref.toString();
String mediaKey = null;
String resourceName = null;
try {
if (tmlLib.isVariant()) {
layoutKey = layoutKey + "//" + databaseKey;
}
mediaKey = tmlLib.getMediaKey().toLowerCase();
if (_core.isMediaKeyDefined(mediaKey) == false) {
_core.addMediaMapping(new MediaKey(mediaKey, "text/html", false, false), false);
}
// Build complete code
StringBuffer tmlCode = new StringBuffer();
tmlCode.append("<%@ taglib uri=\"http://www.innovationgate.de/wgpublisher/webtml/2.2\" prefix=\"tml\" %>");
tmlCode.append("<%@ page ");
// F000037B2
if (_core.getCharacterEncoding() != null) {
tmlCode.append(" pageEncoding=\"" + _core.getCharacterEncoding() + "\" ");
}
tmlCode.append(" buffer=\"" + _core.tmlBuffer + "kb\" autoFlush=\"true\" isThreadSafe=\"true\" session=\"true\" errorPage=\"../error.jsp\" %>");
tmlCode.append("<%@ page import=\"de.innovationgate.wgpublisher.jsputils.*\"%>");
tmlCode.append(_core.tmlHeader);
tmlCode.append("<tml:root ref=\"" + layoutKey + "\" resource=\"" + tmlLib.getName() + " (" + tmlLib.getMediaKey() + ")\" modulename=\"" + tmlLib.getName() + "\" modulemediakey=\""
+ tmlLib.getMediaKey() + "\">");
tmlCode.append(preprocessCode(tmlLib));
tmlCode.append("</tml:root>");
DeployedLayout layout = _layoutMappings.get(layoutKey);
if (layout != null) {
// use existing layout obj
layout.init(tmlLib, layoutKey, _resourcesFolder, _core.getCharacterEncoding());
}
else {
// create new
layout = new DeployedLayout(tmlLib, layoutKey, _resourcesFolder, _core.getCharacterEncoding());
}
// deploy
resourceName = tmlLib.getName().toLowerCase();
LOG.info("Deploying tml " + mediaKey + "/" + resourceName + " (" + ref.getDesignProviderReference().toString() + ")");
layout.deploy(tmlCode.toString());
// Map deployed layout
_layoutMappings.put(layoutKey, layout);
_layoutsByFileName.put(layout.getFile().getName(), layout);
_lastDeployments.put(tmlLib.getDatabase().getDbReference(), new Date());
return layout;
}
catch (Exception e) {
throw new DeployerException("Error deploying tml " + databaseKey + "/" + resourceName + " (" + mediaKey + ")", e);
}
}
public String filenameToTMLModuleName(String filename) {
DeployedLayout layout = _layoutsByFileName.get(filename);
if (layout != null) {
return layout.getMainLibKey();
}
else {
return null;
}
}
public String locateTmlResource(WGTMLModule tmlLib) throws WGAPIException, DeployerException {
DesignReference ref = WGADesignManager.createDesignReference(tmlLib);
DeployedLayout layout = getDeployedLayout(tmlLib, ref);
if (layout == null) {
MultiLevelLock lock = _lockManager.atomicGetOrCreateLock(ref.toString());
try {
try {
lock.acquire(Thread.currentThread(), 1, true, true, Long.MAX_VALUE);
}
catch (InterruptedException e) {
}
layout = getDeployedLayout(tmlLib, ref);
if (layout == null) {
layout = deployTML(tmlLib);
}
}
finally {
lock.release(Thread.currentThread());
}
}
String resourcePath = "/" + FOLDER_DYNAMIC_RESOURCES + "/" + layout.getFile().getName();
return resourcePath;
}
private DeployedLayout getDeployedLayout(WGTMLModule mod, DesignReference ref) throws WGAPIException {
String layoutKey = ref.toString();
// On design variants we must append the database key to the layout key, since different database may use different data
if (mod.isVariant()) {
layoutKey = layoutKey + "//" + mod.getDatabase().getDbReference();
}
DeployedLayout layout = this._layoutMappings.get(layoutKey);
if (layout == null) {
return null;
}
if (layout.needsUpdate(mod)) {
return null;
}
else {
return layout;
}
}
/**
* @see WGDatabaseEventListener#isTemporary()
*/
public boolean isTemporary() {
return false;
}
public Map<String, DeployedLayout> getLayoutMappings() {
return this._layoutMappings;
}
public void removeLayouts(WGDatabase db) {
String key = (String) db.getAttribute(WGACore.DBATTRIB_DBKEY);
Set<String> layoutsSet = this._layoutMappings.keySet();
synchronized (this._layoutMappings) {
Iterator<String> layouts = layoutsSet.iterator();
while (layouts.hasNext()) {
String layoutKey = layouts.next();
if (layoutKey.startsWith(key + "/")) {
DeployedLayout layout = _layoutMappings.get(layoutKey);
if (layout != null) {
_layoutsByFileName.remove(layout.getFile().getName());
}
layouts.remove();
}
}
}
}
public String preprocessCode(WGTMLModule mod) throws WGAPIException {
String result = mod.getCode();
// First pass. Process preprocessor tags
try {
PPTagsPreProcessor preProcessor = new PPTagsPreProcessor(_core, mod);
result = WGUtils.strReplace(result, "{%", preProcessor, true);
}
catch (RuntimeException e) {
LOG.error("Error preprocessing WebTML module " + mod.getDocumentKey(), e);
}
// Second pass. Process WebTML tags
int codeOffset = mod.getCodeOffset();
LineNumberReader reader = new LineNumberReader(new StringReader(result));
StringBuffer out = new StringBuffer();
TMLTagsPreProcessor linePreProcessor = new TMLTagsPreProcessor();
String line;
boolean firstLine = true;
try {
while ((line = reader.readLine()) != null) {
if (!firstLine) {
out.append("\n");
}
else {
firstLine = false;
}
linePreProcessor.setLineNumber(codeOffset + reader.getLineNumber());
out.append(WGUtils.strReplace(line, "<tml:", linePreProcessor, true));
}
result = out.toString();
}
catch (IOException e) {
LOG.error("Error adding line numbers to WebTML code", e);
}
// Third pass. Process WebTML close tags
result = WGUtils.strReplace(result, "</tml:", new TMLCloseTagsPreProcessor(), true);
return result;
}
public void startup() {
// Create folder for dynamic resource
_resourcesFolder = new File(_core.getServletContext().getRealPath("/"), FOLDER_DYNAMIC_RESOURCES);
if (_resourcesFolder.exists()) {
_core.getLog().info("Deleting deployed tmls from previous instance");
WGUtils.delTree(_resourcesFolder);
}
_resourcesFolder.mkdir();
// Register for core events
_core.addEventListener(this);
}
private Map<String,Date> _lastDeployments = new HashMap<String,Date>();
public Date getLastChangedOrDeployed(WGDatabase db) {
String designDBKey = _core.getDesignDatabaseKey(db);
Date lastDeployed = _lastDeployments.get(designDBKey);
Date lastChanged = db.getLastChanged();
if (lastDeployed == null || lastChanged.after(lastDeployed)) {
return lastChanged;
}
else {
return lastDeployed;
}
}
/**
*
* @param domainConfig
* @return
* @throws IOException
*/
public String deployErrorPage(String code) throws IOException {
File errJsp = File.createTempFile("err", ".jsp", _resourcesFolder);
FileWriter out = new FileWriter(errJsp);
out.write(code);
out.flush();
out.close();
return "/" + _resourcesFolder.getName() + "/" + errJsp.getName();
}
public void contentStoreConnected(WGACoreEvent event) {
}
public void contentStoreDisconnected(WGACoreEvent event) {
WGDatabase db = event.getDatabase();
String dbkey = (String) db.getAttribute(WGACore.DBATTRIB_DBKEY);
_lastDeployments.remove(dbkey);
}
public void shutdownPostDisconnect(WGACoreEvent event) {
}
public void shutdownPreDisconnect(WGACoreEvent event) {
}
public void startupPostConnect(WGACoreEvent event) {
}
public void startupPreConnect(WGACoreEvent event) {
}
}