001    /*
002     *  Copyright 2007 Tacos.
003     * 
004     *  Licensed under the Apache 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.apache.org/licenses/LICENSE-2.0
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     *  under the License.
016     */
017    package net.sf.tacos.resolvers;
018    
019    import org.apache.commons.logging.Log;
020    import org.apache.hivemind.ApplicationRuntimeException;
021    import org.apache.hivemind.Location;
022    import org.apache.hivemind.Resource;
023    import org.apache.hivemind.impl.DefaultClassResolver;
024    import org.apache.hivemind.impl.LocationImpl;
025    import org.apache.hivemind.util.ClasspathResource;
026    import org.apache.hivemind.util.Defense;
027    import org.apache.tapestry.INamespace;
028    import org.apache.tapestry.IRequestCycle;
029    import org.apache.tapestry.resolver.AbstractSpecificationResolver;
030    import org.apache.tapestry.resolver.ComponentSpecificationResolver;
031    import org.apache.tapestry.services.ClassFinder;
032    import org.apache.tapestry.spec.ComponentSpecification;
033    import org.apache.tapestry.spec.IComponentSpecification;
034    
035    /**
036     * An AbstractSpecificationResolver similar to tapestry's. Contains additional 
037     * logic that can loads tapestry templates from the classpath.
038     * 
039     * @author Howard Lewis Ship
040     * @author Andreas Andreou, amplafi.com
041     */
042    
043    public class ClasspathComponentSpecResolver extends
044            AbstractSpecificationResolver implements ComponentSpecificationResolver {
045    
046        /** Set by container. */
047        private Log log;
048    
049        /** Set by resolve(). */
050        private String type;
051    
052        private ClassFinder classFinder;
053    
054        protected void reset() {
055            type = null;
056            super.reset();
057        }
058    
059        /**
060         * Passed the namespace of a container (to resolve the type in) and the type
061         * to resolve, performs the processing. A "bare type" (without a library
062         * prefix) may be in the containerNamespace, or the framework namespace (a
063         * search occurs in that order).
064         * 
065         * @param cycle
066         *            current request cycle
067         * @param containerNamespace
068         *            namespace that may contain a library referenced in the type
069         * @param componentType
070         *            the component specification to find, either a simple name, or
071         *            prefixed with a library id (defined for the container
072         *            namespace)
073         * @see #getNamespace()
074         * @see #getSpecification()
075         */
076    
077        public void resolve(IRequestCycle cycle, INamespace containerNamespace,
078                String componentType, Location location) {
079            Defense.notNull(componentType, "type");
080    
081            int colonx = componentType.indexOf(':');
082    
083            if (colonx > 0) {
084                String libraryId = componentType.substring(0, colonx);
085                String simpleType = componentType.substring(colonx + 1);
086    
087                resolve(cycle, containerNamespace, libraryId, simpleType, location);
088            } else {
089                resolve(cycle, containerNamespace, null, componentType, location);
090            }
091        }
092    
093        /**
094         * Like #resolve(org.apache.tapestry.IRequestCycle,
095         * org.apache.tapestry.INamespace, java.lang.String,
096         * org.apache.tapestry.ILocation), but used when the type has already been
097         * parsed into a library id and a simple type.
098         * 
099         * @param cycle
100         *            current request cycle
101         * @param containerNamespace
102         *            namespace that may contain a library referenced in the type
103         * @param libraryId
104         *            the library id within the container namespace, or null
105         * @param componentType
106         *            the component specification to find as a simple name (without
107         *            a library prefix)
108         * @param location
109         *            of reference to be resolved
110         * @throws ApplicationRuntimeException
111         *             if the type cannot be resolved
112         */
113    
114        public void resolve(IRequestCycle cycle, INamespace containerNamespace,
115                String libraryId, String componentType, Location location) {
116            reset();
117            this.type = componentType;
118    
119            INamespace namespace = findNamespaceForId(containerNamespace, libraryId);
120    
121            setNamespace(namespace);
122    
123            if (namespace.containsComponentType(type)) {
124                setSpecification(namespace.getComponentSpecification(type));
125                return;
126            }
127    
128            IComponentSpecification spec = searchForComponent(cycle);
129    
130            // If not found after search, check to see if it's in
131            // the framework instead.
132            if (spec == null) {
133                spec = doCustomSearch(cycle);
134            }
135    
136            if (spec == null) {
137                throw new ApplicationRuntimeException("no Such Component Type:" + this.type,
138                        location, null);
139            }
140    
141            setSpecification(spec);
142    
143            // Install it into the namespace, to short-circuit any future search.
144            install();
145        }
146    
147        /**
148         * Override to add custom behavior to component resolution - will be the last
149         * resolve logic to be executed.
150         * @param cycle
151         * @return
152         */
153        protected IComponentSpecification doCustomSearch(IRequestCycle cycle) {
154            return null;
155        }
156    
157        private IComponentSpecification searchForComponent(IRequestCycle cycle) {
158            INamespace namespace = getNamespace();
159    
160            // if (_log.isDebugEnabled())
161            // _log.debug(ImplMessages.resolvingComponent(_type, namespace));
162    
163            String expectedName = type + ".jwc";
164            Resource namespaceLocation = namespace.getSpecificationLocation();
165    
166            // Look for appropriate file in same folder as the library (or
167            // application) specification.
168    
169            IComponentSpecification result = check(namespaceLocation.getRelativeResource(expectedName));
170    
171            if (result != null) {
172                return result;
173            }
174            if (namespace.isApplicationNamespace()) {
175    
176                // The application namespace gets some extra searching.
177    
178                result = check(getWebInfAppLocation().getRelativeResource(
179                        expectedName));
180    
181                if (result == null) {
182                    result = check(getWebInfLocation().getRelativeResource(
183                            expectedName));
184                }
185                if (result == null) {
186                    result = check(getContextRoot()
187                            .getRelativeResource(expectedName));
188                }
189                if (result != null) {
190                    return result;
191                }
192            }
193    
194            result = searchForComponentClass(namespace, type);
195    
196            if (result != null) {
197                return result;
198            }
199    
200            // Not in the library or app spec; does it match a component
201            // provided by the Framework?
202    
203            INamespace framework = getSpecificationSource().getFrameworkNamespace();
204    
205            if (framework.containsComponentType(type)) {
206                return framework.getComponentSpecification(type);
207            }
208            return getDelegate()
209                    .findComponentSpecification(cycle, namespace, type);
210        }
211    
212        IComponentSpecification searchForComponentClass(INamespace namespace,
213                String componentType) {
214            String packages = namespace
215                    .getPropertyValue("org.apache.tapestry.component-class-packages");
216    
217            String className = componentType.replace('/', '.');
218    
219            Class componentClass = classFinder.findClass(packages, className);
220    
221            if (componentClass == null) {
222                return null;
223            }
224            Resource componentResource = null;
225            Resource namespaceResource = namespace.getSpecificationLocation();
226    
227            String fullPath = componentClass.getName().replace('.', '/');
228            ClasspathResource html = new ClasspathResource(
229                    new DefaultClassResolver(), fullPath + ".html");
230    
231            // first check near the class, if the html is there then that's where
232            // the (fictional) jwc should be - need exploded wars for this
233    
234            if (html.getResourceURL() != null && namespace.isApplicationNamespace()) {
235                componentResource = new ClasspathResource(
236                        new DefaultClassResolver(), fullPath + ".jwc");
237            } else {
238                componentResource = namespaceResource.getRelativeResource(componentType
239                        + ".jwc");
240            }
241    
242            Location location = new LocationImpl(componentResource);
243    
244            IComponentSpecification spec = new ComponentSpecification();
245            spec.setLocation(location);
246            spec.setSpecificationLocation(componentResource);
247            spec.setComponentClassName(componentClass.getName());
248    
249            return spec;
250        }
251    
252        private IComponentSpecification check(Resource resource) {
253            if (log.isDebugEnabled()) {
254                log.debug("Checking: " + resource);
255            }
256            if (resource.getResourceURL() == null) {
257                return null;
258            }
259            return getSpecificationSource().getComponentSpecification(resource);
260        }
261    
262        private void install() {
263            INamespace namespace = getNamespace();
264            IComponentSpecification specification = getSpecification();
265    
266            // if (_log.isDebugEnabled())
267            // _log.debug(ImplMessages.installingComponent(_type, namespace,
268            // specification));
269    
270            namespace.installComponentSpecification(type, specification);
271        }
272    
273        public String getType() {
274            return type;
275        }
276    
277        public void setLog(Log log) {
278            this.log = log;
279        }
280    
281        public void setClassFinder(ClassFinder classFinder) {
282            this.classFinder = classFinder;
283        }
284    
285    }