org.artifactory.webapp.wicket.application.ArtifactoryApplication.java Source code

Java tutorial

Introduction

Here is the source code for org.artifactory.webapp.wicket.application.ArtifactoryApplication.java

Source

/*
 * Artifactory is a binaries repository manager.
 * Copyright (C) 2012 JFrog Ltd.
 *
 * Artifactory 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.
 *
 * Artifactory 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 Artifactory.  If not, see <http://www.gnu.org/licenses/>.
 */

package org.artifactory.webapp.wicket.application;

import com.google.common.collect.Sets;
import org.apache.commons.io.FileUtils;
import org.apache.wicket.Application;
import org.apache.wicket.Page;
import org.apache.wicket.RuntimeConfigurationType;
import org.apache.wicket.authorization.IAuthorizationStrategy;
import org.apache.wicket.authorization.IUnauthorizedComponentInstantiationListener;
import org.apache.wicket.authorization.strategies.CompoundAuthorizationStrategy;
import org.apache.wicket.authroles.authentication.AuthenticatedWebApplication;
import org.apache.wicket.authroles.authentication.AuthenticatedWebSession;
import org.apache.wicket.injection.Injector;
import org.apache.wicket.markup.html.IPackageResourceGuard;
import org.apache.wicket.markup.html.SecurePackageResourceGuard;
import org.apache.wicket.markup.html.WebPage;
import org.apache.wicket.request.http.WebRequest;
import org.apache.wicket.request.http.WebResponse;
import org.apache.wicket.request.resource.IResource;
import org.apache.wicket.request.resource.ResourceReference;
import org.apache.wicket.settings.IApplicationSettings;
import org.apache.wicket.settings.IMarkupSettings;
import org.apache.wicket.settings.IRequestCycleSettings;
import org.apache.wicket.settings.IResourceSettings;
import org.apache.wicket.settings.ISecuritySettings;
import org.apache.wicket.spring.injection.annot.SpringBean;
import org.apache.wicket.util.resource.locator.caching.CachingResourceStreamLocator;
import org.apache.wicket.util.time.Duration;
import org.artifactory.addon.AddonsManager;
import org.artifactory.addon.BootstrapListener;
import org.artifactory.addon.wicket.SamlAddon;
import org.artifactory.api.config.CentralConfigService;
import org.artifactory.api.context.ArtifactoryContext;
import org.artifactory.api.context.ArtifactoryContextThreadBinder;
import org.artifactory.api.context.ContextHelper;
import org.artifactory.api.repo.RepositoryService;
import org.artifactory.common.ArtifactoryHome;
import org.artifactory.common.ConstantValues;
import org.artifactory.common.property.ArtifactorySystemProperties;
import org.artifactory.common.wicket.application.AddWicketPathListener;
import org.artifactory.common.wicket.application.NoLocaleResourceStreamLocator;
import org.artifactory.common.wicket.application.ResponsePageSupport;
import org.artifactory.common.wicket.component.panel.sidemenu.SiteMapAware;
import org.artifactory.common.wicket.contributor.ResourcePackage;
import org.artifactory.common.wicket.model.sitemap.MenuNode;
import org.artifactory.common.wicket.model.sitemap.SiteMap;
import org.artifactory.common.wicket.model.sitemap.SiteMapBuilder;
import org.artifactory.webapp.spring.ArtifactorySpringComponentInjector;
import org.artifactory.webapp.wicket.application.sitemap.ArtifactorySiteMapBuilder;
import org.artifactory.webapp.wicket.page.base.BasePage;
import org.artifactory.webapp.wicket.page.browse.listing.ArtifactListPage;
import org.artifactory.webapp.wicket.page.browse.simplebrowser.SimpleRepoBrowserPage;
import org.artifactory.webapp.wicket.page.build.BuildBrowserConstants;
import org.artifactory.webapp.wicket.page.build.page.BuildBrowserRootPage;
import org.artifactory.webapp.wicket.page.error.AccessDeniedPage;
import org.artifactory.webapp.wicket.page.error.InternalErrorPage;
import org.artifactory.webapp.wicket.page.error.PageExpiredErrorPage;
import org.artifactory.webapp.wicket.page.home.HomePage;
import org.artifactory.webapp.wicket.page.search.artifact.ArtifactSearchPage;
import org.artifactory.webapp.wicket.page.security.login.LoginPage;
import org.artifactory.webapp.wicket.page.security.login.LogoutPage;
import org.artifactory.webapp.wicket.page.security.login.forgot.ForgotPasswordPage;
import org.artifactory.webapp.wicket.page.security.login.reset.ResetPasswordPage;
import org.artifactory.webapp.wicket.page.security.profile.ProfilePage;
import org.artifactory.webapp.wicket.resource.LogoResource;
import org.artifactory.webapp.wicket.service.authentication.LogoutService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.util.Collections;
import java.util.EnumSet;
import java.util.Locale;
import java.util.Map;
import java.util.Set;

