/jira-project/jira-components/jira-core/src/main/java/com/atlassian/jira/web/servlet/InternalServerErrorHelper.java
Java | 246 lines | 204 code | 35 blank | 7 comment | 15 complexity | 3b9c9d4fc762cd3738e059b9434b6b5e MD5 | raw file
Possible License(s): Apache-2.0
- package com.atlassian.jira.web.servlet;
- import com.atlassian.annotations.Internal;
- import com.atlassian.jira.component.ComponentAccessor;
- import com.atlassian.jira.config.LocaleManager;
- import com.atlassian.jira.config.properties.JiraSystemProperties;
- import com.atlassian.jira.i18n.BootstrapJiraAuthenticationContext;
- import com.atlassian.jira.plugin.navigation.FooterModuleDescriptor;
- import com.atlassian.jira.security.JiraAuthenticationContext;
- import com.atlassian.jira.security.PermissionManager;
- import com.atlassian.jira.security.Permissions;
- import com.atlassian.jira.user.ApplicationUser;
- import com.atlassian.jira.util.I18nHelper;
- import com.atlassian.jira.util.JiraContactHelper;
- import com.atlassian.jira.util.system.ExtendedSystemInfoUtilsImpl;
- import com.atlassian.jira.web.util.ExternalLinkUtilImpl;
- import com.atlassian.jira.web.util.InternalServerErrorDataSource;
- import com.atlassian.jira.web.util.MetalResourcesManager;
- import com.atlassian.plugin.PluginAccessor;
- import com.atlassian.soy.renderer.SoyTemplateRenderer;
- import com.google.common.collect.ImmutableList;
- import com.google.common.collect.ImmutableMap;
- import org.slf4j.Logger;
- import org.slf4j.LoggerFactory;
- import javax.servlet.http.HttpServletRequest;
- import java.io.IOException;
- import java.io.Writer;
- import java.util.LinkedHashMap;
- import java.util.List;
- import java.util.Map;
- import java.util.Optional;
- import java.util.UUID;
- import static com.atlassian.jira.component.ComponentAccessor.getComponentSafely;
- @Internal
- public class InternalServerErrorHelper {
- private static final Logger logger = LoggerFactory.getLogger(InternalServerErrorServlet.class);
- public static void render500ResponsePage(HttpServletRequest req, Writer writer) throws IOException {
- final String errorId = UUID.randomUUID().toString();
- try {
- InternalServerErrorHelper instance = new InternalServerErrorHelper(req, writer, errorId);
- logError(instance.getDataSource(), errorId);
- instance.render();
- } catch (RuntimeException re) {
- logger.error("Cannot render the 500 page for error {}", errorId, re);
- // Last resort... Try to give them something.
- writer.write(simple500ErrorPage(errorId));
- }
- }
- private static void logError(InternalServerErrorDataSource dataSource, String errorId) {
- Map<String, Object> map = new LinkedHashMap<>();
- map.put("errorId", errorId);
- map.putAll(dataSource.getGeneralInfo());
- logger.error(map.toString());
- }
- private static String simple500ErrorPage(String errorId) {
- return ""
- + "<html>\n"
- + "<head><title>JIRA — Internal Server Error</title>\n"
- + "<meta name=\"decorator\" content=\"none\"/>\n"
- + "</head><body>\n"
- + "<h1>JIRA — Internal Server Error</h1>\n"
- + "<p>Error reference: " + errorId + "</p>\n"
- + "<p>Unable to render full error details at this time.\n"
- + "Please consult the error logs for more information.</p>\n"
- + "</body></html>\n";
- }
- private final String id;
- private final HttpServletRequest req;
- private final ImmutableMap.Builder<String, Object> map;
- private final Writer writer;
- private final I18nHelper i18n;
- private final boolean isDevMode;
- private final boolean isAdmin;
- private final boolean isSysAdmin;
- private final InternalServerErrorDataSource dataSource;
- private InternalServerErrorHelper(HttpServletRequest req, Writer writer, String id) {
- final JiraAuthenticationContext authenticationContext = getComponentSafely(JiraAuthenticationContext.class)
- .orElseGet(BootstrapJiraAuthenticationContext::new);
- final ApplicationUser user = authenticationContext.getLoggedInUser();
- this.id = id;
- this.req = req;
- this.map = ImmutableMap.builder();
- this.writer = writer;
- this.i18n = authenticationContext.getI18nHelper();
- this.isDevMode = JiraSystemProperties.getInstance().isDevMode();
- this.isAdmin = isAdmin(user);
- this.isSysAdmin = isSysAdmin(user);
- this.dataSource = initializeInternalServerErrorDS(req, logger);
- }
- private boolean render() throws IOException {
- addBasicInfo();
- addTechnicalDetails(dataSource);
- return renderSoy();
- }
- public InternalServerErrorDataSource getDataSource() {
- return dataSource;
- }
- private boolean renderSoy() {
- // If Pico isn't initialized, then we haven't even started *thinking* about plugins, yet,
- // so the Soy renderer cannot possibly be available. Use presence of the PluginAccessor as
- // a proxy for this, as ComponentManager.getInstance().getState().isPicoInitialised() cannot
- // be mocked out.
- if (ComponentAccessor.getComponentSafely(PluginAccessor.class).isPresent()) {
- SoyTemplateRenderer renderer = ComponentAccessor.getOSGiComponentInstanceOfType(SoyTemplateRenderer.class);
- if (renderer != null) {
- renderer.render(writer, "jira.webresources:jira-errors", "JIRA.Templates.errors.InternalError.page", map.build());
- return true;
- }
- }
- return false;
- }
- private void addTechnicalDetails(final InternalServerErrorDataSource ds) {
- final ImmutableMap.Builder<String, Object> technicalDetails = ImmutableMap.builder();
- final boolean shouldShowFullInfo = isAdmin || isDevMode;
- if (shouldShowFullInfo && !Boolean.parseBoolean(req.getParameter("short"))) {
- ds.appendFullMessageData(technicalDetails, isSysAdmin || isDevMode);
- map.put("fullInfo", true);
- } else {
- ds.appendSimpleMessageData(technicalDetails);
- map.put("fullInfo", false);
- }
- map.put("technicalDetails", technicalDetails.build());
- }
- private void addBasicInfo() {
- map.put("footer", getFooterContent());
- map.put("helpsteps", getHelpSteps());
- map.put("resourcesContent", MetalResourcesManager.getMetalResources(req.getContextPath()));
- map.put("errorId", id);
- }
- private String getFooterContent() {
- return getComponentSafely(PluginAccessor.class)
- .map(InternalServerErrorHelper::getFooterModule)
- .map(footer -> footer.getModule().getFullFooterHtml(req))
- .orElse("");
- }
- private static FooterModuleDescriptor getFooterModule(final PluginAccessor pluginAccessor) {
- return (FooterModuleDescriptor) pluginAccessor.getEnabledPluginModule("jira.footer:standard-footer");
- }
- private InternalServerErrorDataSource initializeInternalServerErrorDS(final HttpServletRequest req, final Logger log) {
- final Optional<LocaleManager> localeManager = getComponentSafely(LocaleManager.class);
- ExtendedSystemInfoUtilsImpl extendedSystemInfoUtils = null;
- try {
- // Use whether or not we got the LocaleManager as a proxy for whether or not it is safe to get other
- // things from Pico. If we didn't get that, then don't ask for anything else, either.
- if (localeManager.isPresent()) {
- extendedSystemInfoUtils = new ExtendedSystemInfoUtilsImpl(i18n);
- }
- } catch (RuntimeException e) {
- log.warn("Cannot initialize ExtendedSystemInfoUtilsImpl", e);
- }
- return new InternalServerErrorDataSource(i18n, extendedSystemInfoUtils, req.getServletContext(),
- localeManager.orElse(null), req);
- }
- private List<String> getHelpSteps() {
- return isAdmin ? getJiraSupportHelpSteps() : getContactJiraAdminHelpSteps();
- }
- private List<String> getContactJiraAdminHelpSteps() {
- final String contactAdministratorsLink = getContactAdministratorLink();
- return ImmutableList.of(
- contactAdministratorsLink != null
- ? i18n.getText("500.send.with.ref.to.your.jira.admin.contact.form", "<a target=\"_blank\" href=\"" + contactAdministratorsLink + "\">", "</a>")
- : i18n.getText("500.send.with.ref.to.your.jira.admin")
- );
- }
- private List<String> getJiraSupportHelpSteps() {
- final ExternalLinkUtilImpl externalLinkUtil = new ExternalLinkUtilImpl();
- final String sac = externalLinkUtil.getProperty("external.link.jira.support.site");
- final String stp = req.getContextPath() + "/plugins/servlet/stp/view/#support-zip";
- return ImmutableList.of(
- i18n.getText("500.collect.when.problem.occurred"),
- i18n.getText("500.collect.server.log"),
- i18n.getText("500.create.support.zip", "<a target=\"_blank\" href=\"" + stp + "\">", "</a>"),
- i18n.getText("500.raise.an.issue.with.all.info", "<a href=\"" + sac + "\">", "</a>")
- );
- }
- private List<String> getContactCloudAdminHelpSteps() {
- final String contactAdministratorsLink = getContactAdministratorLink();
- return ImmutableList.of(
- contactAdministratorsLink != null
- ? i18n.getText("500.send.to.your.jira.admin.contact.form", "<a target=\"_blank\" href=\"" + contactAdministratorsLink + "\">", "</a>")
- : i18n.getText("500.send.to.your.jira.admin")
- );
- }
- private List<String> getCloudSupportHelpSteps() {
- final ExternalLinkUtilImpl externalLinkUtil = new ExternalLinkUtilImpl();
- final String sac = externalLinkUtil.getProperty("external.link.jira.support.site");
- return ImmutableList.of(i18n.getText("500.raise.an.issue.on.sac", "<a href=\"" + sac + "\">", "</a>"));
- }
- private String getContactAdministratorLink() {
- try {
- return getComponentSafely(JiraContactHelper.class)
- .filter(JiraContactHelper::isAdministratorContactFormEnabled)
- .map(helper -> helper.getAdministratorContactLink(req.getContextPath()))
- .orElse(null);
- } catch (RuntimeException re) {
- logger.debug("[{}] Error retrieving contact adminstrator link", id, re);
- return null;
- }
- }
- private static boolean isSysAdmin(final ApplicationUser user) {
- return user != null && getComponentSafely(PermissionManager.class)
- .map(permissionManager -> permissionManager.hasPermission(Permissions.SYSTEM_ADMIN, user))
- .orElse(false);
- }
- private static boolean isAdmin(final ApplicationUser user) {
- return user != null && getComponentSafely(PermissionManager.class)
- .map(permissionManager ->
- permissionManager.hasPermission(Permissions.ADMINISTER, user) ||
- permissionManager.hasPermission(Permissions.SYSTEM_ADMIN, user))
- .orElse(false);
- }
- }