Java tutorial
/** * Copyright (C) 2014 BonitaSoft S.A. * BonitaSoft, 32 rue Gustave Eiffel - 38000 Grenoble * This program 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 2.0 of the License, or * (at your option) any later version. * This program 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 this program. If not, see <http://www.gnu.org/licenses/>. */ package org.bonitasoft.console.common.server.page; import java.io.File; import java.io.IOException; import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Properties; import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; import org.apache.commons.io.FileUtils; import org.bonitasoft.console.common.server.preferences.constants.WebBonitaConstantsUtils; import org.bonitasoft.console.common.server.preferences.properties.CompoundPermissionsMapping; import org.bonitasoft.console.common.server.preferences.properties.ConsoleProperties; import org.bonitasoft.console.common.server.preferences.properties.PropertiesFactory; import org.bonitasoft.console.common.server.preferences.properties.ResourcesPermissionsMapping; import org.bonitasoft.console.common.server.preferences.properties.SimpleProperties; import org.bonitasoft.console.common.server.utils.UnzipUtil; import org.bonitasoft.engine.api.PageAPI; import org.bonitasoft.engine.api.TenantAPIAccessor; import org.bonitasoft.engine.exception.AlreadyExistsException; import org.bonitasoft.engine.exception.BonitaException; import org.bonitasoft.engine.exception.InvalidPageTokenException; import org.bonitasoft.engine.exception.InvalidPageZipInconsistentException; import org.bonitasoft.engine.exception.InvalidPageZipMissingAPropertyException; import org.bonitasoft.engine.exception.InvalidPageZipMissingIndexException; import org.bonitasoft.engine.exception.InvalidPageZipMissingPropertiesException; import org.bonitasoft.engine.page.ContentType; import org.bonitasoft.engine.page.Page; import org.bonitasoft.engine.page.PageNotFoundException; import org.bonitasoft.engine.session.APISession; import org.codehaus.groovy.control.CompilationFailedException; import groovy.lang.GroovyClassLoader; /** * @author Anthony Birembaut, Fabio Lombardi */ public class CustomPageService { public static final String GET_SYSTEM_SESSION = "GET|system/session"; public static final String GET_PORTAL_PROFILE = "GET|portal/profile"; public static final String GET_IDENTITY_USER = "GET|identity/user"; /** * Logger */ private static final Logger LOGGER = Logger.getLogger(CustomPageService.class.getName()); private static final String PAGE_LIB_DIRECTORY = "lib"; public static final String PAGE_CONTROLLER_FILENAME = "Index.groovy"; public static final String PAGE_INDEX_NAME = "index"; public static final String PAGE_INDEX_FILENAME = "index.html"; private static final String LASTUPDATE_FILENAME = ".lastupdate"; private static final Map<String, GroovyClassLoader> PAGES_CLASSLOADERS = new HashMap<>(); public static final String RESOURCES_PROPERTY = "resources"; public static final String PROPERTY_CONTENT_TYPE = "contentType"; public static final String PROPERTY_API_EXTENSIONS = "apiExtensions"; public static final String PROPERTY_METHOD_MASK = "%s.method"; public static final String PROPERTY_PATH_TEMPLATE_MASK = "%s.pathTemplate"; public static final String PROPERTY_PERMISSIONS_MASK = "%s.permissions"; public static final String RESOURCE_PERMISSION_KEY_MASK = "%s|extension/%s"; public static final String RESOURCE_PERMISSION_VALUE = "[%s]"; public static final String EXTENSION_SEPARATOR = ","; public static final String NAME_PROPERTY = "name"; public GroovyClassLoader getPageClassloader(final APISession apiSession, final PageResourceProvider pageResourceProvider) throws IOException, CompilationFailedException, BonitaException { return buildPageClassloader(apiSession, pageResourceProvider.getPageName(), pageResourceProvider.getPageDirectory()); } public void ensurePageFolderIsPresent(final APISession apiSession, final PageResourceProvider pageResourceProvider) throws BonitaException, IOException { if (!pageResourceProvider.getPageDirectory().exists()) { retrievePageZipContent(apiSession, pageResourceProvider); } } public void ensurePageFolderIsUpToDate(final APISession apiSession, final PageResourceProvider pageResourceProvider) throws BonitaException, IOException { final File pageFolder = pageResourceProvider.getPageDirectory(); if (!pageResourceProvider.getPageDirectory().exists()) { retrievePageZipContent(apiSession, pageResourceProvider); } else { final File timestampFile = getPageFile(pageFolder, LASTUPDATE_FILENAME); final long lastUpdateTimestamp = getPageLastUpdateDateFromEngine(apiSession, pageResourceProvider); if (timestampFile.exists()) { final String timestampString = FileUtils.readFileToString(timestampFile); final long timestamp = Long.parseLong(timestampString); if (lastUpdateTimestamp != timestamp) { removePage(apiSession, pageResourceProvider.getPageName()); retrievePageZipContent(apiSession, pageResourceProvider); } } else { FileUtils.writeStringToFile(timestampFile, String.valueOf(lastUpdateTimestamp), false); } } } @SuppressWarnings("unchecked") public Class<PageController> registerPage(final GroovyClassLoader pageClassLoader, final PageResourceProvider pageResourceProvider) throws CompilationFailedException, IOException { final File pageControllerFile = getGroovyPageFile(pageResourceProvider.getPageDirectory()); return pageClassLoader.parseClass(pageControllerFile); } public Class<RestApiController> registerRestApiPage(final GroovyClassLoader pageClassLoader, final PageResourceProvider pageResourceProvider, final String classFileName) throws CompilationFailedException, IOException { final File pageControllerFile = getPageFile(pageResourceProvider.getPageDirectory(), classFileName); return pageClassLoader.parseClass(pageControllerFile); } public void verifyPageClass(final File tempPageDirectory, APISession session) throws IOException, CompilationFailedException { final File pageControllerFile = getPageFile(tempPageDirectory, PAGE_CONTROLLER_FILENAME); if (pageControllerFile.exists()) { final String classloaderName = String.valueOf(System.currentTimeMillis()); final GroovyClassLoader pageClassLoader = buildPageClassloader(session, classloaderName, tempPageDirectory); pageClassLoader.parseClass(pageControllerFile); final GroovyClassLoader classLoader = PAGES_CLASSLOADERS.remove(classloaderName); classLoader.close(); } } public PageController loadPage(final Class<PageController> pageClass) throws InstantiationException, IllegalAccessException { return pageClass.newInstance(); } public RestApiController loadRestApiPage(final Class<RestApiController> restApiControllerClass) throws InstantiationException, IllegalAccessException { return restApiControllerClass.newInstance(); } public void removePage(final APISession apiSession, final String pageName) throws IOException { closeClassloader(pageName); final PageResourceProvider pageResourceProvider = new PageResourceProvider(pageName, apiSession.getTenantId()); removePageZipContent(apiSession, pageResourceProvider); CustomPageDependenciesResolver.removePageLibTempFolder(pageName); } private static void closeClassloader(final String pageName) throws IOException { final GroovyClassLoader classloader = PAGES_CLASSLOADERS.remove(pageName); if (classloader != null) { classloader.clearCache(); classloader.close(); } } protected void retrievePageZipContent(final APISession apiSession, final String pageName) throws BonitaException, IOException { final PageResourceProvider pageResourceProvider = new PageResourceProvider(pageName, apiSession.getTenantId()); retrievePageZipContent(apiSession, pageResourceProvider); } protected GroovyClassLoader buildPageClassloader(final APISession apiSession, final String pageName, final File pageDirectory) throws CompilationFailedException, IOException { GroovyClassLoader pageClassLoader = PAGES_CLASSLOADERS.get(pageName); final BDMClientDependenciesResolver bdmDependenciesResolver = new BDMClientDependenciesResolver(apiSession); if (pageClassLoader == null || getConsoleProperties(apiSession).isPageInDebugMode() || isOutdated(pageClassLoader, bdmDependenciesResolver)) { pageClassLoader = new GroovyClassLoader( getParentClassloader(pageName, new CustomPageDependenciesResolver(pageName, pageDirectory, getWebBonitaConstantsUtils(apiSession)), bdmDependenciesResolver)); pageClassLoader.addClasspath(pageDirectory.getPath()); PAGES_CLASSLOADERS.put(pageName, pageClassLoader); } return pageClassLoader; } private boolean isOutdated(GroovyClassLoader pageClassLoader, BDMClientDependenciesResolver bdmDependenciesResolver) { final ClassLoader parent = pageClassLoader.getParent(); if (!(parent instanceof VersionedClassloader)) { throw new IllegalStateException("Parent classloader should be versioned."); } final VersionedClassloader cachedClassloader = (VersionedClassloader) parent; return !cachedClassloader.hasVersion(bdmDependenciesResolver.getBusinessDataModelVersion()); } protected ConsoleProperties getConsoleProperties(final APISession apiSession) { return PropertiesFactory.getConsoleProperties(apiSession.getTenantId()); } protected WebBonitaConstantsUtils getWebBonitaConstantsUtils(final APISession apiSession) { return WebBonitaConstantsUtils.getInstance(apiSession.getTenantId()); } protected ClassLoader getParentClassloader(final String pageName, final CustomPageDependenciesResolver customPageDependenciesResolver, final BDMClientDependenciesResolver bdmDependenciesResolver) throws IOException { final CustomPageChildFirstClassLoader classLoader = new CustomPageChildFirstClassLoader(pageName, customPageDependenciesResolver, bdmDependenciesResolver, Thread.currentThread().getContextClassLoader()); classLoader.addCustomPageResources(); return classLoader; } protected void addLibsToClassPath(final File customPageLibDirectory, final GroovyClassLoader groovyClassLoader) { final File[] libFiles = customPageLibDirectory.listFiles(); for (int i = 0; i < libFiles.length; i++) { final File libFile = libFiles[i]; groovyClassLoader.addClasspath(libFile.getPath()); } } protected void retrievePageZipContent(final APISession apiSession, final PageResourceProvider pageResourceProvider) throws BonitaException, IOException { final PageAPI pageAPI = getPageAPI(apiSession); // retrieve page zip content from engine and cache it final Page page = pageResourceProvider.getPage(pageAPI); final byte[] pageContent = pageAPI.getPageContent(page.getId()); FileUtils.writeByteArrayToFile(pageResourceProvider.getTempPageFile(), pageContent); UnzipUtil.unzip(pageResourceProvider.getTempPageFile(), pageResourceProvider.getPageDirectory().getPath(), true); final File timestampFile = getPageFile(pageResourceProvider.getPageDirectory(), LASTUPDATE_FILENAME); long lastUpdateTimestamp = 0L; if (page.getLastModificationDate() != null) { lastUpdateTimestamp = page.getLastModificationDate().getTime(); } FileUtils.writeStringToFile(timestampFile, String.valueOf(lastUpdateTimestamp), false); } protected PageAPI getPageAPI(final APISession apiSession) throws BonitaException { return TenantAPIAccessor.getCustomPageAPI(apiSession); } protected void removePageZipContent(final APISession apiSession, final PageResourceProvider pageResourceProvider) throws IOException { FileUtils.deleteDirectory(pageResourceProvider.getPageDirectory()); } public File getGroovyPageFile(final File pageDirectory) { return getPageFile(pageDirectory, PAGE_CONTROLLER_FILENAME); } public File getPageFile(final File pageDirectory, final String fileName) { return new File(pageDirectory, fileName); } protected File getCustomPageLibDirectory(final File pageDirectory) { return getPageFile(pageDirectory, PAGE_LIB_DIRECTORY); } protected long getPageLastUpdateDateFromEngine(final APISession apiSession, final PageResourceProvider pageResourceProvider) throws BonitaException { try { final PageAPI pageAPI = getPageAPI(apiSession); final Date lastUpdateDate = pageResourceProvider.getPage(pageAPI).getLastModificationDate(); if (lastUpdateDate != null) { return lastUpdateDate.getTime(); } } catch (final PageNotFoundException e) { if (LOGGER.isLoggable(Level.FINE)) { LOGGER.log(Level.FINE, "Unable to find the page " + pageResourceProvider); } } return 0L; } public Properties getPageProperties(final APISession apiSession, final byte[] zipContent, final boolean checkIfItAlreadyExists, final Long processDefinitionId) throws InvalidPageZipMissingPropertiesException, InvalidPageZipMissingIndexException, InvalidPageZipInconsistentException, InvalidPageZipMissingAPropertyException, InvalidPageTokenException, AlreadyExistsException, BonitaException { final PageAPI pageAPI = getPageAPI(apiSession); Properties properties; if (processDefinitionId == null) { properties = pageAPI.getPageProperties(zipContent, checkIfItAlreadyExists); } else { properties = pageAPI.getPageProperties(zipContent, false); if (checkIfItAlreadyExists) { final String pageName = properties.getProperty(NAME_PROPERTY); try { pageAPI.getPageByNameAndProcessDefinitionId(pageName, processDefinitionId); throw new AlreadyExistsException("A page with name " + pageName + " already exists for the process " + processDefinitionId); } catch (final PageNotFoundException e) { try { pageAPI.getPageByName(pageName); throw new AlreadyExistsException( "A page with name " + pageName + " already exists for the tenant"); } catch (final PageNotFoundException e1) { //Do nothing (if the page was not found, it means a page with the same name doesn't already exist) } //Do nothing (if the page was not found, it means a page with the same name doesn't already exist) } } } return properties; } public Set<String> getCustomPagePermissions(final Properties properties, final ResourcesPermissionsMapping resourcesPermissionsMapping, final boolean alsoReturnResourcesNotFound) { final SimpleProperties pageProperties = new SimpleProperties(properties); return getCustomPagePermissions(pageProperties, resourcesPermissionsMapping, alsoReturnResourcesNotFound); } protected Set<String> getCustomPagePermissions(final SimpleProperties pageProperties, final ResourcesPermissionsMapping resourcesPermissionsMapping, final boolean alsoReturnResourcesNotFound) { final Set<String> pageRestResources = new HashSet<String>( pageProperties.getPropertyAsSet(RESOURCES_PROPERTY)); // pageRestResources.addAll(Arrays.asList(GET_SYSTEM_SESSION, GET_PORTAL_PROFILE, GET_IDENTITY_USER)); final Set<String> permissions = new HashSet<String>(); for (final String pageRestResource : pageRestResources) { final Set<String> resourcePermissions = resourcesPermissionsMapping.getPropertyAsSet(pageRestResource); if (Collections.emptySet().equals(resourcePermissions)) { if (alsoReturnResourcesNotFound) { permissions.add("<" + pageRestResource + ">"); } else { if (LOGGER.isLoggable(Level.WARNING)) { LOGGER.log(Level.WARNING, "Error while getting resources permissions. Unknown resource: " + pageRestResource + " defined in page.properties"); } } } permissions.addAll(resourcePermissions); } return permissions; } public Set<String> getCustomPagePermissions(final File file, final ResourcesPermissionsMapping resourcesPermissionsMapping, final boolean alsoReturnResourcesNotFound) { final SimpleProperties pageProperties = new SimpleProperties(file); return getCustomPagePermissions(pageProperties, resourcesPermissionsMapping, alsoReturnResourcesNotFound); } public void addPermissionsToCompoundPermissions(final String pageName, final Set<String> customPagePermissions, final CompoundPermissionsMapping compoundPermissionsMapping, final ResourcesPermissionsMapping resourcesPermissionsMapping) throws IOException { customPagePermissions.addAll(resourcesPermissionsMapping.getPropertyAsSet(GET_SYSTEM_SESSION)); customPagePermissions.addAll(resourcesPermissionsMapping.getPropertyAsSet(GET_PORTAL_PROFILE)); customPagePermissions.addAll(resourcesPermissionsMapping.getPropertyAsSet(GET_IDENTITY_USER)); compoundPermissionsMapping.setPropertyAsSet(pageName, customPagePermissions); } public Page getPage(final APISession apiSession, final String pageName, final long processDefinitionId) throws BonitaException { return getPageAPI(apiSession).getPageByNameAndProcessDefinitionId(pageName, processDefinitionId); } public Page getPage(final APISession apiSession, final long pageId) throws BonitaException { return getPageAPI(apiSession).getPage(pageId); } public void removeRestApiExtensionPermissions(final ResourcesPermissionsMapping resourcesPermissionsMapping, final PageResourceProvider pageResourceProvider, final APISession apiSession) throws IOException, BonitaException { ensurePageFolderIsUpToDate(apiSession, pageResourceProvider); final Map<String, String> permissionsMapping = getPermissionMapping(pageResourceProvider); for (final String key : permissionsMapping.keySet()) { resourcesPermissionsMapping.removeProperty(key); } } public void addRestApiExtensionPermissions(final ResourcesPermissionsMapping resourcesPermissionsMapping, final PageResourceProvider pageResourceProvider, final APISession apiSession) throws IOException, BonitaException { ensurePageFolderIsUpToDate(apiSession, pageResourceProvider); final Map<String, String> permissionsMapping = getPermissionMapping(pageResourceProvider); for (final String key : permissionsMapping.keySet()) { resourcesPermissionsMapping.setProperty(key, permissionsMapping.get(key)); } } private Map<String, String> getPermissionMapping(final PageResourceProvider pageResourceProvider) { Map<String, String> permissionsMapping; final File pageProperties = pageResourceProvider.getResourceAsFile("page.properties"); permissionsMapping = getApiExtensionResourcesPermissionsMapping(pageProperties); return permissionsMapping; } private Map<String, String> getApiExtensionResourcesPermissionsMapping(final File pagePropertyFile) { final SimpleProperties pageProperties = new SimpleProperties(pagePropertyFile); final Map<String, String> permissionsMap = new HashMap<>(); if (ContentType.API_EXTENSION.equals(pageProperties.getProperty(PROPERTY_CONTENT_TYPE))) { final String apiExtensionList = pageProperties.getProperty(PROPERTY_API_EXTENSIONS); final String[] apiExtensions = apiExtensionList.split(EXTENSION_SEPARATOR); for (final String apiExtension : apiExtensions) { final String method = pageProperties .getProperty(String.format(PROPERTY_METHOD_MASK, apiExtension.trim())); final String pathTemplate = pageProperties .getProperty(String.format(PROPERTY_PATH_TEMPLATE_MASK, apiExtension.trim())); final String permissions = pageProperties .getProperty(String.format(PROPERTY_PERMISSIONS_MASK, apiExtension.trim())); permissionsMap.put(String.format(RESOURCE_PERMISSION_KEY_MASK, method, pathTemplate), String.format(RESOURCE_PERMISSION_VALUE, permissions)); } } return permissionsMap; } public PageResourceProvider getPageResourceProvider(final Page page, final long tenantId) { return new PageResourceProvider(page, tenantId); } public static void clearCachedClassloaders() throws IOException { for (final String page : PAGES_CLASSLOADERS.keySet()) { closeClassloader(page); } } }