All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.manydesigns.portofino.ResourceActionsModule Maven / Gradle / Ivy

There is a newer version: 5.3.4
Show newest version
/*
 * Copyright (C) 2005-2020 ManyDesigns srl.  All rights reserved.
 * http://www.manydesigns.com/
 *
 * This 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 3 of
 * the License, or (at your option) any later version.
 *
 * This software 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 software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */

package com.manydesigns.portofino;

import com.manydesigns.portofino.actions.ActionLogic;
import com.manydesigns.portofino.cache.CacheResetEvent;
import com.manydesigns.portofino.cache.CacheResetListener;
import com.manydesigns.portofino.cache.CacheResetListenerRegistry;
import com.manydesigns.portofino.code.CodeBase;
import com.manydesigns.portofino.dispatcher.ResourceResolver;
import com.manydesigns.portofino.modules.Module;
import com.manydesigns.portofino.modules.ModuleStatus;
import com.manydesigns.portofino.resourceactions.custom.CustomAction;
import com.manydesigns.portofino.resourceactions.form.FormAction;
import com.manydesigns.portofino.resourceactions.form.TableFormAction;
import com.manydesigns.portofino.resourceactions.registry.ActionRegistry;
import com.manydesigns.portofino.rest.PortofinoApplicationRoot;
import com.manydesigns.portofino.shiro.SecurityClassRealm;
import com.manydesigns.portofino.shiro.SelfRegisteringShiroFilter;
import com.manydesigns.portofino.spring.PortofinoSpringConfiguration;
import io.jsonwebtoken.io.Encoders;
import org.apache.commons.configuration2.Configuration;
import org.apache.commons.configuration2.builder.FileBasedConfigurationBuilder;
import org.apache.commons.configuration2.ex.ConfigurationException;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.vfs2.FileObject;
import org.apache.commons.vfs2.FileSystemException;
import org.apache.commons.vfs2.FileType;
import org.apache.shiro.mgt.RealmSecurityManager;
import org.apache.shiro.util.LifecycleUtils;
import org.apache.shiro.web.env.EnvironmentLoader;
import org.apache.shiro.web.env.WebEnvironment;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Bean;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.servlet.ServletContext;
import java.util.UUID;

import static com.manydesigns.portofino.spring.PortofinoSpringConfiguration.APPLICATION_DIRECTORY;
import static com.manydesigns.portofino.spring.PortofinoSpringConfiguration.PORTOFINO_CONFIGURATION;

/*
* @author Paolo Predonzani     - [email protected]
* @author Angelo Lupo          - [email protected]
* @author Giampiero Granatella - [email protected]
* @author Alessio Stalla       - [email protected]
*/
public class ResourceActionsModule implements Module, ApplicationContextAware {
    public static final String copyright =
            "Copyright (C) 2005-2020 ManyDesigns srl";
    public static final String ACTIONS_DIRECTORY = "actionsDirectory";

    //**************************************************************************
    // Fields
    //**************************************************************************

    @Autowired
    public ServletContext servletContext;

    @Autowired
    @Qualifier(PORTOFINO_CONFIGURATION)
    public Configuration configuration;

    @Autowired
    @Qualifier(PortofinoSpringConfiguration.PORTOFINO_CONFIGURATION_FILE)
    public FileBasedConfigurationBuilder configurationFile;

    @Autowired
    @Qualifier(APPLICATION_DIRECTORY)
    public FileObject applicationDirectory;

    @Autowired
    public CodeBase codeBase;

    @Autowired
    public CacheResetListenerRegistry cacheResetListenerRegistry;

    protected EnvironmentLoader environmentLoader = new EnvironmentLoader();
    protected ApplicationContext applicationContext;

    protected ModuleStatus status = ModuleStatus.CREATED;

    //**************************************************************************
    // Logging
    //**************************************************************************

    public static final Logger logger =
            LoggerFactory.getLogger(ResourceActionsModule.class);

    @Override
    public String getModuleVersion() {
        return Module.getPortofinoVersion();
    }

    @Override
    public String getName() {
        return "ResourceActions";
    }

