/*******************************************************************************
* 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.url;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Locale;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import de.innovationgate.utils.URLBuilder;
import de.innovationgate.utils.WGUtils;
import de.innovationgate.webgate.api.WGAPIException;
import de.innovationgate.webgate.api.WGContent;
import de.innovationgate.webgate.api.WGDatabase;
import de.innovationgate.webgate.api.WGException;
import de.innovationgate.webgate.api.WGFileContainer;
import de.innovationgate.webgate.api.WGLanguage;
import de.innovationgate.webgate.api.WGUnavailableException;
import de.innovationgate.wga.config.VirtualHost;
import de.innovationgate.wgpublisher.AccessException;
import de.innovationgate.wgpublisher.AuthenticationException;
import de.innovationgate.wgpublisher.WGACore;
import de.innovationgate.wgpublisher.WGAVirtualHostingFilter;
import de.innovationgate.wgpublisher.WGPDispatcher;
import de.innovationgate.wgpublisher.WGPRequestPath;
import de.innovationgate.wgpublisher.lang.LanguageBehaviour;
import de.innovationgate.wgpublisher.lang.LanguageBehaviourTools;
import de.innovationgate.wgpublisher.lang.WebTMLLanguageChooser;
import de.innovationgate.wgpublisher.webtml.Base;
import de.innovationgate.wgpublisher.webtml.BaseTagStatus;
import de.innovationgate.wgpublisher.webtml.utils.HttpErrorException;
import de.innovationgate.wgpublisher.webtml.utils.TMLAction;
import de.innovationgate.wgpublisher.webtml.utils.TMLActionLink;
import de.innovationgate.wgpublisher.webtml.utils.TMLContext;
public class DefaultURLBuilder implements WGAURLBuilder {
private boolean _titlePathAllowed = true;
public String buildContentURL(TMLContext context, String mediaKey, String layoutKey, boolean ignoreVirtualLink) throws UnsupportedEncodingException, WGAPIException {
String url = innerBuildContentURL(context, mediaKey, layoutKey, ignoreVirtualLink);
return rewriteURL(url, context.getrequest(), context.getwgacore(), context.content().isVirtual());
}
protected String innerBuildContentURL(TMLContext context, String mediaKey, String layoutKey, boolean ignoreVirtualLink) throws UnsupportedEncodingException, WGAPIException {
BaseTagStatus tag = context.getDesignContext().getTag();
if (tag == null) {
return null;
}
String completeUrl = null;
if (mediaKey == null) {
mediaKey = tag.getMainMediaKey();
}
// If content is dummy, return a layout url instead. Use the design of the current request, if none specified.
if (context.content().isDummy()) {
if (layoutKey == null || layoutKey.equals("default")) {
layoutKey = (String) context.getrequest().getAttribute(WGACore.ATTRIB_OUTER_DESIGN);
}
return buildLayoutURL(context, null, mediaKey, layoutKey);
}
// Virtual link
if (context.content().isVirtual() && !ignoreVirtualLink) {
// Virtual links adressing content are treated internally, so the resulting link will match the current content URL policy
if (WGContent.VIRTUALLINKTYPE_CONTENT.equals(context.content().getVirtualLinkType()) ||
WGContent.VIRTUALLINKTYPE_UNIQUENAME.equals(context.content().getVirtualLinkType())) {
List elements = WGUtils.deserializeCollection(context.content().getVirtualLink(), "/");
String docID = (String) elements.get(elements.size() - 1);
TMLContext targetContext = context.context("docid:" + docID, false);
if (targetContext != null) {
completeUrl = targetContext.contenturl(mediaKey, layoutKey);
}
else {
completeUrl = WGPDispatcher.buildVirtualLink(context.content(), context.getrequest(), mediaKey, layoutKey);
}
}
// All other virtual link types are coped with by WGPDispatcher
else {
completeUrl = WGPDispatcher.buildVirtualLink(context.content(), context.getrequest(), mediaKey, layoutKey);
}
}
// Normal content URL
else {
// Default layoutkey or expand local references
if (layoutKey == null) {
layoutKey = "default";
}
else {
layoutKey = context.resolveDesignReference(layoutKey);
}
// Title path URL if there is a title path manager, the document is released and default layout is used
TitlePathManager tpm = (TitlePathManager) context.db().getAttribute(WGACore.DBATTRIB_TITLEPATHMANAGER);
if (isTitlePathAllowed() && context.isbrowserinterface() == false && tpm != null && tpm.isGenerateTitlePathURLs() && layoutKey.equals("default") && context.content().getStatus().equals(WGContent.STATUS_RELEASE)) {
completeUrl = buildTitlePathURL(context, mediaKey, tpm);
}
// Standard URL if there is no title path manager or the title path creation failed/is not allowed
if (completeUrl == null) {
completeUrl = buildStandardContentURL(context, mediaKey, layoutKey);
}
}
// Test, if there is a link action defined, that redirects content links to the specified action
String linkAction = (String) context.option(Base.OPTION_LINK_ACTION);
if (linkAction != null && !ignoreVirtualLink) {
List params = new ArrayList();
params.add("content");
params.add(mediaKey);
params.add(layoutKey);
params.add(completeUrl);
TMLAction tmlAction = context.getActionByID(linkAction, context.getDesignDBKey());
if (tmlAction != null) {
return context.getURLBuilder().buildActionURL(context, tmlAction, params, null, context.getpath());
}
else {
return "";
}
}
else {
return completeUrl;
}
}
private String buildTitlePathURL(TMLContext context, String mediaKey, TitlePathManager tpm) throws WGAPIException, UnsupportedEncodingException {
List path = tpm.buildTitlePath(context.content(), mediaKey, new WebTMLLanguageChooser(context.db(), context));
if (path == null) {
return null;
}
// Add base path
path.add(0, buildBasePath(context));
return WGUtils.serializeCollection(path, "/");
}
private String buildStandardContentURL(TMLContext context, String mediaKey, String layoutKey) throws WGAPIException {
StringBuffer url = new StringBuffer();
// Base path up to layout key
url.append(buildBasePath(context)).append("/");
url.append(mediaKey).append("/");
url.append(layoutKey).append("/");
String contentKey = WGPDispatcher.buildContentURLID(context.content(), mediaKey, context.isbrowserinterface());
url.append(contentKey);
return url.toString();
}
public String buildLayoutURL(TMLContext context, String dbKey, String mediaKey, String layoutKey) throws UnsupportedEncodingException, WGAPIException {
String url = innerBuildLayoutURL(context, dbKey, mediaKey, layoutKey);
return rewriteURL(url, context.getrequest(), context.getwgacore(), false);
}
protected String innerBuildLayoutURL(TMLContext context, String dbKey, String mediaKey, String layoutKey) throws UnsupportedEncodingException, WGAPIException {
BaseTagStatus tag = context.getDesignContext().getTag();
if (tag == null) {
return null;
}
// Defaulting of media and layout key
if (mediaKey == null) {
mediaKey = tag.getMainMediaKey();
}
if (layoutKey == null) {
layoutKey = "default";
}
else {
layoutKey = context.resolveDesignReference(layoutKey);
}
// Base path
StringBuffer url = new StringBuffer();
url.append(context.getEnvironment().getPublisherURL());
url.append("/");
// DB determination
String designdbKey = dbKey;
if (designdbKey == null) {
designdbKey = context.getDesignDBKey();
}
if (designdbKey == null) {
designdbKey = context.db().getDbReference();
}
url.append(designdbKey).append("/");
// Determining target language
String targetLanguage = context.getpreferredlanguage(); // Fallback value
try {
WGDatabase targetDB = context.db(designdbKey);
if (targetDB != null && targetDB.isSessionOpen()) {
LanguageBehaviour langBehaviour = LanguageBehaviourTools.retrieve(targetDB);
WGLanguage lang = langBehaviour.webtmlSelectDatabaseLanguage(targetDB, context);
if (lang != null) {
targetLanguage = lang.getName();
}
}
}
catch (WGException e) {
context.getlog().error("Exception determining target language for layout URL", e);
}
// Media and layout key
url.append(mediaKey).append("/");
url.append(WGPDispatcher.buildLayoutURLID(context.getwgacore().getContentdbs().get(designdbKey), layoutKey, targetLanguage, mediaKey));
// Test, if there is a link action defined, that redirects content links to the specified action
String linkAction = (String) context.option(Base.OPTION_LINK_ACTION);
if (linkAction != null) {
List params = new ArrayList();
params.add("layout");
params.add(mediaKey);
params.add(layoutKey);
params.add(url.toString());
TMLAction tmlAction = context.getActionByID(linkAction, designdbKey);
if (tmlAction != null) {
return context.getURLBuilder().buildActionURL(context, tmlAction, params, null, null);
}
else {
return "";
}
}
else {
return url.toString();
}
}
private String buildBasePath(TMLContext context) {
StringBuffer url = new StringBuffer();
url.append(context.getEnvironment().getPublisherURL());
url.append("/");
url.append(context.getdocument().getDatabase().getAttribute(WGACore.DBATTRIB_DBKEY).toString());
return url.toString();
}
public String buildActionURL(TMLContext context, TMLAction action, List params, String portletMode, String portletContext) throws WGAPIException, UnsupportedEncodingException {
String url = innerBuildActionURL(context, action, params, portletMode, portletContext);
return rewriteURL(url, context.getrequest(), context.getwgacore(), false);
}
protected String innerBuildActionURL(TMLContext context, TMLAction action, List params, String portletMode, String portletContext) throws WGAPIException, UnsupportedEncodingException {
BaseTagStatus tag = context.getDesignContext().getTag();
if (tag == null) {
return null;
}
TMLActionLink actionLink = action.createActionLink(params, context);
actionLink.setPortletmode(portletMode);
if (portletContext != null) {
actionLink.setPortletContextPath(context, portletContext);
}
String encodedActionLink = actionLink.getEncodedString(context.getwgacore());
// Retrieve relevant form.
String formID = tag.getRelevantForm();
// Inside a form we use javascript to post the action
if (formID != null) {
StringBuffer jsUrl = new StringBuffer();
jsUrl.append("javascript:document.forms['" + formID + "'].$formaction.value ='" + encodedActionLink + "';");
jsUrl.append("document.forms['" + formID + "'].submit();");
return jsUrl.toString();
}
// Outside we use a simple action link
else {
try {
URLBuilder builder = new URLBuilder(new java.net.URL(tag.getRequestURL()));
builder.setParameterEncoding(context.getwgacore().getCharacterEncoding());
builder.setParameter("$action", encodedActionLink);
return builder.rebuild(false);
}
catch (MalformedURLException exc) {
context.getlog().error("Error building action URL", exc);
return null;
}
}
}
public boolean isTitlePathAllowed() {
return _titlePathAllowed;
}
public void setTitlePathAllowed(boolean titlePathAllowed) {
_titlePathAllowed = titlePathAllowed;
}
public String buildHomepageURL(WGDatabase db, HttpServletRequest request) throws WGException {
String url = innerBuildHomepageURL(db, request);
if (url != null) {
return rewriteURL(url, request, WGACore.retrieve(request.getSession().getServletContext()), false);
}
else {
return null;
}
}
protected String innerBuildHomepageURL(WGDatabase db, HttpServletRequest request) throws WGException {
String homepage = (String) db.getAttribute(WGACore.DBATTRIB_HOME_PAGE);
WGACore core = WGACore.retrieve(request.getSession().getServletContext());
WGPDispatcher dispatcher = core.getDispatcher();
// First try: if db homepage attribute set - redirect to homepage
if (homepage != null && !homepage.trim().equals("")) {
return dispatcher.getPublisherURL(request) + "/" + db.getDbReference().toLowerCase() + "/" + homepage;
}
// Try to find a document named "home" in the relevant languages
db = core.openContentDB(db, request, false);
if (db.isSessionOpen()) {
LanguageBehaviour langBehaviour = LanguageBehaviourTools.retrieve(db);
WGContent content = langBehaviour.requestSelectContentForName(db, request, "home", false);
if (content != null && content.mayBePublished(false, WGContent.DISPLAYTYPE_NONE)) {
return dispatcher.getPublisherURL(request) + "/" + db.getDbReference().toLowerCase() + "/home";
}
}
return null;
}
public String buildFileURL(TMLContext context, String dbKey, String containerName, String fileName) throws UnsupportedEncodingException, WGAPIException {
String url = innerBuildFileURL(context, dbKey, containerName, fileName);
return rewriteURL(url, context.getrequest(), context.getwgacore(), false);
}
protected String innerBuildFileURL(TMLContext context, String dbKey, String containerName, String fileName) throws UnsupportedEncodingException, WGAPIException {
if (fileName == null) {
context.addwarning("Missing file name for URL");
return null;
}
if (context.iswebenvironment()) {
containerName = context.resolveDesignReference(containerName);
}
// Basic path
StringBuffer url = new StringBuffer();
url.append(context.getEnvironment().getPublisherURL());
url.append("/");
// Determine containing database
boolean containerIsContext = false;
try {
// If no container name given we will use the current content document in context
if (containerName == null || containerName.equals("this")) {
containerName = context.content().getContentKey(false).toString();
containerIsContext = true;
url.append(context.db().getDbReference());
}
// Explicit database addressed
else if (dbKey != null) {
WGDatabase designDB = context.db(context.resolveDBKey(dbKey));
if (designDB != null) {
url.append(designDB.getDbReference());
}
else {
context.addwarning("Unknown design db: " + dbKey);
return null;
}
}
// No database addressed. Try to find file in file container of design db. Else we use the current context db
else {
WGFileContainer container = null;
WGDatabase designDB = context.designdb();
if (designDB != null && designDB.isSessionOpen()) {
container = designDB.getFileContainer(containerName);
}
if (container != null) {
url.append(designDB.getDbReference());
}
else {
url.append(context.db().getDbReference());
}
}
}
catch (WGException e) {
context.addwarning("Error opening design db: " + e.getClass().getName() + " - " + e.getMessage());
context.getlog().error("Error opening design db", e);
return null;
}
// Address nested containers in URL form with slashes instead of colons, encode only the elements
List<String> containerPath = new ArrayList<String>();
for (String elem : containerName.split(":")) {
containerPath.add(URLEncoder.encode(elem, context.getwgacore().getCharacterEncoding()));
}
String containerURL = WGUtils.serializeCollection(containerPath, "/");
// Encode file path elements, but not the slashes
List<String> filePath = new ArrayList<String>();
for (String elem : fileName.split("/")) {
filePath.add(URLEncoder.encode(elem, context.getwgacore().getCharacterEncoding()));
}
String fileURL = WGUtils.serializeCollection(filePath, "/");
// if content doc addressed and filename contains ".zip/", we use the ~file-Syntax relative to the content URL so file URL will be relative to the content document URL (if not titlepath)
if (containerIsContext && fileName.toLowerCase().indexOf(".zip/") != -1) {
url = new StringBuffer();
url.append(context.contenturl());
url.append("/~file/");
url.append(fileURL);
}
// Normal syntax with qualifying "/file/" URL part
else {
url.append("/file/").append(containerURL).append("/").append(fileURL);
}
return url.toString();
}
public String rewriteURL(String url, HttpServletRequest request, WGACore core) {
return rewriteURL(url, request, core, true);
}
public String rewriteURL(String url, HttpServletRequest request, WGACore core, boolean isVirtual) {
// Cannot rewrite if no request available
if (request == null) {
return url;
}
VirtualHost vHost = WGAVirtualHostingFilter.findMatchingHost(core.getWgaConfiguration(), request);
if (vHost == null) {
return url;
} else {
URLBuilder builder = parseURL(core, request, url);
if (builder != null) {
String path = builder.getPath().substring(request.getContextPath().length());
if (path.startsWith("/")) {
path = path.substring(1);
}
if (path.indexOf("/") != -1) {
String targetDBKey = path.substring(0, path.indexOf("/"));
// skip vhosts processing for plugins
if (targetDBKey.startsWith("plugin-")) {
return url;
}
// first check if default database is requested and if we have to hide it from url
if (vHost.isHideDefaultDatabaseInURL() && vHost.getDefaultDatabase() != null) {
String defaultDBKey = WGAVirtualHostingFilter.getDefaultDBKey(core, vHost);
if (defaultDBKey != null && targetDBKey.equalsIgnoreCase(defaultDBKey)) {
// default db requested - remove from path
builder.setPath(path.substring(defaultDBKey.length()));
try {
return builder.rebuild(false).toString();
}
catch (UnsupportedEncodingException e) {
core.getLog().error("Unable to rewrite url '" + url + "'.");
}
}
}
if (!WGAVirtualHostingFilter.isDBKeyAllowed(core.getWgaConfiguration(), vHost, targetDBKey)) {
// we have to find the best matching host for this db and create an absolute url here
VirtualHost preferredHost = WGAVirtualHostingFilter.findPreferredHostForDatabase(core.getWgaConfiguration(), targetDBKey);
if (preferredHost != null) {
builder.setHost(preferredHost.getServername());
if (preferredHost.isHideDefaultDatabaseInURL() && preferredHost.getDefaultDatabase() != null) {
String defaultDBKey = WGAVirtualHostingFilter.getDefaultDBKey(core, preferredHost);
if (defaultDBKey != null && targetDBKey.equalsIgnoreCase(defaultDBKey)) {
// default db requested - remove from path
builder.setPath(path.substring(defaultDBKey.length()));
}
}
try {
return builder.rebuild(true).toString();
}
catch (UnsupportedEncodingException e) {
core.getLog().error("Unable to rewrite url '" + url + "'.");
}
}
}
}
}
}
return url;
}
private URLBuilder parseURL(WGACore core, HttpServletRequest request, String url) {
URLBuilder builder = null;
try {
if (url.contains("://")) {
builder = new URLBuilder(new URL(url));
} else {
URL urlObj = new URL(request.getRequestURL().toString());
String path = null;
String query = null;
if (url.contains("?")) {
String[] tokens = url.split("\\?");
path = tokens[0];
query = tokens[1];
} else {
path = url;
}
builder = new URLBuilder(urlObj.getProtocol(), urlObj.getPort(), urlObj.getHost(), path, null);
if (query != null) {
builder.addQueryString(query, core.getCharacterEncoding());
}
}
}
catch (Exception e) {
core.getLog().error("URLBuilder creating failed for url '" + url + "'.", e);
}
return builder;
}
}