1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 package org.apache.tiles.impl.mgmt;
22
23 import java.util.HashMap;
24 import java.util.Map;
25 import java.util.Set;
26
27 import org.apache.tiles.Attribute;
28 import org.apache.tiles.Definition;
29 import org.apache.tiles.context.TilesRequestContext;
30 import org.apache.tiles.definition.DefinitionsFactory;
31 import org.apache.tiles.definition.NoSuchDefinitionException;
32 import org.slf4j.Logger;
33 import org.slf4j.LoggerFactory;
34
35 /***
36 * Manages custom and configured definitions, so they can be used by the
37 * container, instead of using a simple {@link DefinitionsFactory}.
38 *
39 * @version $Rev: 791161 $ $Date: 2009-07-04 20:53:36 +0200 (sab, 04 lug 2009) $
40 */
41 public class DefinitionManager {
42
43 /***
44 * The logging object.
45 */
46 private final Logger log = LoggerFactory.getLogger(DefinitionManager.class);
47
48 /***
49 * The default name of the attribute in which storing custom definitions.
50 */
51 private static final String DEFAULT_DEFINITIONS_ATTRIBUTE_NAME =
52 "org.apache.tiles.impl.mgmt.DefinitionManager.DEFINITIONS";
53
54 /***
55 * The definitions factory to use to get main definitions.
56 */
57 private DefinitionsFactory factory;
58
59 /***
60 * The name of the attribute in which storing custom definitions.
61 */
62 private String definitionsAttributeName;
63
64 /***
65 * Constructor.
66 */
67 public DefinitionManager() {
68 definitionsAttributeName = DEFAULT_DEFINITIONS_ATTRIBUTE_NAME;
69 }
70
71 /***
72 * Constructor.
73 *
74 * @param definitionsAttributeName The name of the attribute in which
75 * storing custom definitions.
76 */
77 public DefinitionManager(String definitionsAttributeName) {
78 this.definitionsAttributeName = definitionsAttributeName;
79 if (this.definitionsAttributeName == null) {
80 this.definitionsAttributeName = DEFAULT_DEFINITIONS_ATTRIBUTE_NAME;
81 }
82 }
83
84 /***
85 * Returns the used definitions factory.
86 *
87 * @return The used definitions factory.
88 */
89 public DefinitionsFactory getFactory() {
90 return factory;
91 }
92
93 /***
94 * Sets the definitions factory to use.
95 *
96 * @param factory The definitions factory.
97 */
98 public void setFactory(DefinitionsFactory factory) {
99 this.factory = factory;
100 }
101
102 /***
103 * Returns a definition by name.
104 *
105 * @param definition The name of the definition.
106 * @param request The current request.
107 * @return The requested definition, either main or custom.
108 * @throws org.apache.tiles.definition.DefinitionsFactoryException If
109 * something goes wrong when obtaining a main definition.
110 */
111 public Definition getDefinition(String definition,
112 TilesRequestContext request) {
113 Map<String, Definition> definitions =
114 getDefinitions(request);
115 if (definitions != null && definitions.containsKey(definition)) {
116 return definitions.get(definition);
117 }
118 return getFactory().getDefinition(definition, request);
119 }
120
121 /***
122 * Adds a definition to the set of custom ones.
123 *
124 * @param definition The definition to add.
125 * @param request The current request.
126 * @throws org.apache.tiles.definition.DefinitionsFactoryException If
127 * something goes wrong during the addition.
128 */
129 public void addDefinition(Definition definition,
130 TilesRequestContext request) {
131 Map<String, Definition> definitions = getOrCreateDefinitions(request);
132 if (definition.getName() == null) {
133 definition.setName(getNextUniqueDefinitionName(definitions));
134 }
135 validate(definition);
136
137 if (definition.isExtending()) {
138 this.resolveInheritance(definition, request);
139 }
140
141 definitions.put(definition.getName(), definition);
142 }
143
144 /***
145 * Validates a custom definition.
146 *
147 * @param definition The definition to validate.
148 */
149 private void validate(Definition definition) {
150 Set<String> names = definition.getLocalAttributeNames();
151 if (names != null) {
152 for (String name : names) {
153 Attribute attribute = definition.getLocalAttribute(name);
154 if (attribute.getValue() == null) {
155 throw new IllegalArgumentException(
156 "Attribute '" + name + "' value not defined");
157 }
158 }
159 }
160 names = definition.getCascadedAttributeNames();
161 if (names != null) {
162 for (String name : names) {
163 Attribute attribute = definition.getCascadedAttribute(name);
164 if (attribute.getValue() == null) {
165 throw new IllegalArgumentException(
166 "Attribute '" + name + "' value not defined");
167 }
168 }
169 }
170 }
171
172 /***
173 * Resolve inheritance.
174 * First, resolve parent's inheritance, then set template to the parent's
175 * template.
176 * Also copy attributes setted in parent, and not set in child
177 * If instance doesn't extend anything, do nothing.
178 *
179 * @param definition The definition that needs to have its inheritances
180 * resolved.
181 * @param request The current request.
182 * @throws org.apache.tiles.definition.DefinitionsFactoryException If an
183 * inheritance can not be solved.
184 */
185 protected void resolveInheritance(Definition definition,
186 TilesRequestContext request) {
187
188 if (!definition.isExtending()) {
189 return;
190 }
191
192 if (log.isDebugEnabled()) {
193 log.debug("Resolve definition for child name='"
194 + definition.getName()
195 + "' extends='" + definition.getExtends() + "'.");
196 }
197
198
199
200
201
202 Definition parent = getDefinition(definition.getExtends(), request);
203
204 if (parent == null) {
205 String msg = "Error while resolving definition inheritance: child '"
206 + definition.getName()
207 + "' can't find its ancestor '"
208 + definition.getExtends()
209 + "'. Please check your description file.";
210 log.error(msg);
211
212 throw new NoSuchDefinitionException(msg);
213 }
214
215
216 resolveInheritance(parent, request);
217 definition.inherit(parent);
218 }
219
220 /***
221 * Overloads a child definition with a given parent.
222 * All attributes present in child are kept. All missing attributes are
223 * copied from the parent.
224 * Special attribute 'template','role' and 'extends' are overloaded in child
225 * if not defined
226 *
227 * @param parent The parent definition.
228 * @param child The child that will be overloaded.
229 * @deprecated Use {@link Definition#inherit(org.apache.tiles.BasicAttributeContext)}.
230 */
231 protected void overload(Definition parent, Definition child) {
232 child.inherit(parent);
233 }
234
235 /***
236 * Returns the map with custom definitions for the current request.
237 *
238 * @param request The current request.
239 * @return A map that connects a definition name to a definition.
240 */
241 @SuppressWarnings("unchecked")
242 protected Map<String, Definition> getDefinitions(
243 TilesRequestContext request) {
244 return (Map<String, Definition>) request.getRequestScope()
245 .get(definitionsAttributeName);
246 }
247
248 /***
249 * Returns a map of type "definition name -> definition" and, if it has not
250 * been defined before, creates one.
251 *
252 * @param request The current request.
253 * @return A map that connects a definition name to a definition.
254 */
255 @SuppressWarnings("unchecked")
256 protected Map<String, Definition> getOrCreateDefinitions(
257 TilesRequestContext request) {
258 Map<String, Definition> definitions =
259 (Map<String, Definition>) request
260 .getRequestScope().get(definitionsAttributeName);
261 if (definitions == null) {
262 definitions = new HashMap<String, Definition>();
263 request.getRequestScope()
264 .put(definitionsAttributeName, definitions);
265 }
266
267 return definitions;
268 }
269
270 /***
271 * Create a unique definition name usable to store anonymous definitions.
272 *
273 * @param definitions The already created definitions.
274 * @return The unique definition name to be used to store the definition.
275 * @since 2.1.0
276 */
277 protected String getNextUniqueDefinitionName(
278 Map<String, Definition> definitions) {
279 String candidate;
280 int anonymousDefinitionIndex = 1;
281
282 do {
283 candidate = "$anonymousMutableDefinition" + anonymousDefinitionIndex;
284 anonymousDefinitionIndex++;
285 } while (definitions.containsKey(candidate));
286
287 return candidate;
288 }
289 }