1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17 package org.apache.cocoon.components;
18
19 import org.apache.avalon.excalibur.component.ExcaliburComponentSelector;
20 import org.apache.avalon.excalibur.component.RoleManager;
21 import org.apache.avalon.framework.component.Component;
22 import org.apache.avalon.framework.component.ComponentException;
23 import org.apache.avalon.framework.configuration.Configuration;
24 import org.apache.avalon.framework.configuration.ConfigurationException;
25 import org.apache.avalon.framework.configuration.DefaultConfiguration;
26
27 /**
28 * An extension of <code>ExcaliburComponentSelector</code> that can have a parent
29 * and accepts a wider variety of configurations.
30 *
31 * @author <a href="mailto:sylvain@apache.org">Sylvain Wallez</a>
32 * @version CVS $Id: ExtendedComponentSelector.java 433543 2006-08-22 06:22:54Z crossley $
33 */
34 public class ExtendedComponentSelector extends ExcaliburComponentSelector
35 implements ParentAware {
36
37 /** The role manager */
38 protected RoleManager roles;
39
40 /** The parent selector, if any */
41 protected ExtendedComponentSelector parentSelector;
42
43 /** The parent locator, if any */
44 protected ComponentLocator parentLocator;
45
46 /** The class loader to use */
47 protected ClassLoader classLoader;
48
49 /** The role of this selector. Set in <code>configure()</code>. */
50 protected String roleName;
51
52 /** The default hint */
53 protected String defaultHint;
54
55 /** This selector's location (used for debugging purposes) */
56 private String location;
57
58
59 /** Create the ComponentSelector with the Thread context ClassLoader */
60 public ExtendedComponentSelector() {
61 this.classLoader = Thread.currentThread().getContextClassLoader();
62 }
63
64 /** Create the ComponentSelector with a ClassLoader */
65 public ExtendedComponentSelector(ClassLoader loader) {
66 super(loader);
67
68 if (loader == null) {
69 this.classLoader = Thread.currentThread().getContextClassLoader();
70 } else {
71 this.classLoader = loader;
72 }
73 }
74
75 /**
76 * Get the name for component-instance elements (i.e. components not defined
77 * by their role shortcut. If <code>null</code>, any element having a 'class'
78 * attribute will be considered as a component instance.
79 * <p>
80 * The default here is to return <code>null</code>, and subclasses can redefine
81 * this method to return particular values.
82 *
83 * @return <code>null</code>, but can be changed by subclasses
84 */
85 protected String getComponentInstanceName() {
86 return null;
87 }
88
89 /**
90 * Get the name of the attribute giving the class name of a component.
91 * The default here is "class", but this can be overriden in subclasses.
92 *
93 * @return "<code>class</code>", but can be changed by subclasses
94 */
95 protected String getClassAttributeName() {
96 return "class";
97 }
98
99 /**
100 * Get the name of the attribute giving the default hint to use if
101 * none is given. The default here is "default", but this can be
102 * overriden in subclasses. If this method returns <code>null</code>,
103 * no default hint can be specified.
104 *
105 * @return "<code>default</code>", but can be changed by subclasses
106 */
107 protected String getDefaultHintAttributeName() {
108 return "default";
109 }
110
111 /**
112 * Configure the RoleManager. Redeclared only because parent member is private.
113 */
114 public void setRoleManager(RoleManager roles) {
115 super.setRoleManager(roles);
116 this.roles = roles;
117 }
118
119 /**
120 * Set the parent of this selector. This can be done after the selector is
121 * initialized, but <em>only once</em>. This allows this selector to be
122 * created by a component manager while still being able to have a parent.
123 *
124 * @param parent the parent selector
125 * @throws IllegalStateException if parent is already set
126 */
127 /* public void setParentSelector(ComponentSelector parent) {
128 if (this.parentSelector != null) {
129 throw new IllegalStateException("Parent selector is already set");
130 }
131 this.parentSelector = parent;
132 this.parentComponents = new HashSet();
133 }
134 */
135
136 /**
137 * Get the role name for this selector. This is called by <code>configure()</code>
138 * to set the value of <code>this.roleName</code>.
139 *
140 * @return the role name, or <code>null<code> if it couldn't be determined.
141 */
142 protected String getRoleName(Configuration config) {
143 // Get the role for this selector
144 String roleName = config.getAttribute("role", null);
145 if (roleName == null && this.roles != null) {
146 roleName = this.roles.getRoleForName(config.getName());
147 }
148
149 return roleName;
150 }
151
152 /**
153 * Configure this selector. This is the main difference with the parent class :
154 * <ul>
155 * <li>if {@link #getComponentInstanceName()} returns <code>null</code>,
156 * any child configurations having a attribute named as the result of
157 * {@link #getClassAttributeName()}, is considered as a component instance.
158 * </li>
159 * <li>if {@link #getComponentInstanceName()} returns a non-null value,
160 * only child configurations having this name are considered as a
161 * component instance.
162 * </li>
163 * <li>if other cases, it's name is considered to be a hint in the role manager.
164 * The behaviour is then the same as <code>ExcaliburComponentSelector</code>.
165 * </li>
166 *
167 * @param config the configuration
168 * @throws ConfigurationException if some hints aren't defined
169 */
170 public void configure(Configuration config) throws ConfigurationException {
171
172 // Store location
173 this.location = config.getLocation();
174
175 this.roleName = getRoleName(config);
176
177 // Pass a copy of the top-level object to superclass so that
178 // our name is properly initialized
179 // FIXME : could be avoided if parent m_role was protected or had protected accessors
180 DefaultConfiguration temp = new DefaultConfiguration(config.getName(), this.location);
181 if (config.getAttribute("role", null) != null) {
182 temp.setAttribute("role", this.roleName);
183 }
184 super.configure(temp);
185
186 // Get default hint
187 this.defaultHint = config.getAttribute(this.getDefaultHintAttributeName(), null);
188
189 // Add components
190 String compInstanceName = getComponentInstanceName();
191
192 Configuration[] instances = config.getChildren();
193
194 for (int i = 0; i < instances.length; i++) {
195 Configuration instance = instances[i];
196
197 Object hint = instance.getAttribute("name").trim();
198
199 String classAttr = instance.getAttribute(getClassAttributeName(), null);
200 String className;
201
202 if (compInstanceName == null) {
203 // component-instance implicitly defined by the presence of the 'class' attribute
204 if (classAttr == null) {
205 className = this.roles.getDefaultClassNameForHint(roleName, instance.getName());
206 } else {
207 className = classAttr.trim();
208 }
209
210 } else {
211 // component-instances names explicitly defined
212 if (compInstanceName.equals(instance.getName())) {
213 className = (classAttr == null) ? null : classAttr.trim();
214 } else {
215 className = this.roles.getDefaultClassNameForHint(roleName, instance.getName());
216 }
217 }
218
219 if (className == null) {
220 String message = "Unable to determine class name for component named '" + hint +
221 "' at " + instance.getLocation();
222
223 getLogger().error(message);
224 throw new ConfigurationException(message);
225 }
226
227 try {
228 Class clazz = this.classLoader.loadClass(className);
229 addComponent(hint, clazz, instance);
230
231 } catch (Exception e) {
232 String message = "Could not load class " + className + " for component named '" +
233 hint + "' at " + instance.getLocation();
234
235 getLogger().error(message, e);
236 throw new ConfigurationException(message, e);
237 }
238 }
239 }
240
241 /**
242 * Get the default hint, if any for this selector.
243 */
244 public String getDefaultHint() {
245 // Inherit parent default hint if have no own
246 if (this.defaultHint == null && this.parentSelector != null) {
247 return this.parentSelector.getDefaultHint();
248 }
249
250 return this.defaultHint;
251 }
252
253 /* (non-Javadoc)
254 * @see org.apache.avalon.framework.component.ComponentSelector#select(java.lang.Object)
255 */
256 public Component select(Object hint) throws ComponentException {
257 if (hint == null) {
258 hint = this.defaultHint;
259 }
260
261 if (parentSelector == null) {
262 // No parent: default behaviour
263 return super.select(hint);
264 }
265
266 try {
267 // Try in this selector first
268 final Component component = super.select(hint);
269 return component;
270
271 } catch (ComponentException original) {
272 try {
273 // Doesn't exist here: try in parent selector
274 final Component component = this.parentSelector.select(hint);
275 return component;
276
277 } catch (ComponentException nested) {
278 // Doesn't exist in parent too: throw exception.
279
280 if (nested.getCause() != null) {
281 // Nested exception has a cause; let's throw it instead of original.
282 throw nested;
283 }
284
285 // Throw original exception
286 throw original;
287 }
288 }
289 }
290
291 /* (non-Javadoc)
292 * @see org.apache.avalon.framework.component.ComponentSelector#release(org.apache.avalon.framework.component.Component)
293 */
294 public void release(Component component) {
295 // Was it selected on the parent ?
296 if (this.parentSelector != null && this.parentSelector.canRelease(component)) {
297 // Yes
298 this.parentSelector.release(component);
299 } else {
300 // No
301 super.release(component);
302 }
303 }
304
305 /**
306 * Does this selector or its parent have the given hint ?
307 */
308 public boolean hasComponent(Object hint) {
309 boolean exists = super.hasComponent(hint);
310 if (!exists && this.parentSelector != null) {
311 exists = this.parentSelector.hasComponent(hint);
312 }
313 return exists;
314 }
315
316 /**
317 * Does this selector declare a given hint? Check is performed on the components declared for this
318 * selector only, and <strong>not</strong> those potentially inherited from the parent selector.
319 *
320 * @param hint the hint to check for
321 * @return <code>true</code> if this selector has the specified hint
322 */
323 protected boolean hasDeclaredComponent(Object hint) {
324 return super.hasComponent(hint);
325 }
326
327 /* (non-Javadoc)
328 * @see org.apache.cocoon.components.ParentAware#setParentInformation(org.apache.avalon.framework.component.ComponentManager, java.lang.String)
329 */
330 public void setParentLocator(ComponentLocator locator)
331 throws ComponentException {
332 if (this.parentSelector != null) {
333 throw new ComponentException(null, "Parent selector is already set");
334 }
335 this.parentLocator = locator;
336 this.parentSelector = (ExtendedComponentSelector) locator.lookup();
337 }
338
339 /* (non-Javadoc)
340 * @see org.apache.avalon.framework.activity.Disposable#dispose()
341 */
342 public void dispose() {
343 super.dispose();
344 if (this.parentLocator != null) {
345 this.parentLocator.release(this.parentSelector);
346 this.parentLocator = null;
347 this.parentSelector = null;
348 }
349 }
350
351 /* (non-Javadoc)
352 * @see org.apache.avalon.excalibur.component.ExcaliburComponentSelector#canRelease(org.apache.avalon.framework.component.Component)
353 */
354 protected boolean canRelease(Component component) {
355 if (this.parentSelector != null && this.parentSelector.canRelease(component)) {
356 return true;
357 }
358
359 return super.canRelease(component);
360 }
361 }