/**
 * @author Yoav Landman
 */
public class ArtifactoryApplication extends AuthenticatedWebApplication implements SiteMapAware {
    private static final Logger log = LoggerFactory.getLogger(ArtifactoryApplication.class);
    private static final String SHARED_RESOURCES_PATH = "wicket/resource/";

    @SpringBean
    private CentralConfigService centralConfig;

    @SpringBean
    private RepositoryService repositoryService;

    @SpringBean
    private AddonsManager addons;

    private SiteMap siteMap;

    /**
     * Used to prevent logo caching in browsers
     */
    private long logoModifyTime;

    private final EnumSet<ConstantValues> modes = Sets.newEnumSet(Collections.<ConstantValues>emptySet(),
            ConstantValues.class);

    public static ArtifactoryApplication get() {
        return (ArtifactoryApplication) Application.get();
    }

    public void mountPage(Class<? extends Page> pageClass) {
        String url = "/" + pageClass.getSimpleName().replaceFirst("Page", "").toLowerCase(Locale.ENGLISH) + ".html";
        safeMountPage(url, pageClass);
    }

    private void safeMountPage(String url, Class<? extends Page> pageClass) {
        unmount(url); // un-mount first (in case of re-mounting)
        mountPage(url, pageClass);
    }

    /**
     * Updates the logo last modify time to force browsers to reload the logo.
     */
    public void updateLogo() {
        this.logoModifyTime = new File(ContextHelper.get().getArtifactoryHome().getLogoDir(), "logo")
                .lastModified();
    }

    @Override
    public Class<? extends BasePage> getHomePage() {
        return HomePage.class;
    }

    @Override
    public RuntimeConfigurationType getConfigurationType() {
        //Init the modes from the constants if needed
        if (modes.isEmpty()) {
            // use configuration from the servlet context since properties are not bound to the thread when this method is called
            ArtifactoryHome artifactoryHome = getArtifactoryContext().getArtifactoryHome();
            ArtifactorySystemProperties artifactorySystemProperties = artifactoryHome.getArtifactoryProperties();
            /*if (Boolean.parseBoolean(artifactorySystemProperties.getProperty(ConstantValues.dev))) {
            modes.add(ConstantValues.dev);
            }*/
            if (Boolean.parseBoolean(artifactorySystemProperties.getProperty(ConstantValues.test))) {
                modes.add(ConstantValues.test);
            }
            if (Boolean.parseBoolean(artifactorySystemProperties.getProperty(ConstantValues.qa))) {
                modes.add(ConstantValues.qa);
            }
        }
        if (modes.contains(ConstantValues.dev)) {
            return RuntimeConfigurationType.DEVELOPMENT;
        } else {
            return super.getConfigurationType();
        }
    }

    public CentralConfigService getCentralConfig() {
        return centralConfig;
    }

    public CompoundAuthorizationStrategy getAuthorizationStrategy() {
        IAuthorizationStrategy authorizationStrategy = getSecuritySettings().getAuthorizationStrategy();
        if (!(authorizationStrategy instanceof CompoundAuthorizationStrategy)) {
            throw new IllegalStateException(
                    "Unexpected authorization strategy: " + authorizationStrategy.getClass());
        }
        return (CompoundAuthorizationStrategy) authorizationStrategy;
    }

    public RepositoryService getRepositoryService() {
        return repositoryService;
    }

    @Override
    public SiteMap getSiteMap() {
        return siteMap;
    }

    public String getSharedResourcesPath() {
        return SHARED_RESOURCES_PATH;
    }

    public boolean isLogoExists() {
        return logoModifyTime != 0;
    }

    public long getLogoModifyTime() {
        return logoModifyTime;
    }

    boolean isDevelopmentMode() {
        return RuntimeConfigurationType.DEVELOPMENT.equals(getConfigurationType());
    }

    @Override
    protected void onDestroy() {
        deleteUploadsFolder();
        super.onDestroy();
    }

    @Override
    protected Class<? extends AuthenticatedWebSession> getWebSessionClass() {
        return ArtifactoryWebSession.class;
    }

    @Override
    protected Class<? extends WebPage> getSignInPageClass() {
        SamlAddon samlAddon = addons.addonByType(SamlAddon.class);
        if (samlAddon.isAutoRedirectToSamlIdentityProvider()) {
            return samlAddon.getSamlLoginRequestPageClass();
        }

        // return default login page
        return LoginPage.class;
    }

