View Javadoc
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 }