001/**
002 * Copyright 2005-2014 The Kuali Foundation
003 *
004 * Licensed under the Educational Community License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.opensource.org/licenses/ecl2.php
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package org.kuali.rice.krad.uif.util;
017
018import java.io.InputStream;
019import java.net.URL;
020import java.util.Arrays;
021import java.util.Properties;
022
023import javax.xml.namespace.QName;
024
025import org.apache.log4j.Logger;
026import org.junit.After;
027import org.junit.AfterClass;
028import org.junit.Assert;
029import org.junit.Assume;
030import org.junit.Before;
031import org.junit.BeforeClass;
032import org.kuali.rice.core.api.config.property.ConfigContext;
033import org.kuali.rice.core.api.lifecycle.Lifecycle;
034import org.kuali.rice.core.api.resourceloader.GlobalResourceLoader;
035import org.kuali.rice.core.framework.config.property.SimpleConfig;
036import org.kuali.rice.core.framework.resourceloader.SpringResourceLoader;
037import org.kuali.rice.krad.UserSession;
038import org.kuali.rice.krad.uif.freemarker.FreeMarkerInlineRenderBootstrap;
039import org.kuali.rice.krad.uif.view.ViewAuthorizer;
040import org.kuali.rice.krad.util.GlobalVariables;
041import org.springframework.beans.MutablePropertyValues;
042import org.springframework.mock.web.MockServletContext;
043import org.springframework.web.context.ConfigurableWebApplicationContext;
044import org.springframework.web.context.WebApplicationContext;
045import org.springframework.web.context.support.StaticWebApplicationContext;
046import org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer;
047import org.springframework.web.servlet.view.freemarker.FreeMarkerViewResolver;
048
049/**
050 * Utilities class for establishing a minimal environment for testing operations involving Uif
051 * components.
052 *
053 * @author Kuali Rice Team (rice.collab@kuali.org)
054 */
055public final class UifUnitTestUtils {
056    private final static Logger LOG = Logger.getLogger(UifUnitTestUtils.class);
057
058    private final static ThreadLocal<Properties> TL_CONFIG_PROPERTIES = new ThreadLocal<Properties>();
059
060    private static ConfigurableWebApplicationContext webApplicationContext;
061
062    /**
063     * Create a web application context suitable for FreeMarker unit testing.
064     */
065    private static void configureKradWebApplicationContext() {
066        MockServletContext sctx = new MockServletContext();
067        StaticWebApplicationContext ctx = new StaticWebApplicationContext();
068        ctx.setServletContext(sctx);
069
070        MutablePropertyValues mpv = new MutablePropertyValues();
071        mpv.add("preferFileSystemAccess", false);
072        mpv.add("templateLoaderPath", "/krad-web");
073        Properties props = new Properties();
074        props.put("number_format", "computer");
075        props.put("template_update_delay", "2147483647");
076        mpv.add("freemarkerSettings", props);
077        ctx.registerSingleton("freemarkerConfig", FreeMarkerConfigurer.class, mpv);
078
079        mpv = new MutablePropertyValues();
080        mpv.add("cache", true);
081        mpv.add("prefix", "");
082        mpv.add("suffix", ".ftl");
083        ctx.registerSingleton("viewResolver", FreeMarkerViewResolver.class, mpv);
084
085        ctx.registerSingleton("freeMarkerInputBootstrap", FreeMarkerInlineRenderBootstrap.class);
086
087        ctx.refresh();
088        ctx.start();
089        sctx.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ctx);
090        webApplicationContext = ctx;
091    }
092
093    /**
094     * Get the config properties for the current thread.
095     *
096     * @return The config properties for the current thread.
097     */
098    public static Properties getConfigProperties() {
099        return TL_CONFIG_PROPERTIES.get();
100    }
101
102    /**
103     * Get the web application context.
104     */
105    public static WebApplicationContext getWebApplicationContext() {
106        return webApplicationContext;
107    }
108
109    /**
110     * Establish a Rice configuration providing enough mock services via
111     * {@link GlobalResourceLoader} to support the use of KRAD UIF components in unit tests.
112     *
113     * @param applicationId The application ID for the fake environment.
114     * @throws Exception
115     */
116    public static void establishMockConfig(String applicationId) throws Exception {
117        try {
118            ClassLoader loader = Thread.currentThread().getContextClassLoader();
119
120            SimpleConfig config = new SimpleConfig();
121            Properties configProperties = new Properties();
122
123            URL defaultsUrl = loader.getResource("KRAD-UifDefaults.properties");
124            URL rootUrl = new URL(defaultsUrl.toExternalForm().substring(0,
125                    defaultsUrl.toExternalForm().lastIndexOf('/')));
126            configProperties.setProperty("root.url", rootUrl.toExternalForm());
127
128            InputStream defaultPropertyResource = defaultsUrl.openStream();
129            Assert.assertNotNull("KRAD-UifDefaults.properties", defaultPropertyResource);
130            configProperties.load(defaultPropertyResource);
131
132            InputStream appPropertyResource = loader.getResourceAsStream(applicationId + ".properties");
133            Assert.assertNotNull(applicationId + ".properties", appPropertyResource);
134            configProperties.load(appPropertyResource);
135
136            for (String propName : configProperties.stringPropertyNames()) {
137                String propValue = (String) configProperties.getProperty(propName);
138                StringBuilder propBuilder = new StringBuilder(propValue);
139                int exprStart = 0, exprEnd = 0;
140                while (exprStart != -1) {
141                    exprStart = propBuilder.indexOf("${", exprEnd);
142                    if (exprStart == -1) {
143                        continue;
144                    }
145
146                    exprEnd = propBuilder.indexOf("}", exprStart);
147                    if (exprEnd - exprStart < 3) {
148                        continue;
149                    }
150
151                    String expr = propBuilder.substring(exprStart + 2, exprEnd);
152                    String exprValue = configProperties.getProperty(expr);
153                    if (exprValue != null) {
154                        propBuilder.delete(exprStart, exprEnd + 1);
155                        propBuilder.insert(exprStart, exprValue);
156                        configProperties.setProperty(propName, propBuilder.toString());
157                        exprEnd = exprStart + exprValue.length();
158                    }
159                }
160            }
161
162            String resourceBundles = configProperties.getProperty("test.resource.bundles");
163            if (resourceBundles != null) {
164                for (String resourceBundle : resourceBundles.split(",")) {
165                    InputStream propertyResource = loader.getResourceAsStream(resourceBundle);
166                    Assert.assertNotNull(resourceBundle, resourceBundle);
167                    configProperties.load(propertyResource);
168                    LOG.info("Added resource bundle " + resourceBundle);
169                }
170            }
171
172            config.putProperties(configProperties);
173            config.putProperty("application.id", applicationId);
174
175            ConfigContext.init(config);
176
177            MockServletContext servletContext = new MockServletContext();
178            GlobalResourceLoader.addResourceLoader(new SpringResourceLoader(new QName("KRAD-UifDefaults"), Arrays
179                    .asList(
180                    "KRAD-UifDefaults-test-context.xml"), servletContext));
181            GlobalResourceLoader.addResourceLoader(new SpringResourceLoader(new QName(applicationId), Arrays.asList(
182                    applicationId + "-test-context.xml"), servletContext));
183
184            TL_CONFIG_PROPERTIES.set(ConfigContext.getCurrentContextConfig().getProperties());
185            try {
186                GlobalResourceLoader.start();
187                Lifecycle viewService = GlobalResourceLoader.getService("viewService");
188
189                if (viewService != null) {
190                    viewService.start();
191                }
192
193            } finally {
194                TL_CONFIG_PROPERTIES.remove();
195            }
196
197            configureKradWebApplicationContext();
198        } catch (Throwable t) {
199            LOG.error("Skipping tests, resource setup failed", t);
200            Assume.assumeNoException("Skipping tests, resource setup failed", t);
201        }
202    }
203
204    /**
205     * Shut down the mock configuration. When {@link #establishMockConfig(String)} is used with
206     * {@link BeforeClass}, then this method should be used with {@link AfterClass} to tear down
207     * resources.
208     */
209    public static void tearDownMockConfig() throws Exception {
210        GlobalResourceLoader.stop();
211    }
212
213    /**
214     * Establish a user session with the given principal name.
215     *
216     * <p>
217     * This method will use KIM API calls to look up a person with the provided principal name. Use
218     * {@link #establishMockConfig(String)} to set up a mock KIM environment if needed.
219     * </p>
220     *
221     * @param principalName The principal name of the user to establish a session with.
222     */
223    public static void establishMockUserSession(String principalName) {
224        UserSession session = new UserSession(principalName);
225        GlobalVariables.setUserSession(session);
226    }
227
228    /**
229     * Shut down the mock user session. When {@link #establishMockUserSession(String)} is used with
230     * {@link Before}, then this method should be used with {@link After} to tear down resources.
231     */
232    public static void tearDownMockUserSession() {
233        GlobalVariables.setUserSession(null);
234        GlobalVariables.clear();
235    }
236
237    /**
238     * Get a view authorizer allowing most operations.
239     *
240     * @return A view authorizer allowing most operations.
241     */
242    public static ViewAuthorizer getAllowMostViewAuthorizer() {
243        return new MockViewAuthorizer();
244    }
245
246}