    @Override
    protected WebResponse newWebResponse(WebRequest webRequest, HttpServletResponse httpServletResponse) {
        return new IgnoreEofWebResponse(
                new HeaderBufferingWebResponse(super.newWebResponse(webRequest, httpServletResponse)));
    }

    @Override
    protected WebRequest newWebRequest(HttpServletRequest servletRequest, final String filterPath) {
        return new ArtifactoryWebRequest(servletRequest, filterPath);
    }

    @Override
    protected void init() {
        setupSpring();

        // bind context
        ArtifactoryContext originalContext = ContextHelper.get();
        boolean ctxAlreadyBound = originalContext != null;
        ArtifactoryContext context = getArtifactoryContext();
        if (!ctxAlreadyBound) {
            ArtifactoryContextThreadBinder.bind(context);
        } else {
            if (context != originalContext) {
                throw new IllegalStateException("Initialization of Wicket Application with a Spring context "
                        + context + " different from the Thread bound one " + originalContext);
            }
        }

        boolean artifactoryHomeAlreadyBound = ArtifactoryHome.isBound();
        if (!artifactoryHomeAlreadyBound) {
            ArtifactoryHome.bind(context.getArtifactoryHome());
        }

        super.init();

        try {
            doInit();
        } finally {
            // unbind context
            if (!ctxAlreadyBound) {
                ArtifactoryContextThreadBinder.unbind();
            }
            if (!artifactoryHomeAlreadyBound) {
                ArtifactoryHome.unbind();
            }
        }
    }

    /**
     * Rebuild the site map and re-mount the pages.
     */
    public void rebuildSiteMap() {
        buildSiteMap();
        mountPages();
    }

    private void setupSpring() {
        getComponentInstantiationListeners().add(new ArtifactorySpringComponentInjector(this));
        Injector.get().inject(this);
    }

    /**
     * Mount a resource.
     *
     * @param path     The path of the resource.
     * @param resource The resource itself
     */
    private void mountResource(String path, final IResource resource) {
        mountResource(path, new ResourceReference(path) {
            @Override
            public IResource getResource() {
                return resource;
            }
        });
    }

    private void mountLogo() {
        mountResource("/logo", new LogoResource(ContextHelper.get().getArtifactoryHome()));
    }

    private void notifyBootstrapListeners() {
        ArtifactoryContext artifactoryContext = getArtifactoryContext();
        Map<String, BootstrapListener> beanMap = artifactoryContext.beansForType(BootstrapListener.class);
        for (BootstrapListener listener : beanMap.values()) {
            listener.onApplicationInit();
        }
    }

    private ArtifactoryContext getArtifactoryContext() {
        return (ArtifactoryContext) getServletContext().getAttribute(ArtifactoryContext.APPLICATION_CONTEXT_KEY);
    }

    /**
     * Delete the upload folder (in case we were not shut down cleanly)
     */
    private void deleteUploadsFolder() {
        ArtifactoryHome artifactoryHome = getArtifactoryContext().getArtifactoryHome();
        File tmpUploadsDir = artifactoryHome.getTempUploadDir();
        if (tmpUploadsDir.exists()) {
            try {
                FileUtils.cleanDirectory(tmpUploadsDir);
            } catch (IOException ignore) {
                log.warn("Failed to delete the upload directory.");
            }
        }
    }

    private void setupListeners() {
        // wrap the unauthorizedComponentInstantiation listener so that we can discard repoPath
        // attributes from the request when needing to login
        ISecuritySettings securitySettings = getSecuritySettings();
        IUnauthorizedComponentInstantiationListener orig = securitySettings
                .getUnauthorizedComponentInstantiationListener();

        securitySettings.setUnauthorizedComponentInstantiationListener(
                new RepoBrowsingAwareUnauthorizedComponentInstantiationListener(orig));

        // add ArtifactoryRequestCycleListener
        getRequestCycleListeners().add(new ArtifactoryRequestCycleListener());
        getRequestCycleListeners().add(new ResponsePageSupport());
    }

