1 /*
2 * $Id$
3 *
4 * Licensed to the Apache Software Foundation (ASF) under one
5 * or more contributor license agreements. See the NOTICE file
6 * distributed with this work for additional information
7 * regarding copyright ownership. The ASF licenses this file
8 * to you under the Apache License, Version 2.0 (the
9 * "License"); you may not use this file except in compliance
10 * with the License. You may obtain a copy of the License at
11 *
12 * http://www.apache.org/licenses/LICENSE-2.0
13 *
14 * Unless required by applicable law or agreed to in writing,
15 * software distributed under the License is distributed on an
16 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17 * KIND, either express or implied. See the License for the
18 * specific language governing permissions and limitations
19 * under the License.
20 */
21
22 package org.apache.struts.tiles;
23
24 import java.io.Serializable;
25 import java.lang.reflect.InvocationTargetException;
26 import java.util.HashMap;
27 import java.util.Map;
28
29 import org.apache.struts.tiles.xmlDefinition.XmlDefinition;
30 import org.apache.struts.util.RequestUtils;
31 import org.slf4j.Logger;
32 import org.slf4j.LoggerFactory;
33
34 /**
35 * Definition of a template / component attributes.
36 * Attributes of a component can be defined with the help of this class.
37 * An instance of this class can be used as a bean, and passed to 'insert' tag.
38 */
39 public class ComponentDefinition implements Serializable {
40 private static final long serialVersionUID = -62457661179275424L;
41
42 /**
43 * The {@code Log} instance for this class.
44 */
45 private final static Logger LOG =
46 LoggerFactory.getLogger(ComponentDefinition.class);
47
48 /**
49 * Definition name
50 */
51 protected String name = null;
52
53 /**
54 * Component / template path (URL).
55 */
56 protected String path = null;
57
58 /**
59 * Attributes defined for the component.
60 */
61 protected Map<String, Object> attributes = null;
62
63 /**
64 * Role associated to definition.
65 */
66 protected String role = null;
67
68 /** Associated Controller URL or classname, if defined */
69 protected String controller = null;
70
71 /**
72 * Associated Controller typename, if controllerName defined.
73 * Can be CONTROLLER, ACTION or URL, or null.
74 */
75 protected String controllerType = null;
76
77 /**
78 * Controller name type.
79 */
80 public static final String URL = "url";
81
82 /**
83 * Controller name type.
84 */
85 public static final String CONTROLLER = "controller";
86
87 /**
88 * Controller name type.
89 */
90 public static final String ACTION = "action";
91
92 /**
93 * Controller associated to Definition.
94 * Lazy creation : only on first request
95 */
96 private Controller controllerInstance = null;
97
98 /**
99 * Constructor.
100 */
101 public ComponentDefinition() {
102 attributes = new HashMap<>();
103 }
104
105 /**
106 * Copy Constructor.
107 * Create a new definition initialized with parent definition.
108 * Do a shallow copy : attributes are shared between copies, but not the Map
109 * containing attributes.
110 */
111 public ComponentDefinition(ComponentDefinition definition) {
112 attributes = new HashMap<>(definition.getAttributes());
113 this.name = definition.getName();
114 this.path = definition.getPath();
115 this.role = definition.getRole();
116 this.controllerInstance = definition.getControllerInstance();
117 this.controller = definition.getController();
118 this.controllerType = definition.getControllerType();
119 }
120
121 /**
122 * Constructor.
123 * Create a new definition initialized from a RawDefinition.
124 * Raw definitions are used to read definition from a data source (xml file, db, ...).
125 * A RawDefinition mainly contains properties of type String, while Definition
126 * contains more complex type (ex : Controller).
127 * Do a shallow copy : attributes are shared between objects, but not the Map
128 * containing attributes.
129 * OO Design issues : Actually RawDefinition (XmlDefinition) extends ComponentDefinition.
130 * This must not be the case. I have do it because I am lazy.
131 * @throws InstantiationException if an error occur while instantiating Controller :
132 * (classname can't be instantiated, Illegal access with instantiated class,
133 * Error while instantiating class, classname can't be instantiated.
134 */
135 public ComponentDefinition(XmlDefinition definition) {
136
137 this((ComponentDefinition) definition);
138 }
139
140 /**
141 * Constructor.
142 */
143 public ComponentDefinition(String name, String path, Map<String, Object> attributes) {
144 this.name = name;
145 this.path = path;
146 this.attributes = attributes;
147 }
148
149 /**
150 * Access method for the name property.
151 *
152 * @return the current value of the name property
153 */
154 public String getName() {
155 return name;
156 }
157
158 /**
159 * Sets the value of the name property.
160 *
161 * @param aName the new value of the name property
162 */
163 public void setName(String aName) {
164 name = aName;
165 }
166
167 /**
168 * Access method for the path property.
169 *
170 * @return The current value of the path property.
171 */
172 public String getPage() {
173 return path;
174 }
175
176 /**
177 * Sets the value of the path property.
178 *
179 * @param page the new value of the path property
180 */
181 public void setPage(String page) {
182 path = page;
183 }
184
185 /**
186 * Access method for the path property.
187 *
188 * @return the current value of the path property
189 */
190 public String getPath() {
191 return path;
192 }
193
194 /**
195 * Sets the value of the path property.
196 *
197 * @param aPath the new value of the path property
198 */
199 public void setPath(String aPath) {
200 path = aPath;
201 }
202
203 /**
204 * Access method for the template property.
205 * Same as getPath()
206 * @return the current value of the template property
207 */
208 public String getTemplate() {
209 return path;
210 }
211
212 /**
213 * Sets the value of the template property.
214 * Same as setPath()
215 *
216 * @param template the new value of the path property
217 */
218 public void setTemplate(String template) {
219 path = template;
220 }
221
222 /**
223 * Access method for the role property.
224 * @return the current value of the role property
225 */
226 public String getRole() {
227 return role;
228 }
229
230 /**
231 * Sets the value of the role property.
232 *
233 * @param role the new value of the path property
234 */
235 public void setRole(String role) {
236 this.role = role;
237 }
238
239 /**
240 * Access method for the attributes property.
241 * If there is no attributes, return an empty map.
242 * @return the current value of the attributes property
243 */
244 public Map<String, Object> getAttributes() {
245 return attributes;
246 }
247
248 /**
249 * Returns the value of the named attribute as an Object, or null if no
250 * attribute of the given name exists.
251 *
252 * @return requested attribute or null if not found
253 */
254 public Object getAttribute(String key) {
255 return attributes.get(key);
256 }
257
258 /**
259 * Put a new attribute in this component
260 *
261 * @param key String key for attribute
262 * @param value Attibute value.
263 */
264 public void putAttribute(String key, Object value) {
265 attributes.put(key, value);
266 }
267
268 /**
269 * Put an attribute in component / template definition.
270 * Attribute can be used as content for tag get.
271 * @param name Attribute name
272 * @param content Attribute value
273 */
274 public void put(String name, Object content) {
275 put(name, content, false, null);
276 }
277
278 /**
279 * Put an attribute in template definition.
280 * Attribute can be used as content for tag get.
281 * @param name Attribute name
282 * @param content Attribute value
283 * @param direct Determines how content is handled by get tag: true means content is printed directly; false, the default, means content is included
284 */
285 public void put(String name, Object content, boolean direct) {
286 put(name, content, direct, null);
287 }
288
289 /**
290 * Put an attribute in template definition.
291 * Attribute can be used as content for tag get.
292 * @param name Attribute name
293 * @param content Attribute value
294 * @param direct Determines how content is handled by get tag: true means content is printed directly; false, the default, means content is included
295 * @param role Determine if content is used by get tag. If user is in role, content is used.
296 */
297 public void put(String name, Object content, boolean direct, String role) {
298 if (direct == true) { // direct String
299 put(name, content, "string", role);
300 } else {
301 put(name, content, "template", role);
302 }
303
304 }
305
306 /**
307 * Put an attribute in template definition.
308 * Attribute can be used as content for tag get.
309 * @param name Attribute name
310 * @param content Attribute value
311 * @param type attribute type: template, string, definition
312 * @param role Determine if content is used by get tag. If user is in role, content is used.
313 */
314 public void put(String name, Object content, String type, String role) {
315 // Is there a type set ?
316 // First check direct attribute, and translate it to a valueType.
317 // Then, evaluate valueType, and create requested typed attribute.
318 AttributeDefinition attribute = null;
319
320 if (content != null
321 && type != null
322 && !(content instanceof AttributeDefinition)) {
323
324 String strValue = content.toString();
325 if (type.equalsIgnoreCase("string")) {
326 attribute = new DirectStringAttribute(strValue);
327
328 } else if (type.equalsIgnoreCase("page")) {
329 attribute = new PathAttribute(strValue);
330
331 } else if (type.equalsIgnoreCase("template")) {
332 attribute = new PathAttribute(strValue);
333
334 } else if (type.equalsIgnoreCase("instance")) {
335 attribute = new DefinitionNameAttribute(strValue);
336
337 } else if (type.equalsIgnoreCase("definition")) {
338 attribute = new DefinitionNameAttribute(strValue);
339 }
340 }
341
342 putAttribute(name, attribute);
343 }
344
345 /**
346 * Returns a description of the attributes.
347 */
348 public String toString() {
349 return "{name="
350 + name
351 + ", path="
352 + path
353 + ", role="
354 + role
355 + ", controller="
356 + controller
357 + ", controllerType="
358 + controllerType
359 + ", controllerInstance="
360 + controllerInstance
361 + ", attributes="
362 + attributes
363 + "}\n";
364 }
365
366 /**
367 * Get associated controller type.
368 * Type denote a fully qualified classname.
369 */
370 public String getControllerType() {
371 return controllerType;
372 }
373
374 /**
375 * Set associated controller type.
376 * Type denote a fully qualified classname.
377 * @param controllerType Typeof associated controller
378 */
379 public void setControllerType(String controllerType) {
380 this.controllerType = controllerType;
381 }
382
383 /**
384 * Set associated controller name as an url, and controller
385 * type as "url".
386 * Name must be an url (not checked).
387 * Convenience method.
388 * @param controller Controller url
389 */
390 public void setControllerUrl(String controller) {
391 setController(controller);
392 setControllerType("url");
393 }
394
395 /**
396 * Set associated controller name as a classtype, and controller
397 * type as "classname".
398 * Name denote a fully qualified classname
399 * Convenience method.
400 * @param controller Controller classname.
401 */
402 public void setControllerClass(String controller) {
403 setController(controller);
404 setControllerType("classname");
405 }
406
407 /**
408 * Get associated controller local URL.
409 * URL should be local to webcontainer in order to allow request context followup.
410 * URL comes as a string.
411 */
412 public String getController() {
413 return controller;
414 }
415
416 /**
417 * Set associated controller URL.
418 * URL should be local to webcontainer in order to allow request context followup.
419 * URL is specified as a string.
420 * @param url Url called locally
421 */
422 public void setController(String url) {
423 this.controller = url;
424 }
425
426 /**
427 * Get controller instance.
428 * @return controller instance.
429 */
430 public Controller getControllerInstance() {
431 return controllerInstance;
432 }
433
434 /**
435 * Get or create controller.
436 * Get controller, create it if necessary.
437 * @return controller if controller or controllerType is set, null otherwise.
438 * @throws InstantiationException if an error occur while instanciating Controller :
439 * (classname can't be instanciated, Illegal access with instanciated class,
440 * Error while instanciating class, classname can't be instanciated.
441 */
442 public Controller getOrCreateController() throws InstantiationException {
443
444 if (controllerInstance != null) {
445 return controllerInstance;
446 }
447
448 // Do we define a controller ?
449 if (controller == null && controllerType == null) {
450 return null;
451 }
452
453 // check parameters
454 if (controllerType != null && controller == null) {
455 throw new InstantiationException("Controller name should be defined if controllerType is set");
456 }
457
458 controllerInstance = createController(controller, controllerType);
459
460 return controllerInstance;
461 }
462
463 /**
464 * Set controller.
465 */
466 public void setControllerInstance(Controller controller) {
467 this.controllerInstance = controller;
468 }
469
470 /**
471 * Create a new instance of controller named in parameter.
472 * If controllerType is specified, create controller accordingly.
473 * Otherwise, if name denote a classname, create an instance of it. If class is
474 * subclass of org.apache.struts.action.Action, wrap controller
475 * appropriately.
476 * Otherwise, consider name as an url.
477 * @param name Controller name (classname, url, ...)
478 * @param controllerType Expected Controller type
479 * @return org.apache.struts.tiles.Controller
480 * @throws InstantiationException if an error occur while instanciating Controller :
481 * (classname can't be instanciated, Illegal access with instanciated class,
482 * Error while instanciating class, classname can't be instanciated.
483 */
484 public static Controller createController(String name, String controllerType)
485 throws InstantiationException {
486
487 LOG.debug("Create controller name={}, type={}", name, controllerType);
488
489 Controller controller = null;
490
491 if (controllerType == null) { // first try as a classname
492 try {
493 return createControllerFromClassname(name);
494
495 } catch (InstantiationException ex) { // ok, try something else
496 controller = new UrlController(name);
497 }
498
499 } else if ("url".equalsIgnoreCase(controllerType)) {
500 controller = new UrlController(name);
501
502 } else if ("classname".equalsIgnoreCase(controllerType)) {
503 controller = createControllerFromClassname(name);
504 }
505
506 return controller;
507 }
508
509 /**
510 * Create a controller from specified classname.
511 *
512 * @param classname Controller classname.
513 *
514 * @return org.apache.struts.tiles.Controller
515 *
516 * @throws InstantiationException if an error occur while instanciating Controller:
517 * (classname can't be instantiated, Illegal access with instantiated class,
518 * Error while instantiating class, classname can't be instantiated.
519 */
520 public static Controller createControllerFromClassname(String classname)
521 throws InstantiationException {
522
523 try {
524 Class<?> requestedClass = RequestUtils.applicationClass(classname);
525 Object instance = requestedClass.getDeclaredConstructor().newInstance();
526
527 LOG.debug("Controller created : {}", instance);
528 return (Controller) instance;
529
530 } catch (ClassNotFoundException ex) {
531 InstantiationException e2 = new InstantiationException(
532 "Error - Class not found :" + classname);
533 e2.initCause(ex);
534 throw e2;
535
536 } catch (IllegalAccessException ex) {
537 InstantiationException e2 = new InstantiationException(
538 "Error - Illegal class access :" + classname);
539 e2.initCause(ex);
540 throw e2;
541
542 } catch (IllegalArgumentException ex) {
543 InstantiationException e2 = new InstantiationException(
544 "Error - Illegal class argument :" + classname);
545 e2.initCause(ex);
546 throw e2;
547
548 } catch (InvocationTargetException ex) {
549 InstantiationException e2 = new InstantiationException(
550 "Error - Invocation class target :" + classname);
551 e2.initCause(ex);
552 throw e2;
553
554 } catch (NoSuchMethodException ex) {
555 InstantiationException e2 = new InstantiationException(
556 "Error - No such method in class:" + classname);
557 e2.initCause(ex);
558 throw e2;
559
560 } catch (SecurityException ex) {
561 InstantiationException e2 = new InstantiationException(
562 "Error - Security exception in class :" + classname);
563 e2.initCause(ex);
564 throw e2;
565
566 } catch (InstantiationException ex) {
567 throw ex;
568
569 } catch (ClassCastException ex) {
570 InstantiationException e2 = new InstantiationException(
571 "Controller of class '"
572 + classname
573 + "' should implements 'Controller' or extends 'Action'");
574 e2.initCause(ex);
575 throw e2;
576 }
577 }
578 }