Java tutorial
/** * Vosao CMS. Simple CMS for Google App Engine. * * Copyright (C) 2009-2010 Vosao development team. * * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * * email: vosao.dev@gmail.com */ package org.vosao.business.impl.plugin; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; import org.apache.commons.lang.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.dom4j.DocumentException; import org.dom4j.DocumentHelper; import org.dom4j.Element; import org.vosao.business.Business; import org.vosao.common.PluginException; import org.vosao.dao.Dao; import org.vosao.entity.FileEntity; import org.vosao.entity.FolderEntity; import org.vosao.entity.PluginEntity; import org.vosao.entity.PluginResourceEntity; import org.vosao.utils.FolderUtil; import org.vosao.utils.MimeType; import org.vosao.utils.StrUtil; public class PluginLoader { private static final Log logger = LogFactory.getLog(PluginLoader.class); private static final String VOSAO_PLUGIN = "WEB-INF/vosao-plugin.xml"; private static final String RESOURCE_LIST = ".resourceList"; private static final String FILE_LIST = ".fileList"; private Dao dao; private Business business; public PluginLoader(Dao dao, Business business) { super(); this.dao = dao; this.business = business; } private static class WarItem { public String path; public String filename; public ByteArrayOutputStream data; public WarItem(String path, ByteArrayOutputStream data) { super(); this.path = path; this.filename = FolderUtil.getFileName(path); this.data = data; } } /** * Plugin installation: * - PluginEntity created * - All resources are placed to /plugins/PLUGIN_NAME/ * - all classes are placed to PluginResourceEntity */ public void install(String filename, byte[] data) throws IOException, PluginException, DocumentException { Map<String, WarItem> war = readWar(data); if (!war.containsKey(VOSAO_PLUGIN)) { throw new PluginException(VOSAO_PLUGIN + " not found"); } PluginEntity plugin = readPluginConfig(war.get(VOSAO_PLUGIN)); if (StringUtils.isEmpty(plugin.getEntryPointClass())) { throw new PluginException("Entry point class not defined."); } PluginEntity p = getDao().getPluginDao().getByName(plugin.getName()); if (p != null) { plugin.setConfigData(p.getConfigData()); uninstall(p); } getDao().getPluginDao().save(plugin); String pluginBase = "/plugins/" + plugin.getName(); getBusiness().getFolderBusiness().createFolder(pluginBase); List<String> resourceList = new ArrayList<String>(); List<String> fileCacheList = new ArrayList<String>(); String filePrefix = pluginBase + "/"; for (String url : war.keySet()) { if (!url.equals(VOSAO_PLUGIN)) { WarItem item = war.get(url); byte[] fileData = item.data.toByteArray(); if (url.startsWith("WEB-INF/classes")) { resourceList.add(loadClasspathResource(item, plugin)); } if (!url.startsWith("WEB-INF")) { String filePath = filePrefix + item.path; fileCacheList.add(filePath); String folderPath = pluginBase + "/" + FolderUtil.getFilePath(item.path); FolderEntity folder = getBusiness().getFolderBusiness().createFolder(folderPath); FileEntity file = new FileEntity(item.filename, item.filename, folder.getId(), MimeType.getContentTypeByExt(FolderUtil.getFileExt(item.path)), new Date(), fileData.length); getDao().getFileDao().save(file, fileData); getBusiness().getSystemService().getFileCache().remove(filePath); } if (url.startsWith("WEB-INF/lib") && url.endsWith(".jar")) { resourceList.addAll(loadJarFile(item, plugin)); } } } saveResourceList(plugin, resourceList, fileCacheList); } private String loadClasspathResource(WarItem item, PluginEntity plugin) { String ext = FolderUtil.getFileExt(item.path); byte[] fileData = item.data.toByteArray(); String resourceName = item.path.replace("WEB-INF/classes/", ""); if (ext.equals("class")) { resourceName = resourceName.replace('/', '.').replace(".class", ""); } PluginResourceEntity res = getDao().getPluginResourceDao().getByUrl(plugin.getName(), resourceName); if (res == null) { res = new PluginResourceEntity(plugin.getName(), resourceName, fileData); } else { res.setContent(fileData); } getDao().getPluginResourceDao().save(res); getBusiness().getPluginResourceBusiness().updateResourceCache(res); return res.getId().toString(); } private List<String> loadJarFile(WarItem file, PluginEntity plugin) throws IOException { List<String> result = new ArrayList<String>(); Map<String, WarItem> war = readWar(file.data.toByteArray()); for (String path : war.keySet()) { result.add(loadClasspathResource(war.get(path), plugin)); } return result; } private void saveResourceList(PluginEntity plugin, List<String> resourceList, List<String> fileList) { String resourceListStr = StrUtil.toCSV(resourceList); String fileListStr = StrUtil.toCSV(fileList); try { getDao().getPluginResourceDao().save(new PluginResourceEntity(plugin.getName(), plugin.getName() + RESOURCE_LIST, resourceListStr.getBytes("UTF-8"))); getDao().getPluginResourceDao().save(new PluginResourceEntity(plugin.getName(), plugin.getName() + FILE_LIST, fileListStr.getBytes("UTF-8"))); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } } private Map<String, WarItem> readWar(byte[] data) throws IOException { ByteArrayInputStream inputData = new ByteArrayInputStream(data); ZipInputStream in = new ZipInputStream(inputData); Map<String, WarItem> map = new HashMap<String, WarItem>(); ZipEntry entry; byte[] buffer = new byte[4096]; while ((entry = in.getNextEntry()) != null) { if (!entry.isDirectory()) { ByteArrayOutputStream itemData = new ByteArrayOutputStream(); int len = 0; while ((len = in.read(buffer)) > 0) { itemData.write(buffer, 0, len); } WarItem item = new WarItem(entry.getName(), itemData); map.put(entry.getName(), item); } } in.close(); return map; } private PluginEntity readPluginConfig(WarItem zipItem) throws UnsupportedEncodingException, DocumentException { PluginEntity result = new PluginEntity(); Element root = DocumentHelper.parseText(zipItem.data.toString("UTF-8")).getRootElement(); result.setName(root.elementText("name")); result.setTitle(root.elementText("title")); result.setVersion(root.elementText("version")); result.setDescription(root.elementText("description")); result.setWebsite(root.elementText("website")); if (root.element("entry-point-class") != null) { result.setEntryPointClass(StringUtils.strip(root.elementText("entry-point-class"))); } if (root.element("plugin-config-url") != null) { result.setConfigURL(StringUtils.strip(root.elementText("plugin-config-url"))); } StringBuffer header = new StringBuffer(); if (root.element("header-javascript") != null) { for (Element e : (List<Element>) root.elements("header-javascript")) { header.append("<script type=\"text/javascript\" src=\"/file/plugins/").append(result.getName()) .append("/").append(e.getText()).append("\"></script>\n"); } } if (root.element("header-css") != null) { for (Element e : (List<Element>) root.elements("header-css")) { header.append("<link rel=\"stylesheet\" type=\"text/css\" href=\"/file/plugins/") .append(result.getName()).append("/").append(e.getText()).append("\"/>\n"); } } result.setPageHeader(header.toString()); if (root.element("config") != null) { result.setConfigStructure(root.element("config").asXML()); } return result; } public void uninstall(PluginEntity plugin) { removePluginResources(plugin); removePluginFileCache(plugin); getBusiness().getFolderBusiness().recursiveRemove("/plugins/" + plugin.getName()); getDao().getPluginDao().remove(plugin.getId()); } private void removePluginResources(PluginEntity plugin) { PluginResourceEntity listResource = getDao().getPluginResourceDao().getByUrl(plugin.getName(), plugin.getName() + RESOURCE_LIST); if (listResource == null) { return; } List<Long> ids = new ArrayList<Long>(); ids.add(listResource.getId()); if (listResource.getContent() != null && listResource.getContent().length > 0) { try { String list = new String(listResource.getContent(), "UTF-8"); String[] resources = list.split(","); for (String id : resources) { ids.add(Long.valueOf(id)); } } catch (UnsupportedEncodingException e) { e.printStackTrace(); } } getDao().getPluginResourceDao().remove(ids); } private void removePluginFileCache(PluginEntity plugin) { PluginResourceEntity listResource = getDao().getPluginResourceDao().getByUrl(plugin.getName(), plugin.getName() + FILE_LIST); if (listResource == null) { return; } getDao().getPluginResourceDao().remove(listResource.getId()); try { String list = new String(listResource.getContent(), "UTF-8"); String[] resources = list.split(","); for (String path : resources) { getBusiness().getSystemService().getFileCache().remove(path); } } catch (UnsupportedEncodingException e) { e.printStackTrace(); } } public Dao getDao() { return dao; } public void setDao(Dao dao) { this.dao = dao; } public Business getBusiness() { return business; } public void setBusiness(Business business) { this.business = business; } }