    private void mountPages() {
        Set<Class<? extends Page>> hardMountPages = Sets.newHashSet();

        hardMountPage(hardMountPages, SimpleRepoBrowserPage.PATH, SimpleRepoBrowserPage.class);
        hardMountPage(hardMountPages, ArtifactListPage.PATH, ArtifactListPage.class);

        // mount services
        hardMountPage(hardMountPages, "/service/logout", LogoutService.class);

        // mount general pages
        mountPage(InternalErrorPage.class);
        mountPage(AccessDeniedPage.class);
        mountPage(PageExpiredErrorPage.class);

        mountPage(LoginPage.class);
        mountPage(LogoutPage.class);
        mountPage(ProfilePage.class);
        mountPage(ResetPasswordPage.class);
        mountPage(ForgotPasswordPage.class);

        hardMountPage(hardMountPages, "/search/artifact", ArtifactSearchPage.class);
        safeMountPage("/search/artifact/", ArtifactSearchPage.class);

        SamlAddon samlAddon = addons.addonByType(SamlAddon.class);
        Class<? extends Page> loginRequest = samlAddon.getSamlLoginRequestPageClass();
        if (loginRequest != null) {
            hardMountPage(hardMountPages, "/saml/loginRequest", loginRequest);
        }
        Class<? extends Page> loginResponse = samlAddon.getSamlLoginResponsePageClass();
        if (loginResponse != null) {
            hardMountPage(hardMountPages, "/saml/loginResponse", loginResponse);
        }
        Class<? extends Page> logoutRequest = samlAddon.getSamlLogoutRequestPageClass();
        if (logoutRequest != null) {
            hardMountPage(hardMountPages, "/saml/logoutRequest", logoutRequest);
        }

        // We need both of these since accessing modules without the trailing "/" mount fails
        hardMountPage(hardMountPages, BuildBrowserConstants.MOUNT_PATH, BuildBrowserRootPage.class);
        safeMountPage(BuildBrowserConstants.MOUNT_PATH + "/", BuildBrowserRootPage.class);

        for (MenuNode pageNode : siteMap.getPages()) {
            if (!hardMountPages.contains(pageNode.getPageClass())) {
                mountPage(pageNode.getPageClass());
            }
        }
    }

    private void hardMountPage(Set<Class<? extends Page>> hardMountPages, String url,
            Class<? extends Page> pageClass) {
        safeMountPage(url, pageClass);
        hardMountPages.add(pageClass);
    }

    private void buildSiteMap() {
        SiteMapBuilder builder = newSiteMapBuilder();
        builder.buildSiteMap();
        builder.cachePageNodes();
        siteMap = builder.getSiteMap();
    }

    protected void doInit() {
        setup();

        buildSiteMap();
        mountPages();
        mountLogo();

        deleteUploadsFolder();
        notifyBootstrapListeners();
        updateLogo();
    }

    protected void setup() {
        setupListeners();

        // set HeaderRenderStrategy = ParentFirstHeaderRenderStrategy
        System.setProperty("Wicket_HeaderRenderStrategy",
                "org.apache.wicket.markup.renderStrategy.ParentFirstHeaderRenderStrategy");

        // look for pages at the root of the web-app
        IResourceSettings resourceSettings = getResourceSettings();
        resourceSettings.addResourceFolder("");
        IPackageResourceGuard packageResourceGuard = resourceSettings.getPackageResourceGuard();
        if (packageResourceGuard instanceof SecurePackageResourceGuard) {
            SecurePackageResourceGuard resourceGuard = (SecurePackageResourceGuard) packageResourceGuard;
            resourceGuard.addPattern("+Icon");
        }

        // ResourcePackage resources are locale insensitive
        NoLocaleResourceStreamLocator locator = new NoLocaleResourceStreamLocator();
        locator.addNoLocaleClass(ResourcePackage.class);
        resourceSettings.setResourceStreamLocator(new CachingResourceStreamLocator(locator));

        // add the addons authorization strategy
        AddonsAuthorizationStrategy addonsAuthorizationStrategy = new AddonsAuthorizationStrategy();
        Injector.get().inject(addonsAuthorizationStrategy);
        getAuthorizationStrategy().add(addonsAuthorizationStrategy);

        // increase request timeout to support long running transactions
        IRequestCycleSettings requestCycleSettings = getRequestCycleSettings();
        requestCycleSettings.setTimeout(Duration.hours(5));

        // set error pages
        IApplicationSettings applicationSettings = getApplicationSettings();
        applicationSettings.setPageExpiredErrorPage(PageExpiredErrorPage.class);
        applicationSettings.setAccessDeniedPage(AccessDeniedPage.class);
        applicationSettings.setInternalErrorPage(InternalErrorPage.class);

        // markup settings
        IMarkupSettings markupSettings = getMarkupSettings();
        markupSettings.setDefaultMarkupEncoding("UTF-8");
        markupSettings.setCompressWhitespace(true);
        markupSettings.setStripComments(true);
        markupSettings.setStripWicketTags(true);

        //QA settings
        if (modes.contains(ConstantValues.qa)) {
            getComponentInstantiationListeners().add(new AddWicketPathListener());
        }

        // RTFACT-4619, fixed by patching HeaderBufferingWebResponse
        getRequestCycleSettings().setBufferResponse(false);

        // RTFACT-4636
        getPageSettings().setVersionPagesByDefault(false);
    }

    protected SiteMapBuilder newSiteMapBuilder() {
        SiteMapBuilder builder = new ArtifactorySiteMapBuilder();
        Injector.get().inject(builder);
        return builder;
    }
}