001: // Copyright 2006, 2007 The Apache Software Foundation
002: //
003: // Licensed under the Apache License, Version 2.0 (the "License");
004: // you may not use this file except in compliance with the License.
005: // You may obtain a copy of the License at
006: //
007: // http://www.apache.org/licenses/LICENSE-2.0
008: //
009: // Unless required by applicable law or agreed to in writing, software
010: // distributed under the License is distributed on an "AS IS" BASIS,
011: // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
012: // See the License for the specific language governing permissions and
013: // limitations under the License.
014:
015: package org.apache.tapestry.internal.services;
016:
017: import java.io.File;
018: import java.io.IOException;
019: import java.net.URL;
020: import java.net.URLClassLoader;
021: import java.net.URLConnection;
022: import java.util.UUID;
023:
024: import javassist.CannotCompileException;
025: import javassist.ClassPool;
026: import javassist.CtClass;
027: import javassist.CtMethod;
028: import javassist.CtNewMethod;
029: import javassist.LoaderClassPath;
030: import javassist.NotFoundException;
031:
032: import org.apache.commons.logging.Log;
033: import org.apache.tapestry.internal.InternalComponentResources;
034: import org.apache.tapestry.internal.InternalConstants;
035: import org.apache.tapestry.internal.SingleKeySymbolProvider;
036: import org.apache.tapestry.internal.SyntheticModuleDef;
037: import org.apache.tapestry.internal.SyntheticSymbolSourceContributionDef;
038: import org.apache.tapestry.internal.test.InternalBaseTestCase;
039: import org.apache.tapestry.internal.transform.pages.BasicComponent;
040: import org.apache.tapestry.internal.transform.pages.BasicSubComponent;
041: import org.apache.tapestry.ioc.Registry;
042: import org.apache.tapestry.ioc.RegistryBuilder;
043: import org.apache.tapestry.ioc.def.ContributionDef;
044: import org.apache.tapestry.ioc.def.ModuleDef;
045: import org.apache.tapestry.ioc.services.PropertyAccess;
046: import org.apache.tapestry.ioc.services.SymbolProvider;
047: import org.apache.tapestry.runtime.Component;
048: import org.apache.tapestry.services.TapestryModule;
049: import org.testng.annotations.AfterClass;
050: import org.testng.annotations.BeforeClass;
051: import org.testng.annotations.Test;
052:
053: /**
054: * Tests for {@link org.apache.tapestry.internal.services.ComponentInstantiatorSourceImpl}. Several
055: * of these tests are more of the form of integration tests that instantiate the Tapestry IoC
056: * Registry.
057: */
058: public class ComponentInstantiatorSourceImplTest extends
059: InternalBaseTestCase {
060: private static final ClassLoader _contextLoader = Thread
061: .currentThread().getContextClassLoader();
062:
063: private static final String SYNTH_COMPONENT_CLASSNAME = "org.apache.tapestry.internal.transform.pages.SynthComponent";
064:
065: private File _extraClasspath;
066:
067: private ComponentInstantiatorSource _source;
068:
069: private Registry _registry;
070:
071: private PropertyAccess _access;
072:
073: private ClassLoader _extraLoader;
074:
075: private String _tempDir;
076:
077: @Test
078: public void controlled_packages() throws Exception {
079: ComponentClassTransformer transformer = newMock(ComponentClassTransformer.class);
080: Log log = mockLog();
081:
082: replay();
083:
084: ComponentInstantiatorSourceImpl e = new ComponentInstantiatorSourceImpl(
085: _contextLoader, transformer, log);
086:
087: assertEquals(e.inControlledPackage("foo.bar.Baz"), false);
088:
089: // Check that classes in the default package are never controlled
090:
091: assertEquals(e.inControlledPackage("Biff"), false);
092:
093: // Now add a controlled package
094:
095: e.addPackage("foo.bar");
096:
097: assertEquals(e.inControlledPackage("foo.bar.Baz"), true);
098:
099: // Sub-packages of controlled packages are controlled as well
100:
101: assertEquals(e.inControlledPackage("foo.bar.biff.Pop"), true);
102:
103: // Parents of controlled packages are not controlled
104:
105: assertEquals(e.inControlledPackage("foo.Gloop"), false);
106:
107: verify();
108: }
109:
110: /** More of an integration test. */
111: @Test
112: public void load_component_via_service() throws Exception {
113: Component target = createComponent(BasicComponent.class);
114:
115: // Should not be an instance, since it is loaded by a different class loader.
116: assertFalse(BasicComponent.class.isInstance(target));
117:
118: _access.set(target, "value", "some default value");
119: assertEquals(_access.get(target, "value"), "some default value");
120:
121: _access.set(target, "retainedValue", "some retained value");
122: assertEquals(_access.get(target, "retainedValue"),
123: "some retained value");
124:
125: // Setting a property value before pageDidLoad will cause that value
126: // to be the default when the page detaches.
127:
128: target.containingPageDidLoad();
129:
130: _access.set(target, "value", "some transient value");
131: assertEquals(_access.get(target, "value"),
132: "some transient value");
133:
134: target.containingPageDidDetach();
135:
136: assertEquals(_access.get(target, "value"), "some default value");
137: assertEquals(_access.get(target, "retainedValue"),
138: "some retained value");
139: }
140:
141: @Test
142: public void load_sub_component_via_service() throws Exception {
143: Component target = createComponent(BasicSubComponent.class);
144:
145: target.containingPageDidLoad();
146:
147: _access.set(target, "value", "base class");
148: assertEquals(_access.get(target, "value"), "base class");
149:
150: _access.set(target, "intValue", 33);
151: assertEquals(_access.get(target, "intValue"), 33);
152:
153: target.containingPageDidDetach();
154:
155: assertNull(_access.get(target, "value"));
156: assertEquals(_access.get(target, "intValue"), 0);
157: }
158:
159: /**
160: * This allows tests the exists() method.
161: */
162: @Test
163: public void component_class_reload() throws Exception {
164: // Ensure it doesn't already exist:
165:
166: assertFalse(_source.exists(SYNTH_COMPONENT_CLASSNAME));
167:
168: // Create the class on the fly.
169:
170: createSynthComponentClass("Original");
171:
172: assertTrue(_source.exists(SYNTH_COMPONENT_CLASSNAME));
173:
174: Named named = (Named) createComponent(SYNTH_COMPONENT_CLASSNAME);
175:
176: assertEquals(named.getName(), "Original");
177:
178: String path = _tempDir + "/"
179: + SYNTH_COMPONENT_CLASSNAME.replace('.', '/')
180: + ".class";
181: URL url = new File(path).toURL();
182:
183: long dtm = readDTM(url);
184:
185: while (true) {
186: if (readDTM(url) != dtm)
187: break;
188:
189: // Keep re-writing the file until we see the DTM change.
190:
191: createSynthComponentClass("Updated");
192: }
193:
194: // Detect the change and clear out the internal caches
195:
196: UpdateListenerHub hub = _registry.getService(
197: "UpdateListenerHub", UpdateListenerHub.class);
198:
199: hub.fireUpdateEvent();
200:
201: // This will be the new version of the class
202:
203: named = (Named) createComponent(SYNTH_COMPONENT_CLASSNAME);
204:
205: assertEquals(named.getName(), "Updated");
206: }
207:
208: private long readDTM(URL url) throws Exception {
209: URLConnection connection = url.openConnection();
210:
211: connection.connect();
212:
213: return connection.getLastModified();
214: }
215:
216: private void createSynthComponentClass(String name)
217: throws CannotCompileException, NotFoundException,
218: IOException {
219: ClassPool pool = new ClassPool();
220: // Inside Maven Surefire, the system classpath is not sufficient to find all
221: // the necessary files.
222: pool.appendClassPath(new LoaderClassPath(_extraLoader));
223:
224: CtClass ctClass = pool.makeClass(SYNTH_COMPONENT_CLASSNAME);
225:
226: ctClass.setSuperclass(pool.get(BasicComponent.class.getName()));
227:
228: // Implement method getName()
229:
230: CtMethod method = CtNewMethod.make(
231: "public String getName() { return \"" + name + "\"; }",
232: ctClass);
233: ctClass.addMethod(method);
234:
235: ctClass.addInterface(pool.get(Named.class.getName()));
236:
237: ctClass.writeFile(_extraClasspath.getAbsolutePath());
238: }
239:
240: private Component createComponent(Class componentClass) {
241: String classname = componentClass.getName();
242:
243: return createComponent(classname);
244: }
245:
246: private Component createComponent(String classname) {
247: InternalComponentResources resources = mockInternalComponentResources();
248:
249: replay();
250:
251: // Can't wait for the HiveMind code base to start using some generics for this kind of
252: // thing.
253:
254: Instantiator inst = _source.findInstantiator(classname);
255:
256: Component target = inst.newInstance(resources);
257:
258: verify();
259:
260: return target;
261: }
262:
263: @BeforeClass
264: public void setup_tests() throws Exception {
265: String tempdir = System.getProperty("java.io.tmpdir");
266: String uid = UUID.randomUUID().toString();
267:
268: _tempDir = tempdir + "/tapestry-test-classpath/" + uid;
269: _extraClasspath = new File(_tempDir);
270:
271: System.out.println("Creating dir: " + _extraClasspath);
272:
273: _extraClasspath.mkdirs();
274:
275: URL url = _extraClasspath.toURL();
276:
277: _extraLoader = new URLClassLoader(new URL[] { url },
278: _contextLoader);
279: RegistryBuilder builder = new RegistryBuilder(_extraLoader);
280:
281: builder.add(TapestryModule.class);
282:
283: SymbolProvider provider = new SingleKeySymbolProvider(
284: InternalConstants.TAPESTRY_ALIAS_MODE_SYMBOL, "servlet");
285: ContributionDef contribution = new SyntheticSymbolSourceContributionDef(
286: "AliasMode", provider, "before:ApplicationDefaults");
287:
288: ModuleDef module = new SyntheticModuleDef(contribution);
289:
290: builder.add(module);
291:
292: _registry = builder.build();
293:
294: // _registry.getService("Alias", Alias.class).setMode("servlet");
295:
296: _source = _registry
297: .getService(ComponentInstantiatorSource.class);
298: _access = _registry.getService(PropertyAccess.class);
299:
300: _source
301: .addPackage("org.apache.tapestry.internal.transform.pages");
302: }
303:
304: @AfterClass
305: public void cleanup_tests() {
306: _registry.shutdown();
307:
308: _registry = null;
309: _source = null;
310: _access = null;
311: }
312: }
|