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     */
016    package org.kuali.rice.krad.uif.util;
017    
018    import java.io.InputStream;
019    import java.net.URL;
020    import java.util.Arrays;
021    import java.util.Properties;
022    
023    import javax.xml.namespace.QName;
024    
025    import org.apache.log4j.Logger;
026    import org.junit.After;
027    import org.junit.AfterClass;
028    import org.junit.Assert;
029    import org.junit.Assume;
030    import org.junit.Before;
031    import org.junit.BeforeClass;
032    import org.kuali.rice.core.api.config.property.ConfigContext;
033    import org.kuali.rice.core.api.lifecycle.Lifecycle;
034    import org.kuali.rice.core.api.resourceloader.GlobalResourceLoader;
035    import org.kuali.rice.core.framework.config.property.SimpleConfig;
036    import org.kuali.rice.core.framework.resourceloader.SpringResourceLoader;
037    import org.kuali.rice.krad.UserSession;
038    import org.kuali.rice.krad.uif.freemarker.FreeMarkerInlineRenderBootstrap;
039    import org.kuali.rice.krad.uif.view.ViewAuthorizer;
040    import org.kuali.rice.krad.util.GlobalVariables;
041    import org.springframework.beans.MutablePropertyValues;
042    import org.springframework.mock.web.MockServletContext;
043    import org.springframework.web.context.ConfigurableWebApplicationContext;
044    import org.springframework.web.context.WebApplicationContext;
045    import org.springframework.web.context.support.StaticWebApplicationContext;
046    import org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer;
047    import 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     */
055    public 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    }