/**
* Copyright (c) 2014, the Railo Company Ltd.
* Copyright (c) 2015, Lucee Assosication Switzerland
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
*/
package lucee.runtime.extension;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.Serializable;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.jar.Attributes;
import java.util.jar.Manifest;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import lucee.Info;
import lucee.print;
import lucee.commons.digest.HashUtil;
import lucee.commons.io.IOUtil;
import lucee.commons.io.SystemUtil;
import lucee.commons.io.log.Log;
import lucee.commons.io.res.Resource;
import lucee.commons.io.res.util.ResourceUtil;
import lucee.commons.lang.ExceptionUtil;
import lucee.commons.lang.StringUtil;
import lucee.loader.util.Util;
import lucee.runtime.config.Config;
import lucee.runtime.config.ConfigImpl;
import lucee.runtime.config.ConfigWeb;
import lucee.runtime.config.ConfigWebUtil;
import lucee.runtime.config.Constants;
import lucee.runtime.config.DeployHandler;
import lucee.runtime.config.XMLConfigAdmin;
import lucee.runtime.db.ClassDefinition;
import lucee.runtime.engine.ThreadLocalConfig;
import lucee.runtime.engine.ThreadLocalPageContext;
import lucee.runtime.exp.ApplicationException;
import lucee.runtime.exp.DatabaseException;
import lucee.runtime.exp.PageException;
import lucee.runtime.exp.PageRuntimeException;
import lucee.runtime.functions.conversion.DeserializeJSON;
import lucee.runtime.op.Caster;
import lucee.runtime.op.Decision;
import lucee.runtime.osgi.BundleFile;
import lucee.runtime.osgi.BundleInfo;
import lucee.runtime.osgi.OSGiUtil;
import lucee.runtime.osgi.OSGiUtil.BundleDefinition;
import lucee.runtime.type.Collection.Key;
import lucee.runtime.type.KeyImpl;
import lucee.runtime.type.Query;
import lucee.runtime.type.QueryImpl;
import lucee.runtime.type.util.ArrayUtil;
import lucee.runtime.type.util.KeyConstants;
import lucee.runtime.type.util.ListUtil;
import org.osgi.framework.BundleException;
import org.osgi.framework.Version;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
/**
* Extension completely handled by the engine and not by the Install/config.xml
*/
public class RHExtension implements Serializable {
private static final long serialVersionUID = 2904020095330689714L;
//public static final Key JARS = KeyImpl.init("jars");
private static final Key BUNDLES = KeyImpl.init("bundles");
private static final Key TLDS = KeyImpl.init("tlds");
private static final Key FLDS = KeyImpl.init("flds");
private static final Key EVENT_GATEWAYS = KeyImpl.init("eventGateways");
private static final Key TAGS = KeyImpl.init("tags");
private static final Key FUNCTIONS = KeyConstants._functions;
private static final Key ARCHIVES = KeyImpl.init("archives");
private static final Key CONTEXTS = KeyImpl.init("contexts");
private static final Key WEBCONTEXTS = KeyImpl.init("webcontexts");
private static final Key CONFIG = KeyImpl.init("config");
private static final Key COMPONENTS = KeyImpl.init("components");
private static final Key APPLICATIONS = KeyImpl.init("applications");
private static final Key CATEGORIES = KeyImpl.init("categories");
private static final Key PLUGINS = KeyImpl.init("plugins");
private static final Key START_BUNDLES = KeyImpl.init("startBundles");
private static final Key TRIAL = KeyImpl.init("trial");
private static final Key RELEASE_TYPE = KeyImpl.init("releaseType");
private static final String[] EMPTY = new String[0];
private static final BundleDefinition[] EMPTY_BD = new BundleDefinition[0];
public static final int RELEASE_TYPE_ALL=0;
public static final int RELEASE_TYPE_SERVER=1;
public static final int RELEASE_TYPE_WEB=2;
private String id;
private int releaseType;
private String version;
private String name;
private String description;
private boolean trial;
private String image;
private boolean startBundles;
private BundleInfo[] bundles;
private String[] jars;
private String[] flds;
private String[] tlds;
private String[] tags;
private String[] functions;
private String[] archives;
private String[] applications;
private String[] components;
private String[] plugins;
private String[] contexts;
private String[] configs;
private String[] webContexts;
private String[] categories;
private String[] gateways;
private List<Map<String, String>> caches;
private List<Map<String, String>> cacheHandlers;
private List<Map<String, String>> orms;
private List<Map<String, String>> monitors;
private List<Map<String, String>> searchs;
private List<Map<String, String>> resources;
private List<Map<String, String>> amfs;
private List<Map<String, String>> jdbcs;
private List<Map<String, String>> mappings;
private Resource extensionFile;
private String type;
private Version minCoreVersion;
private double minLoaderVersion;
private String amfsJson;
private String resourcesJson;
//private Config config;
private String searchsJson;
private String ormsJson;
private String monitorsJson;
private String cachesJson;
private String cacheHandlersJson;
private String jdbcsJson;
private String mappingsJson;
private boolean loaded;
private final Config config;
public final boolean softLoaded;
public RHExtension(Config config,Element el) throws PageException, IOException, BundleException {
this.config=config;
// we have a newer version that holds the Manifest data
if(el.hasAttribute("start-bundles")) {
this.extensionFile=toResource(config,el);
boolean _softLoaded;
try{
readManifestConfig(el, extensionFile.getAbsolutePath(), null);
_softLoaded=true;
}
catch(ApplicationException ae) {
ae.printStackTrace();
init(toResource(config,el),false);
_softLoaded=false;
}
softLoaded=_softLoaded;
}
else {
init(toResource(config,el),false);
softLoaded=false;
}
}
public RHExtension(Config config, Resource ext, boolean moveIfNecessary) throws PageException, IOException, BundleException {
this.config=config;
init( ext, moveIfNecessary);
softLoaded=false;
}
private void init(Resource ext, boolean moveIfNecessary) throws PageException, IOException, BundleException {
// make sure the config is registerd with the thread
if(ThreadLocalPageContext.getConfig()==null) ThreadLocalConfig.register(config);
// is it a web or server context?
type=config instanceof ConfigWeb?"web":"server";
load(ext);
this.extensionFile=ext;
if(moveIfNecessary)move(ext);
}
// copy the file to extension dir if it is not already there
private void move(Resource ext) throws PageException {
Resource trg;
Resource trgDir;
try {
trg = getExtensionFile(config, ext,id,name,version);
trgDir = trg.getParentResource();
trgDir.mkdirs();
if(!ext.getParentResource().equals(trgDir)) {
if(trg.exists()) trg.delete();
ResourceUtil.moveTo(ext, trg,true);
this.extensionFile=trg;
}
}
catch(Exception e){
throw Caster.toPageException(e);
}
}
public static Manifest getManifestFromFile(Config config,Resource file) throws IOException, BundleException, ApplicationException {
ZipInputStream zis = new ZipInputStream( IOUtil.toBufferedInputStream(file.getInputStream()) ) ;
ZipEntry entry;
Manifest manifest = null;
try {
while ( ( entry = zis.getNextEntry()) != null ) {
if(!entry.isDirectory() && entry.getName().equalsIgnoreCase("META-INF/MANIFEST.MF")) {
manifest = toManifest(config,zis,null);
}
zis.closeEntry() ;
if(manifest!=null) return manifest;
}
}
finally {
IOUtil.closeEL(zis);
}
return null;
}
private void load(Resource ext) throws IOException, BundleException, ApplicationException {
//print.ds(ext.getAbsolutePath());
loaded=true;
// no we read the content of the zip
ZipInputStream zis = new ZipInputStream( IOUtil.toBufferedInputStream(ext.getInputStream()) ) ;
ZipEntry entry;
Manifest manifest = null;
String _img=null;
String path;
String fileName,sub;
List<BundleInfo> bundles=new ArrayList<BundleInfo>();
List<String> jars=new ArrayList<String>();
List<String> flds=new ArrayList<String>();
List<String> tlds=new ArrayList<String>();
List<String> tags=new ArrayList<String>();
List<String> functions=new ArrayList<String>();
List<String> contexts=new ArrayList<String>();
List<String> configs=new ArrayList<String>();
List<String> webContexts=new ArrayList<String>();
List<String> applications=new ArrayList<String>();
List<String> components=new ArrayList<String>();
List<String> plugins=new ArrayList<String>();
List<String> gateways=new ArrayList<String>();
List<String> archives=new ArrayList<String>();
try {
while ( ( entry = zis.getNextEntry()) != null ) {
path=entry.getName();
fileName=fileName(entry);
sub=subFolder(entry);
if(!entry.isDirectory() && path.equalsIgnoreCase("META-INF/MANIFEST.MF")) {
manifest = toManifest(config,zis,null);
}
else if(!entry.isDirectory() && path.equalsIgnoreCase("META-INF/logo.png")) {
_img = toBase64(zis,null);
}
// jars
else if(!entry.isDirectory() &&
(startsWith(path,type,"jars") || startsWith(path,type,"jar")
|| startsWith(path,type,"bundles") || startsWith(path,type,"bundle")
|| startsWith(path,type,"lib") || startsWith(path,type,"libs")) && StringUtil.endsWithIgnoreCase(path, ".jar")) {
//print.e("xxxxxx-------- "+fileName+" -------xxxxxx");
jars.add(fileName);
BundleInfo bi = BundleInfo.getInstance(fileName,zis, false);
if(bi.isBundle()) bundles.add(bi);
}
// flds
else if(!entry.isDirectory() && startsWith(path,type,"flds") && (StringUtil.endsWithIgnoreCase(path, ".fld") || StringUtil.endsWithIgnoreCase(path, ".fldx")))
flds.add(fileName);
// tlds
else if(!entry.isDirectory() && startsWith(path,type,"tlds") && (StringUtil.endsWithIgnoreCase(path, ".tld") || StringUtil.endsWithIgnoreCase(path, ".tldx")))
tlds.add(fileName);
// archives
else if(!entry.isDirectory() && (startsWith(path,type,"archives") || startsWith(path,type,"mappings")) && StringUtil.endsWithIgnoreCase(path, ".lar"))
archives.add(fileName);
// event-gateway
else if(!entry.isDirectory() &&
(startsWith(path,type,"event-gateways") || startsWith(path,type,"eventGateways")) &&
(
StringUtil.endsWithIgnoreCase(path, "."+Constants.getCFMLComponentExtension()) ||
StringUtil.endsWithIgnoreCase(path, "."+Constants.getLuceeComponentExtension())
)
)
gateways.add(sub);
// tags
else if(!entry.isDirectory() && startsWith(path,type,"tags"))
tags.add(sub);
// functions
else if(!entry.isDirectory() && startsWith(path,type,"functions"))
functions.add(sub);
// context
else if(!entry.isDirectory() && startsWith(path,type,"context") && !StringUtil.startsWith(fileName(entry), '.'))
contexts.add(sub);
// config
else if(!entry.isDirectory() && startsWith(path,type,"config") && !StringUtil.startsWith(fileName(entry), '.'))
configs.add(sub);
// web contextS
else if(!entry.isDirectory() && startsWith(path,type,"webcontexts") && !StringUtil.startsWith(fileName(entry), '.'))
webContexts.add(sub);
// applications
else if(!entry.isDirectory() && (startsWith(path,type,"applications")) && !StringUtil.startsWith(fileName(entry), '.'))
applications.add(sub);
else if(!entry.isDirectory() && (startsWith(path,type,"web")) && !StringUtil.startsWith(fileName(entry), '.'))
applications.add(sub);
// components
else if(!entry.isDirectory() && (startsWith(path,type,"components")) && !StringUtil.startsWith(fileName(entry), '.'))
components.add(sub);
// plugins
else if(!entry.isDirectory() && (startsWith(path,type,"plugins")) && !StringUtil.startsWith(fileName(entry), '.'))
plugins.add(sub);
zis.closeEntry() ;
}
}
finally {
IOUtil.closeEL(zis);
}
// read the manifest
if(manifest==null)
throw new ApplicationException("The Extension ["+ext+"] is invalid,no Manifest file was found at [META-INF/MANIFEST.MF].");
readManifestConfig(manifest,ext.getAbsolutePath(),_img);
this.jars=jars.toArray(new String[jars.size()]);
this.flds=flds.toArray(new String[flds.size()]);
this.tlds=tlds.toArray(new String[tlds.size()]);
this.tags=tags.toArray(new String[tags.size()]);
this.gateways=gateways.toArray(new String[gateways.size()]);
this.functions=functions.toArray(new String[functions.size()]);
this.archives=archives.toArray(new String[archives.size()]);
this.contexts=contexts.toArray(new String[contexts.size()]);
this.configs=configs.toArray(new String[configs.size()]);
this.webContexts=webContexts.toArray(new String[webContexts.size()]);
this.applications=applications.toArray(new String[applications.size()]);
this.components=components.toArray(new String[components.size()]);
this.plugins=plugins.toArray(new String[plugins.size()]);
this.bundles=bundles.toArray(new BundleInfo[bundles.size()]);
}
private void readManifestConfig(Manifest manifest, String label, String _img) throws ApplicationException {
boolean isWeb=config instanceof ConfigWeb;
type=isWeb?"web":"server";
Log logger = ((ConfigImpl)config).getLog("deploy");
Info info = ConfigWebUtil.getEngine(config).getInfo();
Attributes attr = manifest.getMainAttributes();
readName(label,StringUtil.unwrap(attr.getValue("name")));
label=name;
readVersion(label, StringUtil.unwrap(attr.getValue("version")));
label+=" : "+version;
readId(label, StringUtil.unwrap(attr.getValue("id")));
readReleaseType(label,StringUtil.unwrap(attr.getValue("release-type")),isWeb);
description=StringUtil.unwrap(attr.getValue("description"));
trial=Caster.toBooleanValue(StringUtil.unwrap(attr.getValue("trial")),false);
if(_img==null)_img=StringUtil.unwrap(attr.getValue("image"));
image=_img;
String cat=StringUtil.unwrap(attr.getValue("category"));
if(StringUtil.isEmpty(cat,true))cat=StringUtil.unwrap(attr.getValue("categories"));
readCategories(label, cat);
readCoreVersion(label,StringUtil.unwrap(attr.getValue("lucee-core-version")),info);
readLoaderVersion(label,StringUtil.unwrap(attr.getValue("lucee-loader-version")));
startBundles=Caster.toBooleanValue(StringUtil.unwrap(attr.getValue("start-bundles")),true);
readAMF(label,StringUtil.unwrap(attr.getValue("amf")),logger);
readResource(label,StringUtil.unwrap(attr.getValue("resource")),logger);
readSearch(label,StringUtil.unwrap(attr.getValue("search")),logger);
readORM(label,StringUtil.unwrap(attr.getValue("orm")),logger);
readMonitor(label,StringUtil.unwrap(attr.getValue("monitor")),logger);
readCache(label,StringUtil.unwrap(attr.getValue("cache")),logger);
readCacheHandler(label,StringUtil.unwrap(attr.getValue("cache-handler")),logger);
readJDBC(label,StringUtil.unwrap(attr.getValue("jdbc")),logger);
readMapping(label,StringUtil.unwrap(attr.getValue("mapping")),logger);
}
private void readManifestConfig(Element el, String label, String _img) throws ApplicationException {
boolean isWeb=config instanceof ConfigWeb;
type=isWeb?"web":"server";
Log logger = ((ConfigImpl)config).getLog("deploy");
Info info = ConfigWebUtil.getEngine(config).getInfo();
readName(label,el.getAttribute("name"));
label=name;
readVersion(label,el.getAttribute("version"));
label+=" : "+version;
readId(label,el.getAttribute("id"));
readReleaseType(label,el.getAttribute("release-type"),isWeb);
description=el.getAttribute("description");
trial=Caster.toBooleanValue(el.getAttribute("trial"),false);
if(_img==null)_img=el.getAttribute("image");
image=_img;
String cat=el.getAttribute("category");
if(StringUtil.isEmpty(cat,true))cat=el.getAttribute("categories");
readCategories(label,cat);
readCoreVersion(label,el.getAttribute("lucee-core-version"),info);
readLoaderVersion(label,el.getAttribute("lucee-loader-version"));
startBundles=Caster.toBooleanValue(el.getAttribute("start-bundles"),true);
readAMF(label,el.getAttribute("amf"),logger);
readResource(label,el.getAttribute("resource"),logger);
readSearch(label,el.getAttribute("search"),logger);
readORM(label,el.getAttribute("orm"),logger);
readMonitor(label,el.getAttribute("monitor"),logger);
readCache(label,el.getAttribute("cache"),logger);
readCacheHandler(label,el.getAttribute("cache-handler"),logger);
readJDBC(label,el.getAttribute("jdbc"),logger);
readMapping(label,el.getAttribute("mapping"),logger);
}
private void readMapping(String label, String str, Log logger) {
if(!StringUtil.isEmpty(str,true)) {
mappings = toSettings(logger,str);
mappingsJson=str;
}
if(mappings==null) mappings=new ArrayList<Map<String, String>>();
}
private void readJDBC(String label, String str, Log logger) {
if(!StringUtil.isEmpty(str,true)) {
jdbcs = toSettings(logger,str);
jdbcsJson=str;
}
if(jdbcs==null) jdbcs=new ArrayList<Map<String, String>>();
}
private void readCacheHandler(String label, String str, Log logger) {
if(!StringUtil.isEmpty(str,true)) {
cacheHandlers = toSettings(logger,str);
cacheHandlersJson=str;
}
if(cacheHandlers==null) cacheHandlers=new ArrayList<Map<String, String>>();
}
private void readCache(String label, String str, Log logger) {
if(!StringUtil.isEmpty(str,true)) {
caches = toSettings(logger,str);
cachesJson=str;
}
if(caches==null) caches=new ArrayList<Map<String, String>>();
}
private void readMonitor(String label, String str, Log logger) {
if(!StringUtil.isEmpty(str,true)) {
monitors = toSettings(logger,str);
monitorsJson=str;
}
if(monitors==null) monitors=new ArrayList<Map<String, String>>();
}
private void readORM(String label, String str, Log logger) {
if(!StringUtil.isEmpty(str,true)) {
orms = toSettings(logger,str);
ormsJson=str;
}
if(orms==null) orms=new ArrayList<Map<String, String>>();
}
private void readSearch(String label, String str, Log logger) {
if(!StringUtil.isEmpty(str,true)) {
searchs = toSettings(logger,str);
searchsJson=str;
}
if(searchs==null) searchs=new ArrayList<Map<String, String>>();
}
private void readResource(String label, String str, Log logger) {
if(!StringUtil.isEmpty(str,true)) {
resources = toSettings(logger,str);
resourcesJson=str;
}
if(resources==null) resources=new ArrayList<Map<String, String>>();
}
private void readAMF(String label, String str, Log logger) {
if(!StringUtil.isEmpty(str,true)) {
amfs = toSettings(logger,str);
amfsJson = str;
}
if(amfs==null) amfs=new ArrayList<Map<String, String>>();
}
private void readLoaderVersion(String label, String str) throws ApplicationException {
minLoaderVersion = Caster.toDoubleValue(str,0);
if(minLoaderVersion>SystemUtil.getLoaderVersion()) {
throw new ApplicationException("The Extension ["+label+"] cannot be loaded, "+Constants.NAME+" Loader Version must be at least ["+str+"], update the Lucee.jar first.");
}
}
private void readCoreVersion(String label, String str, Info info) throws ApplicationException {
minCoreVersion = OSGiUtil.toVersion(str, null);
if(minCoreVersion!=null && Util.isNewerThan(minCoreVersion,info.getVersion())) {
throw new ApplicationException("The Extension ["+label+"] cannot be loaded, "+Constants.NAME+" Version must be at least ["+minCoreVersion.toString()+"], version is ["+info.getVersion().toString()+"].");
}
}
private void readCategories(String label, String cat) {
if(!StringUtil.isEmpty(cat,true)) {
categories=ListUtil.trimItems(ListUtil.listToStringArray(cat, ","));
}
else categories=null;
}
private void readReleaseType(String label,String str, boolean isWeb) throws ApplicationException {
// release type
int rt=RELEASE_TYPE_ALL;
if(!Util.isEmpty(str)) {
str=str.trim();
if("server".equalsIgnoreCase(str)) rt=RELEASE_TYPE_SERVER;
else if("web".equalsIgnoreCase(str)) rt=RELEASE_TYPE_WEB;
}
if((rt==RELEASE_TYPE_SERVER && isWeb) || (rt==RELEASE_TYPE_WEB && !isWeb)) {
throw new ApplicationException("Cannot install the Extension ["+label+"] in the "+type+" context, this Extension has the release type ["+toReleaseType(rt, "")+"].");
}
releaseType=rt;
}
private void readId(String label, String id) throws ApplicationException {
this.id=StringUtil.unwrap(id);
if(!Decision.isUUId(id)) {
throw new ApplicationException("The Extension ["+label+"] has no valid id defined ("+id+"),id must be a valid UUID.");
}
}
private void readVersion(String label, String version) throws ApplicationException {
this.version=version;
if(StringUtil.isEmpty(version)) {
throw new ApplicationException("cannot deploy extension ["+label+"], this Extension has no version information.");
}
}
private void readName(String label,String str) throws ApplicationException {
str=StringUtil.unwrap(str);
if(StringUtil.isEmpty(str,true)) {
throw new ApplicationException("The Extension ["+label+"] has no name defined, a name is necesary.");
}
name=str.trim();
}
public void deployBundles(Config config) throws IOException, BundleException {
// no we read the content of the zip
ZipInputStream zis = new ZipInputStream( IOUtil.toBufferedInputStream(extensionFile.getInputStream()) ) ;
ZipEntry entry;
String path;
String fileName;
try {
while ( ( entry = zis.getNextEntry()) != null ) {
path=entry.getName();
fileName=fileName(entry);
// jars
if(!entry.isDirectory() &&
(startsWith(path,type,"jars") || startsWith(path,type,"jar")
|| startsWith(path,type,"bundles") || startsWith(path,type,"bundle")
|| startsWith(path,type,"lib") || startsWith(path,type,"libs")) && StringUtil.endsWithIgnoreCase(path, ".jar")) {
Object obj = XMLConfigAdmin.installBundle(config,zis,fileName,version,false,false);
// jar is not a bundle, only a regular jar
if(!(obj instanceof BundleFile)) {
Resource tmp=(Resource)obj;
Resource tmpJar=tmp.getParentResource().getRealResource(ListUtil.last(path, "\\/"));
tmp.moveTo(tmpJar);
XMLConfigAdmin.updateJar(config, tmpJar, false);
}
}
zis.closeEntry() ;
}
}
finally {
IOUtil.closeEL(zis);
}
}
public static Resource toResource(Config config, Element el) throws ApplicationException {
String fileName = el.getAttribute("file-name");
if(StringUtil.isEmpty(fileName)) throw new ApplicationException("missing attribute [file-name]");
Resource res=getExtensionDir(config).getRealResource(fileName);
if(!res.exists())
throw new ApplicationException("Extension ["+fileName+"] was not found at ["+res+"]");
return res;
}
public static Resource toResource(Config config, Element el, Resource defaultValue) {
String fileName = el.getAttribute("file-name");
if(StringUtil.isEmpty(fileName)) return defaultValue;
Resource res=getExtensionDir(config).getRealResource(fileName);
if(!res.exists())
return defaultValue;
return res;
}
private static Resource getExtensionFile(Config config, Resource ext, String id,String name, String version) {
String fileName=toHash(id, name, version, ResourceUtil.getExtension(ext, "lex"));
//String fileName=HashUtil.create64BitHashAsString(id+version,Character.MAX_RADIX)+"."+ResourceUtil.getExtension(ext, "lex");
return getExtensionDir(config).getRealResource(fileName);
}
public static String toHash(String id,String name, String version, String ext) {
if(ext==null) ext="lex";
return HashUtil.create64BitHashAsString(id+version,Character.MAX_RADIX)+"."+ext;
}
private static Resource getExtensionDir(Config config) {
return config.getConfigDir().getRealResource("extensions/installed");
}
public static BundleDefinition[] toBundleDefinitions(String strBundles) {
if(StringUtil.isEmpty(strBundles,true)) return EMPTY_BD;
String[] arrStrs = toArray(strBundles);
BundleDefinition[] arrBDs;
if(!ArrayUtil.isEmpty(arrStrs)) {
arrBDs = new BundleDefinition[arrStrs.length];
int index;
for(int i=0;i<arrStrs.length;i++){
index=arrStrs[i].indexOf(':');
if(index==-1) arrBDs[i]=new BundleDefinition(arrStrs[i].trim());
else {
try {
arrBDs[i]=new BundleDefinition(arrStrs[i].substring(0,index).trim(),arrStrs[i].substring(index+1).trim());
} catch (BundleException e) {
throw new PageRuntimeException(e);// should not happen
}
}
}
}
else arrBDs=EMPTY_BD;
return arrBDs;
}
public static void populate(Element el,Manifest manifest) {
Attributes attr = manifest.getMainAttributes();
pop(el,attr,"id",null);
pop(el,attr,"name",null);
pop(el,attr,"version",null);
pop(el,attr,"start-bundles","false");
pop(el,attr,"release-type","all");
pop(el,attr,"description",null);
pop(el,attr,"trial",null);
pop(el,attr,"image",null);
pop(el,attr,"categories",null);
pop(el,attr,"category",null);
pop(el,attr,"lucee-core-version",null);
pop(el,attr,"lucee-loader-version",null);
pop(el,attr,"amf",null);
pop(el,attr,"resource",null);
pop(el,attr,"search",null);
pop(el,attr,"orm",null);
pop(el,attr,"monitor",null);
pop(el,attr,"cache",null);
pop(el,attr,"cache-handler",null);
pop(el,attr,"jdbc",null);
pop(el,attr,"mapping",null);
}
private static void pop(Element el, Attributes attr, String name,String defaultValue) {
String val = StringUtil.unwrap(attr.getValue(name));
if(!StringUtil.isEmpty(val))el.setAttribute(name, val);
else if(defaultValue!=null) el.setAttribute(name, defaultValue);
else el.removeAttribute(name);
}
public void populate(Element el) {
el.setAttribute("file-name", extensionFile.getName());
String id=getId();
String name=getName();
if(StringUtil.isEmpty(name)) name=id;
el.setAttribute("id", id);
el.setAttribute("name", name);
el.setAttribute("version", getVersion());
// newly added
// start bundles (IMPORTANT:this key is used to reconize a newer entry, so do not change)
el.setAttribute("start-bundles", Caster.toString(getStartBundles()));
// release type
el.setAttribute("release-type", toReleaseType(getReleaseType(),"all"));
// Description
if(StringUtil.isEmpty(getDescription()))
el.setAttribute("description", toStringForAttr(getDescription()));
else el.removeAttribute("description");
// Trial
el.setAttribute("trial", Caster.toString(isTrial()));
// Image
if(StringUtil.isEmpty(getImage()))
el.setAttribute("image", toStringForAttr(getImage()));
else el.removeAttribute("image");
// Categories
String[] cats = getCategories();
if(!ArrayUtil.isEmpty(cats)) {
StringBuilder sb=new StringBuilder();
for(String cat:cats) {
if(sb.length()>0) sb.append(',');
sb.append(toStringForAttr(cat).replace(',', ' '));
}
el.setAttribute("categories",sb.toString());
}
else el.removeAttribute("categories");
// core version
if(minCoreVersion!=null)
el.setAttribute("lucee-core-version", toStringForAttr(minCoreVersion.toString()));
else el.removeAttribute("lucee-core-version");
// loader version
if(minLoaderVersion>0)
el.setAttribute("loader-version", Caster.toString(minLoaderVersion));
else el.removeAttribute("loader-version");
// amf
if(!StringUtil.isEmpty(amfsJson))
el.setAttribute("amf", toStringForAttr(amfsJson));
else el.removeAttribute("amf");
// resource
if(!StringUtil.isEmpty(resourcesJson))
el.setAttribute("resource", toStringForAttr(resourcesJson));
else el.removeAttribute("resource");
// search
if(!StringUtil.isEmpty(searchsJson))
el.setAttribute("search", toStringForAttr(searchsJson));
else el.removeAttribute("search");
// orm
if(!StringUtil.isEmpty(ormsJson))
el.setAttribute("orm", toStringForAttr(ormsJson));
else el.removeAttribute("orm");
// monitor
if(!StringUtil.isEmpty(monitorsJson))
el.setAttribute("monitor", toStringForAttr(monitorsJson));
else el.removeAttribute("monitor");
// cache
if(!StringUtil.isEmpty(cachesJson))
el.setAttribute("cache", toStringForAttr(cachesJson));
else el.removeAttribute("cache");
// cache-handler
if(!StringUtil.isEmpty(cacheHandlersJson))
el.setAttribute("cache-handler", toStringForAttr(cacheHandlersJson));
else el.removeAttribute("cache-handler");
// jdbc
if(!StringUtil.isEmpty(jdbcsJson))
el.setAttribute("jdbc", toStringForAttr(jdbcsJson));
else el.removeAttribute("jdbc");
// mapping
if(!StringUtil.isEmpty(mappingsJson))
el.setAttribute("mapping", toStringForAttr(mappingsJson));
else el.removeAttribute("mapping");
}
private String toStringForAttr(String str) {
if(str==null) return "";
return str;
}
private static String[] toArray(String str) {
if(StringUtil.isEmpty(str,true)) return new String[0];
return ListUtil.listToStringArray(str.trim(), ',');
}
public static Query toQuery(Config config,RHExtension[] children) throws PageException {
Log log = config.getLog("deploy");
Query qry = createQuery();
for(int i=0;i<children.length;i++) {
try{
children[i].populate(qry); // ,i+1
}
catch(Throwable t){
ExceptionUtil.rethrowIfNecessary(t);
log.error("extension", t);
}
}
return qry;
}
public static Query toQuery(Config config,Element[] children) throws PageException {
Log log = config.getLog("deploy");
Query qry = createQuery();
for(int i=0;i<children.length;i++) {
try{
new RHExtension(config,children[i]).populate(qry); // ,i+1
}
catch(Throwable t){
ExceptionUtil.rethrowIfNecessary(t);
log.error("extension", t);
}
}
return qry;
}
private static Query createQuery() throws DatabaseException {
return new QueryImpl(new Key[]{
KeyConstants._id
,KeyConstants._version
,KeyConstants._name
,KeyConstants._description
,KeyConstants._image
,RELEASE_TYPE
,TRIAL
,CATEGORIES
,START_BUNDLES
,BUNDLES
,FLDS
,TLDS
,TAGS
,FUNCTIONS
,CONTEXTS
,WEBCONTEXTS
,CONFIG
,APPLICATIONS
,COMPONENTS
,PLUGINS
,EVENT_GATEWAYS
,ARCHIVES
}, 0, "Extensions");
}
private void populate(Query qry) throws PageException, IOException, BundleException {
int row=qry.addRow();
qry.setAt(KeyConstants._id, row, getId());
qry.setAt(KeyConstants._name, row, name);
qry.setAt(KeyConstants._image, row, getImage());
qry.setAt(KeyConstants._description, row, description);
qry.setAt(KeyConstants._version, row, getVersion()==null?null:getVersion().toString());
qry.setAt(TRIAL, row, isTrial());
qry.setAt(RELEASE_TYPE, row, toReleaseType(getReleaseType(),"all"));
//qry.setAt(JARS, row,Caster.toArray(getJars()));
qry.setAt(FLDS, row, Caster.toArray(getFlds()));
qry.setAt(TLDS, row, Caster.toArray(getTlds()));
qry.setAt(FUNCTIONS, row, Caster.toArray(getFunctions()));
qry.setAt(ARCHIVES, row, Caster.toArray(getArchives()));
qry.setAt(TAGS, row, Caster.toArray(getTags()));
qry.setAt(CONTEXTS, row, Caster.toArray(getContexts()));
qry.setAt(WEBCONTEXTS, row, Caster.toArray(getWebContexts()));
qry.setAt(CONFIG, row, Caster.toArray(getConfigs()));
qry.setAt(EVENT_GATEWAYS, row, Caster.toArray(getEventGateways()));
qry.setAt(CATEGORIES, row, Caster.toArray(getCategories()));
qry.setAt(APPLICATIONS, row, Caster.toArray(getApplications()));
qry.setAt(COMPONENTS, row, Caster.toArray(getComponents()));
qry.setAt(PLUGINS, row, Caster.toArray(getPlugins()));
qry.setAt(START_BUNDLES, row, Caster.toBoolean(getStartBundles()));
BundleInfo[] bfs = getBundles();
Query qryBundles=new QueryImpl(new Key[]{KeyConstants._name,KeyConstants._version}, bfs.length, "bundles");
for(int i=0;i<bfs.length;i++){
qryBundles.setAt(KeyConstants._name, i+1, bfs[i].getSymbolicName());
if(bfs[i].getVersion()!=null)
qryBundles.setAt(KeyConstants._version, i+1, bfs[i].getVersionAsString());
}
qry.setAt(BUNDLES, row,qryBundles);
}
public String getId() {
return id;
}
public String getImage() {
return image;
}
public String getVersion() {
return version;
}
public boolean getStartBundles() {
return startBundles;
}
private static Manifest toManifest(Config config,InputStream is, Manifest defaultValue) {
try {
Charset cs = config.getResourceCharset();
String str = IOUtil.toString(is,cs);
if(StringUtil.isEmpty(str,true)) return defaultValue;
str=str.trim()+"\n";
return new Manifest(new ByteArrayInputStream(str.getBytes(cs)));
}
catch(Throwable t) {
ExceptionUtil.rethrowIfNecessary(t);
return defaultValue;
}
}
private static String toBase64(InputStream is, String defaultValue) {
try {
byte[] bytes = IOUtil.toBytes(is);
if(ArrayUtil.isEmpty(bytes)) return defaultValue;
return Caster.toB64(bytes,defaultValue);
}
catch(Throwable t) {
ExceptionUtil.rethrowIfNecessary(t);
return defaultValue;
}
}
public static ClassDefinition<?> toClassDefinition(Config config, Map<String, String> map) {
String _class=map.get("class");
String _name=map.get("bundle-name");
if(StringUtil.isEmpty(_name)) _name=map.get("bundleName");
if(StringUtil.isEmpty(_name)) _name=map.get("bundlename");
if(StringUtil.isEmpty(_name)) _name=map.get("name");
String _version=map.get("bundle-version");
if(StringUtil.isEmpty(_version)) _version=map.get("bundleVersion");
if(StringUtil.isEmpty(_version)) _version=map.get("bundleversion");
if(StringUtil.isEmpty(_version)) _version=map.get("version");
return new lucee.transformer.library.ClassDefinitionImpl(
_class
,_name
,_version
,config.getIdentification());
}
private static List<Map<String,String>> toSettings(Log log, String str) {
try {
Object res = DeserializeJSON.call(null, str);
// only a single row
if(!Decision.isArray(res) && Decision.isStruct(res)) {
List<Map<String,String>> list = new ArrayList<>();
_toSetting(list, Caster.toMap(res));
return list;
}
// multiple rows
if(Decision.isArray(res)) {
List tmpList=Caster.toList(res);
Iterator it = tmpList.iterator();
List<Map<String,String>> list=new ArrayList<>();
while(it.hasNext()) {
_toSetting(list,Caster.toMap(it.next()));
}
return list;
}
}
catch(Throwable t) {
ExceptionUtil.rethrowIfNecessary(t);
log.error("Extension Installation", t);
}
return null;
}
private static void _toSetting(List<Map<String, String>> list, Map src) throws PageException {
Entry e;
Iterator<Entry> it = src.entrySet().iterator();
Map<String,String> map=new HashMap<String,String>();
while(it.hasNext()){
e = it.next();
map.put(Caster.toString(e.getKey()), Caster.toString(e.getValue()));
}
list.add(map);
}
private static boolean startsWith(String path,String type, String name) {
return StringUtil.startsWithIgnoreCase(path, name+"/") || StringUtil.startsWithIgnoreCase(path, type+"/"+name+"/");
}
private static String fileName(ZipEntry entry) {
String name = entry.getName();
int index=name.lastIndexOf('/');
if(index==-1) return name;
return name.substring(index+1);
}
private static String subFolder(ZipEntry entry) {
String name = entry.getName();
int index=name.indexOf('/');
if(index==-1) return name;
return name.substring(index+1);
}
private static BundleDefinition toBundleDefinition(InputStream is, String name,String extensionVersion,boolean closeStream) throws IOException, BundleException, ApplicationException {
Resource tmp=SystemUtil.getTempDirectory().getRealResource(name);
try{
IOUtil.copy(is, tmp,closeStream);
BundleFile bf = new BundleFile(tmp);
if(bf.isBundle()) throw new ApplicationException("Jar ["+name+"] is not a valid OSGi Bundle");
return new BundleDefinition(bf.getSymbolicName(), bf.getVersion());
}
finally {
tmp.delete();
}
}
public String getName() {
return name;
}
public boolean isTrial() {
return trial;
}
public String getDescription() {
return description;
}
public int getReleaseType() {
return releaseType;
}
public BundleInfo[] getBundles() throws ApplicationException, IOException, BundleException {
if(!loaded)load(extensionFile);
return bundles;
}
public String[] getFlds() throws ApplicationException, IOException, BundleException {
if(!loaded)load(extensionFile);
return flds==null?EMPTY:flds;
}
public String[] getJars() throws ApplicationException, IOException, BundleException {
if(!loaded)load(extensionFile);
return jars==null?EMPTY:jars;
}
public String[] getTlds() throws ApplicationException, IOException, BundleException {
if(!loaded)load(extensionFile);
return tlds==null?EMPTY:tlds;
}
public String[] getFunctions() throws ApplicationException, IOException, BundleException {
if(!loaded)load(extensionFile);
return functions==null?EMPTY:functions;
}
public String[] getArchives() throws ApplicationException, IOException, BundleException {
if(!loaded)load(extensionFile);
return archives==null?EMPTY:archives;
}
public String[] getTags() throws ApplicationException, IOException, BundleException {
if(!loaded)load(extensionFile);
return tags==null?EMPTY:tags;
}
public String[] getEventGateways() throws ApplicationException, IOException, BundleException {
if(!loaded)load(extensionFile);
return gateways==null?EMPTY:gateways;
}
public String[] getApplications() throws ApplicationException, IOException, BundleException {
if(!loaded)load(extensionFile);
return applications==null?EMPTY:applications;
}
public String[] getComponents() throws ApplicationException, IOException, BundleException {
if(!loaded)load(extensionFile);
return components==null?EMPTY:components;
}
public String[] getPlugins() throws ApplicationException, IOException, BundleException {
if(!loaded)load(extensionFile);
return plugins==null?EMPTY:plugins;
}
public String[] getContexts() throws ApplicationException, IOException, BundleException {
if(!loaded)load(extensionFile);
return contexts==null?EMPTY:contexts;
}
public String[] getConfigs() throws ApplicationException, IOException, BundleException {
if(!loaded)load(extensionFile);
return configs==null?EMPTY:configs;
}
public String[] getWebContexts() throws ApplicationException, IOException, BundleException {
if(!loaded)load(extensionFile);
return webContexts==null?EMPTY:webContexts;
}
public String[] getCategories() {
return categories==null?EMPTY:categories;
}
public List<Map<String, String>> getCaches() {
return caches;
}
public List<Map<String, String>> getCacheHandlers() {
return cacheHandlers;
}
public List<Map<String, String>> getOrms() {
return orms;
}
public List<Map<String, String>> getMonitors() {
return monitors;
}
public List<Map<String, String>> getSearchs() {
return searchs;
}
public List<Map<String, String>> getResources() {
return resources;
}
public List<Map<String, String>> getAMFs() {
return amfs;
}
public List<Map<String, String>> getJdbcs() {
return jdbcs;
}
public List<Map<String, String>> getMappings() {
return mappings;
}
public Resource getExtensionFile() {
if(!extensionFile.exists()) {
Config c = ThreadLocalPageContext.getConfig();
if(c!=null) {
Resource res = DeployHandler.getExtension(c,new ExtensionDefintion( id, version), null);
if(res!=null && res.exists()) {
try {
IOUtil.copy(res, extensionFile);
}
catch (IOException e) {
res.delete();
}
}
}
}
return extensionFile;
}
@Override
public boolean equals(Object objOther) {
if(objOther == this) return true;
if(objOther instanceof RHExtension) {
RHExtension other=(RHExtension) objOther;
if(!getId().equals(other.getId())) return false;
if(!getName().equals(other.getName())) return false;
if(!getVersion().equals(other.getVersion())) return false;
if(isTrial()!=other.isTrial()) return false;
return true;
}
if(objOther instanceof ExtensionDefintion) {
ExtensionDefintion ed=(ExtensionDefintion) objOther;
if(!ed.getId().equalsIgnoreCase(getId())) return false;
if(ed.getVersion()==null || getVersion()==null) return true;
return ed.getVersion().equalsIgnoreCase(getVersion());
}
return false;
}
public static String toReleaseType(int releaseType, String defaultValue) {
if(releaseType==RELEASE_TYPE_WEB) return "web";
if(releaseType==RELEASE_TYPE_SERVER) return "server";
if(releaseType==RELEASE_TYPE_ALL) return "all";
return defaultValue;
}
public static int toReleaseType(String releaseType, int defaultValue) {
if("web".equalsIgnoreCase(releaseType)) return RELEASE_TYPE_WEB;
if("server".equalsIgnoreCase(releaseType)) return RELEASE_TYPE_SERVER;
if("all".equalsIgnoreCase(releaseType)) return RELEASE_TYPE_ALL;
if("both".equalsIgnoreCase(releaseType)) return RELEASE_TYPE_ALL;
return defaultValue;
}
public static List<ExtensionDefintion> toExtensionDefinitions(String str) {
// first we split the list
List<ExtensionDefintion> rtn=new ArrayList<ExtensionDefintion>();
if(StringUtil.isEmpty(str)) return rtn;
String[] arr = ListUtil.trimItems(ListUtil.listToStringArray(str, ','));
if(ArrayUtil.isEmpty(arr)) return rtn;
String[] arrr;
int index;
ExtensionDefintion ed;
String s;
for(int i=0;i<arr.length;i++){
s=arr[i];
arrr = ListUtil.trimItems(ListUtil.listToStringArray(s, ';'));
ed=new ExtensionDefintion();
for(String ss:arrr){
index=ss.indexOf('=');
if(index!=-1) {
ed.setParam(ss.substring(0,index).trim(),ss.substring(index+1).trim());
}
else ed.setId(ss);
}
rtn.add(ed);
}
return rtn;
}
public static List<RHExtension> toRHExtensions(List<ExtensionDefintion> eds) throws PageException {
try {
List<RHExtension> rtn=new ArrayList<RHExtension>();
Iterator<ExtensionDefintion> it = eds.iterator();
while(it.hasNext()) {
rtn.add(it.next().toRHExtension());
}
return rtn;
}
catch (Exception e) {
throw Caster.toPageException(e);
}
}
}