    @PostConstruct
    public void init() throws FileSystemException {
        logger.debug("Initializing dispatcher");
        ActionLogic.init(configuration);

        FileObject actionsDirectory = getActionsDirectory(configuration, applicationDirectory);
        logger.info("Actions directory: " + actionsDirectory);
        //TODO ElementsFileUtils.ensureDirectoryExistsAndWarnIfNotWritable(actionsDirectory);

        if(configuration.getBoolean(PortofinoProperties.GROOVY_PRELOAD_PAGES, false)) {
            logger.info("Preloading actions");
            try {
                ResourceResolver resourceResolver =
                        PortofinoApplicationRoot.getRootFactory().createRoot().getResourceResolver();
                preloadResourceActions(actionsDirectory, resourceResolver);
            } catch (Exception e) {
                logger.warn("Could not preload actions", e);
            }
        }
        if(configuration.getBoolean(PortofinoProperties.GROOVY_PRELOAD_CLASSES, false)) {
            logger.info("Preloading Groovy classes");
            preloadClasses(codeBase.getRoot());
        }

        cacheResetListenerRegistry.getCacheResetListeners().add(new ConfigurationCacheResetListener());

        if(!configuration.containsKey("jwt.secret")) {
            String jwtSecret = Encoders.BASE64.encode((UUID.randomUUID() + UUID.randomUUID().toString()).getBytes());
            logger.warn("No jwt.secret property was set, so we generated one: {}.", jwtSecret);
            configuration.setProperty("jwt.secret", jwtSecret);
            try {
                configurationFile.save();
                logger.info("Saved configuration file {}", configurationFile.getFileHandler().getFile().getAbsolutePath());
            } catch (ConfigurationException e) {
                logger.warn("Configuration could not be saved", e);
            }
        }

        logger.info("Initializing Shiro environment");
        WebEnvironment environment = environmentLoader.initEnvironment(servletContext);
        RealmSecurityManager rsm = (RealmSecurityManager) environment.getWebSecurityManager();
        SelfRegisteringShiroFilter shiroFilter = SelfRegisteringShiroFilter.get(servletContext);
        if(shiroFilter != null) {
            try {
                //when reloading the Spring context, this overwrites the filter's stale security manager.
                shiroFilter.init();
            } catch (Exception e) {
                logger.error("Could not initialize the Shiro filter", e);
                status = ModuleStatus.FAILED;
                return;
            }
        }
        logger.debug("Creating SecurityClassRealm");
        SecurityClassRealm realm = new SecurityClassRealm(codeBase, "Security", applicationContext);
        try {
            LifecycleUtils.init(realm);
        } catch (Exception  e) {
            logger.warn("Security class not found or invalid or initialization failed. We will reload and/or initialize it on next use.", e);
        }
        rsm.setRealm(realm);
        status = ModuleStatus.STARTED;
    }

    @Bean
    public ActionRegistry getResourceActionRegistry() {
        logger.debug("Creating actions registry");
        ActionRegistry actionRegistry = new ActionRegistry();
        actionRegistry.register(CustomAction.class);
        actionRegistry.register(FormAction.class);
        actionRegistry.register(TableFormAction.class);
        return actionRegistry;
    }

    @Bean(name = ACTIONS_DIRECTORY)
    public FileObject getActionsDirectory(
            @Autowired @Qualifier(PORTOFINO_CONFIGURATION) Configuration configuration,
            @Autowired @Qualifier(APPLICATION_DIRECTORY) FileObject applicationDirectory) throws FileSystemException {
        String actionsDirectory = configuration.getString("portofino.actions.path", "actions");
        return applicationDirectory.resolveFile(actionsDirectory);
    }

    protected void preloadResourceActions(FileObject directory, ResourceResolver resourceResolver) throws FileSystemException {
        for(FileObject child : directory.getChildren()) {
            logger.debug("visit {}", child);
            if(child.getType() == FileType.FOLDER) {
                if(!child.equals(directory) && !child.equals(directory.getParent())) {
                    try {
                        resourceResolver.resolve(child, Class.class).getConstructor().newInstance();
                    } catch(Throwable t) {
                        logger.warn("ResourceAction preload failed for actionDescriptor " + child.getName().getPath(), t);
                    }
                    preloadResourceActions(child, resourceResolver);
                }
            }
        }
    }

    protected void preloadClasses(FileObject directory) {
        try {
            for(FileObject file : directory.getChildren()) {
                logger.debug("visit {}", file);
                if(file.getType() == FileType.FOLDER) {
                    if(!file.equals(directory) && !file.equals(directory.getParent())) {
                        preloadClasses(file);
                    }
                } else {
                    String extension = file.getName().getExtension();
                    String className = codeBase.getRoot().getName().getRelativeName(file.getName());
                    if(!StringUtils.isEmpty(extension)) {
                        className = className.substring(0, className.length() - extension.length() - 1);
                    }
                    logger.debug("Preloading " + className);
                    try {
                        codeBase.loadClass(className);
                    } catch(Throwable t) {
                        logger.warn("Class preload failed for " + className, t);
                    }
                }
            }
        } catch (FileSystemException e) {
            logger.warn("Could not preload classes under " + directory, e);
        }
    }

    @PreDestroy
    public void destroy() {
        logger.info("Destroying Shiro environment...");
        environmentLoader.destroyEnvironment(servletContext);
        status = ModuleStatus.DESTROYED;
    }

    @Override
    public ModuleStatus getStatus() {
        return status;
    }

    private static class ConfigurationCacheResetListener implements CacheResetListener {
        @Override
        public void handleReset(CacheResetEvent e) {
            ActionLogic.clearConfigurationCache();
        }
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy