Spring MVC – Access Spring profiles in JSP

This post explains how to restrict access to an area, based on active Spring profile. By reading this, you will be able to access Spring profiles in JSP in order to achieve this functionality:

<qcm:profile value="dev">
    <form action="login-as-admin" method="POST">
        <input type="submit" value="login as admin"/>
    </form>
</qcm:profile>

Spring profiles

Spring profiles allow to create and run a separate configuration per environment. A common use case is the declaration of multiple data source configuration beans according to the environment (h2 for development purposes, PostgreSQL in production).

To enable any class / method for a given profile, simply annotate the class/bean method with @Profile("..."):

@Profile(value = {QcmProfile.HEROKU, QcmProfile.PROD})
@Bean
public DataSource dataSource() {
    final HikariDataSource dataSource = new HikariDataSource();
    dataSource.setMaximumPoolSize(properties.getMaximumPoolSize());
    dataSource.setDataSourceClassName(properties.getDataSourceClassName());
    dataSource.setDataSourceProperties(dataSourceProperties());
    return dataSource;
}

@Profile(QcmProfile.TEST)
@Bean
public DataSource testDataSource() {
    final HikariDataSource dataSource = new HikariDataSource();
    dataSource.setMaximumPoolSize(properties.getMaximumPoolSize());
    dataSource.setDataSourceClassName(properties.getDataSourceClassName());
    dataSource.setDataSourceProperties(testDataSourceProperties());
    return dataSource;
}

You can see the complete configuration class here.

Any component (@Component / @Configuration) annotated with @Profile("...") will be loaded if the given profile(s) is/are enabled.
This behavior is achieved by using @Conditional(ProfileCondition.class) on the @Profile annotation itself.

As you can see, the ProfileCondition simply check the given value with current environment profile:

/**
 * {@link Condition} that matches based on the value of a {@link Profile @Profile}
 * annotation.
 *
 * @author Chris Beams
 * @author Phillip Webb
 * @author Juergen Hoeller
 * @since 4.0
 */
class ProfileCondition implements Condition {

	@Override
	public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
		if (context.getEnvironment() != null) {
			MultiValueMap<String, Object> attrs = metadata.getAllAnnotationAttributes(Profile.class.getName());
			if (attrs != null) {
				for (Object value : attrs.get("value")) {
					if (context.getEnvironment().acceptsProfiles(((String[]) value))) {
						return true;
					}
				}
				return false;
			}
		}
		return true;
	}

}

Use Spring profiles in JSP

It may be useful to display a piece of content in a JSP file, based on a specific environment profile.
In my use case, I wanted to display an admin-login button to facilitate tests, only in development phase (development profile).

I found that the best way to achieve this behavior was to develop a custom JSP tag as the Servlet class gives helpers to show/hide a piece of text.

The main concern was to find out how to access Spring profiles inside a tag. Fortunately, Spring provides with a useful tag class: RequestContextAwareTag.

Access Spring profiles in a tag

To gain access to the Spring context, you need your tag to extend RequestContextAware class, which
exposes the current “RequestContext” according to the JavaDoc:


/**
 * Superclass for all tags that require a {@link RequestContext}.
 *
 * <p>The {@code RequestContext} instance provides easy access
 * to current state like the
 * {@link org.springframework.web.context.WebApplicationContext},
 * the {@link java.util.Locale}, the
 * {@link org.springframework.ui.context.Theme}, etc.
 *
 * <p>Mainly intended for
 * {@link org.springframework.web.servlet.DispatcherServlet} requests;
 * will use fallbacks when used outside {@code DispatcherServlet}.
 *
 * @author Rod Johnson
 * @author Juergen Hoeller
 * @see org.springframework.web.servlet.support.RequestContext
 * @see org.springframework.web.servlet.DispatcherServlet
 */

This class extends the TagSupport Servlet class and override the main method doStartTag() to inject the RequestContext:

	/**
	 * Create and expose the current RequestContext.
	 * Delegates to {@link #doStartTagInternal()} for actual work.
	 * @see #REQUEST_CONTEXT_PAGE_ATTRIBUTE
	 * @see org.springframework.web.servlet.support.JspAwareRequestContext
	 */
	@Override
	public final int doStartTag() throws JspException {
		try {
			this.requestContext = (RequestContext) this.pageContext.getAttribute(REQUEST_CONTEXT_PAGE_ATTRIBUTE);
			if (this.requestContext == null) {
				this.requestContext = new JspAwareRequestContext(this.pageContext);
				this.pageContext.setAttribute(REQUEST_CONTEXT_PAGE_ATTRIBUTE, this.requestContext);
			}
			return doStartTagInternal();
		}
		catch (JspException ex) {
			logger.error(ex.getMessage(), ex);
			throw ex;
		}
		catch (RuntimeException ex) {
			logger.error(ex.getMessage(), ex);
			throw ex;
		}
		catch (Exception ex) {
			logger.error(ex.getMessage(), ex);
			throw new JspTagException(ex.getMessage());
		}
	}

By extending the Spring RequestContextAwareTag and overriding the doStartTagInternal() method, your tag will have access to the RequestContext, needed to retrieve Spring profiles.

With this context, it’s easy to retrieve environment profiles:

public class ProfileConditionTag extends RequestContextAwareTag {
    
    private String profile;

    @Override
    protected int doStartTagInternal() throws Exception {
        final Environment environment = this.getRequestContext().getWebApplicationContext().getEnvironment();
        if (environment != null) {
            final String[] profiles = environment.getActiveProfiles();
            if (ArrayUtils.contains(profiles, this.profile)) {
                return EVAL_BODY_INCLUDE;
            }
        }
        return SKIP_BODY;
    }

    public String getValue() {
        return profile;
    }

    public void setValue(String profile) {
        this.profile = profile;
    }
}

Usage

Create the taglib descriptor and place it into the WEB-INF/taglibs/ directory:

<?xml version="1.0" encoding="UTF-8" ?>
    <taglib xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-jsptaglibrary_2_0.xsd" version="2.0">
    <description>Conditional profile Tag</description>
    <tlib-version>2.1</tlib-version>
    <short-name>ProfileConditionTag</short-name>
    <uri></uri>
    <tag>
        <name>profile</name>
        <tag-class>com.ingesup.java.qcm.taglib.ProfileConditionTag</tag-class>
        <body-content>scriptless</body-content>
        <attribute>
            <name>value</name>
            <required>true</required>
        </attribute>
    </tag>
</taglib>

Using this tag is pretty straightforward:

<qcm:profile value="dev">
    <form action="login-as-admin" method="POST">
        <input type="submit" value="login as admin"/>
    </form>
</qcm:profile>

You can head at this url and see that the admin-login button doesn’t appear as the active profile for the application is heroku.

Application code

You can see the whole application code in my GitHub project.