/******************************************************************************
* Copyright 2013 Bernhard Grünewaldt *
* *
* Licensed under the Apache License, Version 2.0 (the "License"); *
* you may not use this file except in compliance with the License. *
* You may obtain a copy of the License at *
* *
* http://www.apache.org/licenses/LICENSE-2.0 *
* *
* Unless required by applicable law or agreed to in writing, software *
* distributed under the License is distributed on an "AS IS" BASIS, *
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. *
* See the License for the specific language governing permissions and *
* limitations under the License. *
******************************************************************************/
package com.google.code.greenwood.confluenceadvancedcodeblockplugin.macro;
import java.io.IOException;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import org.apache.commons.codec.binary.Base64;
import org.ini4j.Ini;
import org.ini4j.InvalidFileFormatException;
import org.ini4j.Profile.Section;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.atlassian.cache.Cache;
import com.atlassian.cache.CacheManager;
import com.atlassian.confluence.core.ContentPropertyManager;
import com.atlassian.confluence.pages.Page;
import com.atlassian.confluence.pages.PageManager;
import com.atlassian.confluence.renderer.radeox.macros.MacroUtils;
import com.atlassian.confluence.setup.BootstrapManager;
import com.atlassian.confluence.util.velocity.VelocityUtils;
import com.atlassian.renderer.RenderContext;
import com.atlassian.renderer.v2.RenderMode;
import com.atlassian.renderer.v2.macro.BaseMacro;
import com.atlassian.renderer.v2.macro.MacroException;
import com.atlassian.sal.api.message.I18nResolver;
import com.google.code.greenwood.confluenceadvancedcodeblockplugin.cache.AdvancedCodeBlockCache;
import com.google.code.greenwood.confluenceadvancedcodeblockplugin.servlets.DownloadCodeServlet;
public class BaseAdvancedCodeblockMacro extends BaseMacro {
private static final Logger log = LoggerFactory.getLogger(BaseAdvancedCodeblockMacro.class);
public static final String PARAM_PLACEHOLDERCONFIG = "placeholderconfig";
public static final String PARAM_ID = "id";
protected static final String[] allowedSyntaxHighlighterLanguages = new String[]{"bsh", "c", "cc", "cpp", "cs", "csh", "cyc", "cv", "htm", "html",
"java", "js", "m", "mxml", "perl", "pl", "pm", "py", "rb", "sh",
"xhtml", "xml", "xsl"};
public static final String VELOCITY_PLACEHOLDER_MACROTYPE = "macrotype";
public static final String VELOCITY_PLACEHOLDER_CODE = "code";
public static final String VELOCITY_PLACEHOLDER_TITLE = "title";
public static final String VELOCITY_PLACEHOLDER_LANG = "lang";
public static final String VELOCITY_PLACEHOLDER_MACROID = "macroid";
public static final String VELOCITY_PLACEHOLDER_ENABLEDDL = "enableddl";
public static final String VELOCITY_PLACEHOLDER_GLOBALTITLE = "globaltitle";
public static final String VELOCITY_PLACEHOLDER_PAGEID = "pageid";
public static final String VELOCITY_PLACEHOLDER_CODEBLOCKS = "codeblocks";
public static final String PAGE_PROPERTY_PREFIX = "com.google.code.advanced.codeblock.plugin.";
public static final String VELOCITY_PLACEHOLDER_LAST_CACHE_UPDATE = "lastcacheupdate";
/* CACHE */
public final static String MACRO_CACHE_KEY = "confluenceadvancedcodeblockplugin";
public final static String MACRO_ACTION_PREFIX = "confluenceadvancedcodeblockplugin";
protected ContentPropertyManager contentPropertyManager;
protected PageManager pageManager;
private I18nResolver i18n;
private CacheManager cacheManager;
protected BootstrapManager bootstrapManager;
@Override
public String execute(@SuppressWarnings("rawtypes") Map params, String body, RenderContext renderContext)
throws MacroException {
return null;
}
@Override
public RenderMode getBodyRenderMode() {
return null;
}
@Override
public boolean hasBody() {
return false;
}
public ContentPropertyManager getContentPropertyManager() {
return contentPropertyManager;
}
public void setContentPropertyManager(
ContentPropertyManager contentPropertyManager) {
this.contentPropertyManager = contentPropertyManager;
}
public void setPageManager(PageManager pageManager) {
this.pageManager = pageManager;
}
public void setI18n(I18nResolver i18n) {
this.i18n = i18n;
}
public void setCacheManager(CacheManager cacheManager) {
this.cacheManager = cacheManager;
}
public void setBootstrapManager(BootstrapManager bootstrapManager) {
this.bootstrapManager = bootstrapManager;
}
protected boolean isValidLanguage(String lang) {
List<String> allowedLangs = Arrays.asList(allowedSyntaxHighlighterLanguages);
if (lang != null && allowedLangs.contains(lang)) {
return true;
}
return false;
}
public static String[] splitMacroBodyIntoContentAndConfig(String configWithContent) throws InvalidFileFormatException {
String[] splitted = configWithContent.split("\\[content\\]", 2);
if (configWithContent == null || splitted.length < 2) {
throw new InvalidFileFormatException("no [content] block found. Invalid format.");
}
return splitted;
}
/**
* Adds all PagePropertyKeys to a StringArray which then the DownloadListCodeServlet will parse and provide as XML.
* @param page
* @param propertykeyWithoutPrefix
*/
protected void addPagePropertyKeyToPagePropertyList(Page page, String propertykeyWithoutPrefix) {
if (propertykeyWithoutPrefix != null && ! propertykeyWithoutPrefix.isEmpty()) {
// get PagePropertyKey List
String pagePropertyKey = contentPropertyManager.getTextProperty(page, AdvancedCodeBlockMacro.PAGE_PROPERTY_PREFIX+page.getIdAsString());
if (pagePropertyKey == null || pagePropertyKey.isEmpty()) {
pagePropertyKey = propertykeyWithoutPrefix+"|";
} else if (!pagePropertyKey.contains(propertykeyWithoutPrefix)) {
pagePropertyKey = pagePropertyKey + propertykeyWithoutPrefix+"|";
} else {
return;
}
// save PagePropertyKey List
contentPropertyManager.setTextProperty(page, AdvancedCodeBlockMacro.PAGE_PROPERTY_PREFIX+page.getIdAsString(), pagePropertyKey);
}
}
/**
* Mockable Method
*/
public Map<String,Object> getDefaultVelocityContext() {
return MacroUtils.defaultVelocityContext();
}
/**
* Mockable Method
*/
public String renderTemplate(String templateName, Map<String,Object> context) {
return VelocityUtils.getRenderedTemplate(templateName,context);
}
public List<Map<String, String>> parseConfigWithContent(String configUnparsed, String contentUnparsed, String macroid, Long pageid, Map<String, Object> context, Boolean enableddl) throws InvalidFileFormatException, IOException {
List<Map<String, String>> result = new ArrayList<Map<String, String>>();
Ini ini = new Ini();
StringReader input = new StringReader(configUnparsed);
ini.load(input);
Set<String> sections = ini.keySet();
for (String sectionName : sections) {
StringBuilder strbuilder = new StringBuilder();
Map<String, String> params = new HashMap<String, String>();
Section currentsection = ini.get(sectionName);
Set<Entry<String,String>> entrySet = currentsection.entrySet();
String currentContent = contentUnparsed;
for (Entry<String,String> entry : entrySet) {
currentContent = currentContent.replace("${"+entry.getKey()+"}", entry.getValue());
}
strbuilder.append(currentContent);
String finalcode = strbuilder.toString();
// content properties -> https://developer.atlassian.com/display/CONFDEV/Persistence+in+Confluence
// FIXME: Remove dead code from page properties. Cause: Renaming a section. Removing Macro from Page. Renaming Macroid.
// Cleanup Task should delete entries, that have no valid macroid and sectionname.
// Also delete all props, where is no macro on the page.
if (enableddl) {
String propertyKey = new String(Base64.encodeBase64((macroid+sectionName).getBytes()));
if (pageManager != null) {
Page page = pageManager.getPage(pageid);
contentPropertyManager.setTextProperty(page, PAGE_PROPERTY_PREFIX+propertyKey, finalcode);
addPagePropertyKeyToPagePropertyList(page, PAGE_PROPERTY_PREFIX+propertyKey);
params.put(DownloadCodeServlet.PARAM_PAGECONTENTPROPERTYKEY, propertyKey);
params.put(DownloadCodeServlet.PARAM_PAGEID, pageid.toString());
}
}
params.put(VELOCITY_PLACEHOLDER_CODE, finalcode);
params.put(VELOCITY_PLACEHOLDER_TITLE, sectionName);
result.add(params);
}
// Raw Download
String propertyKey = new String(Base64.encodeBase64((macroid+"raw").getBytes()));
if (pageManager != null) {
Page page = pageManager.getPage(pageid);
contentPropertyManager.setTextProperty(page, PAGE_PROPERTY_PREFIX+propertyKey, contentUnparsed);
addPagePropertyKeyToPagePropertyList(page, PAGE_PROPERTY_PREFIX+propertyKey);
context.put(DownloadCodeServlet.PARAM_RAWDOWNLOAD_PAGECONTENTPROPERTYKEY, propertyKey);
context.put(VELOCITY_PLACEHOLDER_PAGEID, new Long(pageid).toString());
}
return result;
}
public String renderErrorWithTemplate(String errorMessage, String macroType, Map<String,Object> additionalParams) {
Map<String,Object> context = getDefaultVelocityContext();
if (additionalParams != null) { context.putAll(additionalParams); }
context.put( "errormessage", errorMessage);
context.put( "macrotype", macroType);
return renderTemplate("templates/genericerror.vm", context);
}
public String i18n_getText(String key) {
String result = key;
result = i18n.getText(key);
return result;
}
public String i18n_getText(String key, String[] params) {
String result = key;
result = i18n.getText(key, params);
return result;
}
protected AdvancedCodeBlockCache getFromCache(String innerKeyCacheName) {
if (innerKeyCacheName != null && ! innerKeyCacheName.isEmpty()) {
Cache cache = cacheManager.getCache(MACRO_CACHE_KEY);
AdvancedCodeBlockCache cacheObj = null;
try {
cacheObj = (AdvancedCodeBlockCache) cache.get(innerKeyCacheName);
} catch (Exception e) {
// If Object of old version with different footprint is already there, discard the object.
log.info("Cache gets renewed because of ClassCastException."+e.getMessage());
cacheObj = null;
}
if (cacheObj != null) {
return cacheObj;
} else {
log.debug("cache is null!");
}
log.debug("Recreating cache.");
}
return null;
}
protected void putToCache(List<Map<String,String>> codeblocks, String innerKeyCacheName) {
if (innerKeyCacheName != null && ! innerKeyCacheName.isEmpty()) {
Cache cache = cacheManager.getCache(MACRO_CACHE_KEY);
Date lastCacheUpdate = newDate();
AdvancedCodeBlockCache cacheObj = new AdvancedCodeBlockCache();
cacheObj.setCodeblocks(codeblocks);
cacheObj.setCreated(lastCacheUpdate);
cache.put(innerKeyCacheName, cacheObj);
}
}
/* mockable */
protected Date newDate() {
return new Date();
}
}