001: /*
002: * $Id: I18nFactorySet.java 471754 2006-11-06 14:55:09Z husted $
003: *
004: * Licensed to the Apache Software Foundation (ASF) under one
005: * or more contributor license agreements. See the NOTICE file
006: * distributed with this work for additional information
007: * regarding copyright ownership. The ASF licenses this file
008: * to you under the Apache License, Version 2.0 (the
009: * "License"); you may not use this file except in compliance
010: * with the License. You may obtain a copy of the License at
011: *
012: * http://www.apache.org/licenses/LICENSE-2.0
013: *
014: * Unless required by applicable law or agreed to in writing,
015: * software distributed under the License is distributed on an
016: * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017: * KIND, either express or implied. See the License for the
018: * specific language governing permissions and limitations
019: * under the License.
020: */
021:
022: package org.apache.struts.tiles.xmlDefinition;
023:
024: import java.io.FileNotFoundException;
025: import java.io.IOException;
026: import java.io.InputStream;
027: import java.util.ArrayList;
028: import java.util.HashMap;
029: import java.util.Iterator;
030: import java.util.List;
031: import java.util.Locale;
032: import java.util.Map;
033: import java.util.StringTokenizer;
034:
035: import javax.servlet.ServletContext;
036: import javax.servlet.ServletRequest;
037: import javax.servlet.http.HttpServletRequest;
038: import javax.servlet.http.HttpSession;
039:
040: import org.apache.commons.logging.Log;
041: import org.apache.commons.logging.LogFactory;
042: import org.apache.struts.tiles.taglib.ComponentConstants;
043: import org.apache.struts.tiles.DefinitionsFactoryException;
044: import org.apache.struts.tiles.FactoryNotFoundException;
045: import org.xml.sax.SAXException;
046:
047: /**
048: * Definitions factory.
049: * This implementation allows to have a set of definition factories.
050: * There is a main factory and one factory for each file associated to a Locale.
051: *
052: * To retrieve a definition, we first search for the appropriate factory using
053: * the Locale found in session context. If no factory is found, use the
054: * default one. Then we ask the factory for the definition.
055: *
056: * A definition factory file is loaded using main filename extended with locale code
057: * (ex : <code>templateDefinitions_fr.xml</code>). If no file is found under this name, use default file.
058: */
059: public class I18nFactorySet extends FactorySet {
060:
061: /**
062: * Commons Logging instance.
063: */
064: protected static Log log = LogFactory.getLog(I18nFactorySet.class);
065:
066: /**
067: * Config file parameter name.
068: */
069: public static final String DEFINITIONS_CONFIG_PARAMETER_NAME = "definitions-config";
070:
071: /**
072: * Config file parameter name.
073: */
074: public static final String PARSER_DETAILS_PARAMETER_NAME = "definitions-parser-details";
075:
076: /**
077: * Config file parameter name.
078: */
079: public static final String PARSER_VALIDATE_PARAMETER_NAME = "definitions-parser-validate";
080:
081: /**
082: * Possible definition filenames.
083: */
084: public static final String DEFAULT_DEFINITION_FILENAMES[] = {
085: "/WEB-INF/tileDefinitions.xml",
086: "/WEB-INF/componentDefinitions.xml",
087: "/WEB-INF/instanceDefinitions.xml" };
088:
089: /**
090: * Default filenames extension.
091: */
092: public static final String FILENAME_EXTENSION = ".xml";
093:
094: /**
095: * Default factory.
096: */
097: protected DefinitionsFactory defaultFactory = null;
098:
099: /**
100: * XML parser used.
101: * Attribute is transient to allow serialization. In this implementaiton,
102: * xmlParser is created each time we need it ;-(.
103: */
104: protected transient XmlParser xmlParser;
105:
106: /**
107: * Do we want validating parser. Default is <code>false</code>.
108: * Can be set from servlet config file.
109: */
110: protected boolean isValidatingParser = false;
111:
112: /**
113: * Parser detail level. Default is 0.
114: * Can be set from servlet config file.
115: */
116: protected int parserDetailLevel = 0;
117:
118: /**
119: * Names of files containing instances descriptions.
120: */
121: private List filenames = null;
122:
123: /**
124: * Collection of already loaded definitions set, referenced by their suffix.
125: */
126: private Map loaded = null;
127:
128: /**
129: * Parameterless Constructor.
130: * Method {@link #initFactory} must be called prior to any use of created factory.
131: */
132: public I18nFactorySet() {
133: super ();
134: }
135:
136: /**
137: * Constructor.
138: * Init the factory by reading appropriate configuration file.
139: * @param servletContext Servlet context.
140: * @param properties Map containing all properties.
141: * @throws FactoryNotFoundException Can't find factory configuration file.
142: */
143: public I18nFactorySet(ServletContext servletContext, Map properties)
144: throws DefinitionsFactoryException {
145:
146: initFactory(servletContext, properties);
147: }
148:
149: /**
150: * Initialization method.
151: * Init the factory by reading appropriate configuration file.
152: * This method is called exactly once immediately after factory creation in
153: * case of internal creation (by DefinitionUtil).
154: * @param servletContext Servlet Context passed to newly created factory.
155: * @param properties Map of name/property passed to newly created factory. Map can contains
156: * more properties than requested.
157: * @throws DefinitionsFactoryException An error occur during initialization.
158: */
159: public void initFactory(ServletContext servletContext,
160: Map properties) throws DefinitionsFactoryException {
161:
162: // Set some property values
163: String value = (String) properties
164: .get(PARSER_VALIDATE_PARAMETER_NAME);
165: if (value != null) {
166: isValidatingParser = Boolean.valueOf(value).booleanValue();
167: }
168:
169: value = (String) properties.get(PARSER_DETAILS_PARAMETER_NAME);
170: if (value != null) {
171: try {
172: parserDetailLevel = Integer.valueOf(value).intValue();
173:
174: } catch (NumberFormatException ex) {
175: log.error("Bad format for parameter '"
176: + PARSER_DETAILS_PARAMETER_NAME
177: + "'. Integer expected.");
178: }
179: }
180:
181: // init factory withappropriate configuration file
182: // Try to use provided filename, if any.
183: // If no filename are provided, try to use default ones.
184: String filename = (String) properties
185: .get(DEFINITIONS_CONFIG_PARAMETER_NAME);
186: if (filename != null) { // Use provided filename
187: try {
188: initFactory(servletContext, filename);
189: if (log.isDebugEnabled()) {
190: log.debug("Factory initialized from file '"
191: + filename + "'.");
192: }
193:
194: } catch (FileNotFoundException ex) { // A filename is specified, throw appropriate error.
195: log.error(ex.getMessage() + " : Can't find file '"
196: + filename + "'");
197: throw new FactoryNotFoundException(ex.getMessage()
198: + " : Can't find file '" + filename + "'");
199: }
200:
201: } else { // try each default file names
202: for (int i = 0; i < DEFAULT_DEFINITION_FILENAMES.length; i++) {
203: filename = DEFAULT_DEFINITION_FILENAMES[i];
204: try {
205: initFactory(servletContext, filename);
206: if (log.isInfoEnabled()) {
207: log.info("Factory initialized from file '"
208: + filename + "'.");
209: }
210: } catch (FileNotFoundException ex) {
211: // Do nothing
212: }
213: }
214: }
215:
216: }
217:
218: /**
219: * Initialization method.
220: * Init the factory by reading appropriate configuration file.
221: * This method is called exactly once immediately after factory creation in
222: * case of internal creation (by DefinitionUtil).
223: * @param servletContext Servlet Context passed to newly created factory.
224: * @param proposedFilename File names, comma separated, to use as base file names.
225: * @throws DefinitionsFactoryException An error occur during initialization.
226: */
227: protected void initFactory(ServletContext servletContext,
228: String proposedFilename)
229: throws DefinitionsFactoryException, FileNotFoundException {
230:
231: // Init list of filenames
232: StringTokenizer tokenizer = new StringTokenizer(
233: proposedFilename, ",");
234: this .filenames = new ArrayList(tokenizer.countTokens());
235: while (tokenizer.hasMoreTokens()) {
236: this .filenames.add(tokenizer.nextToken().trim());
237: }
238:
239: loaded = new HashMap();
240: defaultFactory = createDefaultFactory(servletContext);
241: if (log.isDebugEnabled())
242: log.debug("default factory:" + defaultFactory);
243: }
244:
245: /**
246: * Get default factory.
247: * @return Default factory
248: */
249: protected DefinitionsFactory getDefaultFactory() {
250: return defaultFactory;
251: }
252:
253: /**
254: * Create default factory .
255: * Create InstancesMapper for specified Locale.
256: * If creation failes, use default mapper and log error message.
257: * @param servletContext Current servlet context. Used to open file.
258: * @return Created default definition factory.
259: * @throws DefinitionsFactoryException If an error occur while creating factory.
260: * @throws FileNotFoundException if factory can't be loaded from filenames.
261: */
262: protected DefinitionsFactory createDefaultFactory(
263: ServletContext servletContext)
264: throws DefinitionsFactoryException, FileNotFoundException {
265:
266: XmlDefinitionsSet rootXmlConfig = parseXmlFiles(servletContext,
267: "", null);
268: if (rootXmlConfig == null) {
269: throw new FileNotFoundException();
270: }
271:
272: rootXmlConfig.resolveInheritances();
273:
274: if (log.isDebugEnabled()) {
275: log.debug(rootXmlConfig);
276: }
277:
278: DefinitionsFactory factory = new DefinitionsFactory(
279: rootXmlConfig);
280: if (log.isDebugEnabled()) {
281: log.debug("factory loaded : " + factory);
282: }
283:
284: return factory;
285: }
286:
287: /**
288: * Extract key that will be used to get the sub factory.
289: * @param name Name of requested definition
290: * @param request Current servlet request.
291: * @param servletContext Current servlet context.
292: * @return the key or <code>null</code> if not found.
293: */
294: protected Object getDefinitionsFactoryKey(String name,
295: ServletRequest request, ServletContext servletContext) {
296:
297: Locale locale = null;
298: try {
299: HttpSession session = ((HttpServletRequest) request)
300: .getSession(false);
301: if (session != null) {
302: locale = (Locale) session
303: .getAttribute(ComponentConstants.LOCALE_KEY);
304: }
305:
306: } catch (ClassCastException ex) {
307: log.error("I18nFactorySet.getDefinitionsFactoryKey");
308: ex.printStackTrace();
309: }
310:
311: return locale;
312: }
313:
314: /**
315: * Create a factory for specified key.
316: * If creation failes, return default factory and log an error message.
317: * @param key The key.
318: * @param request Servlet request.
319: * @param servletContext Servlet context.
320: * @return Definition factory for specified key.
321: * @throws DefinitionsFactoryException If an error occur while creating factory.
322: */
323: protected DefinitionsFactory createFactory(Object key,
324: ServletRequest request, ServletContext servletContext)
325: throws DefinitionsFactoryException {
326:
327: if (key == null) {
328: return getDefaultFactory();
329: }
330:
331: // Build possible postfixes
332: List possiblePostfixes = calculateSuffixes((Locale) key);
333:
334: // Search last postix corresponding to a config file to load.
335: // First check if something is loaded for this postfix.
336: // If not, try to load its config.
337: XmlDefinitionsSet lastXmlFile = null;
338: DefinitionsFactory factory = null;
339: String curPostfix = null;
340: int i = 0;
341:
342: for (i = possiblePostfixes.size() - 1; i >= 0; i--) {
343: curPostfix = (String) possiblePostfixes.get(i);
344:
345: // Already loaded ?
346: factory = (DefinitionsFactory) loaded.get(curPostfix);
347: if (factory != null) { // yes, stop search
348: return factory;
349: }
350:
351: // Try to load it. If success, stop search
352: lastXmlFile = parseXmlFiles(servletContext, curPostfix,
353: null);
354: if (lastXmlFile != null) {
355: break;
356: }
357: }
358:
359: // Have we found a description file ?
360: // If no, return default one
361: if (lastXmlFile == null) {
362: return getDefaultFactory();
363: }
364:
365: // We found something. Need to load base and intermediate files
366: String lastPostfix = curPostfix;
367: XmlDefinitionsSet rootXmlConfig = parseXmlFiles(servletContext,
368: "", null);
369: for (int j = 0; j < i; j++) {
370: curPostfix = (String) possiblePostfixes.get(j);
371: parseXmlFiles(servletContext, curPostfix, rootXmlConfig);
372: }
373:
374: rootXmlConfig.extend(lastXmlFile);
375: rootXmlConfig.resolveInheritances();
376:
377: factory = new DefinitionsFactory(rootXmlConfig);
378: loaded.put(lastPostfix, factory);
379:
380: if (log.isDebugEnabled()) {
381: log.debug("factory loaded : " + factory);
382: }
383:
384: // return last available found !
385: return factory;
386: }
387:
388: /**
389: * Calculate the suffixes based on the locale.
390: * @param locale the locale
391: */
392: private List calculateSuffixes(Locale locale) {
393:
394: List suffixes = new ArrayList(3);
395: String language = locale.getLanguage();
396: String country = locale.getCountry();
397: String variant = locale.getVariant();
398:
399: StringBuffer suffix = new StringBuffer();
400: suffix.append('_');
401: suffix.append(language);
402: if (language.length() > 0) {
403: suffixes.add(suffix.toString());
404: }
405:
406: suffix.append('_');
407: suffix.append(country);
408: if (country.length() > 0) {
409: suffixes.add(suffix.toString());
410: }
411:
412: suffix.append('_');
413: suffix.append(variant);
414: if (variant.length() > 0) {
415: suffixes.add(suffix.toString());
416: }
417:
418: return suffixes;
419:
420: }
421:
422: /**
423: * Parse files associated to postix if they exist.
424: * For each name in filenames, append postfix before file extension,
425: * then try to load the corresponding file.
426: * If file doesn't exist, try next one. Each file description is added to
427: * the XmlDefinitionsSet description.
428: * The XmlDefinitionsSet description is created only if there is a definition file.
429: * Inheritance is not resolved in the returned XmlDefinitionsSet.
430: * If no description file can be opened and no definiion set is provided, return <code>null</code>.
431: * @param postfix Postfix to add to each description file.
432: * @param xmlDefinitions Definitions set to which definitions will be added. If <code>null</code>, a definitions
433: * set is created on request.
434: * @return XmlDefinitionsSet The definitions set created or passed as parameter.
435: * @throws DefinitionsFactoryException On errors parsing file.
436: */
437: protected XmlDefinitionsSet parseXmlFiles(
438: ServletContext servletContext, String postfix,
439: XmlDefinitionsSet xmlDefinitions)
440: throws DefinitionsFactoryException {
441:
442: if (postfix != null && postfix.length() == 0) {
443: postfix = null;
444: }
445:
446: // Iterate throw each file name in list
447: Iterator i = filenames.iterator();
448: while (i.hasNext()) {
449: String filename = concatPostfix((String) i.next(), postfix);
450: xmlDefinitions = parseXmlFile(servletContext, filename,
451: xmlDefinitions);
452: }
453:
454: return xmlDefinitions;
455: }
456:
457: /**
458: * Parse specified xml file and add definition to specified definitions set.
459: * This method is used to load several description files in one instances list.
460: * If filename exists and definition set is <code>null</code>, create a new set. Otherwise, return
461: * passed definition set (can be <code>null</code>).
462: * @param servletContext Current servlet context. Used to open file.
463: * @param filename Name of file to parse.
464: * @param xmlDefinitions Definitions set to which definitions will be added. If null, a definitions
465: * set is created on request.
466: * @return XmlDefinitionsSet The definitions set created or passed as parameter.
467: * @throws DefinitionsFactoryException On errors parsing file.
468: */
469: protected XmlDefinitionsSet parseXmlFile(
470: ServletContext servletContext, String filename,
471: XmlDefinitionsSet xmlDefinitions)
472: throws DefinitionsFactoryException {
473:
474: try {
475: InputStream input = servletContext
476: .getResourceAsStream(filename);
477: // Try to load using real path.
478: // This allow to load config file under websphere 3.5.x
479: // Patch proposed Houston, Stephen (LIT) on 5 Apr 2002
480: if (null == input) {
481: try {
482: input = new java.io.FileInputStream(servletContext
483: .getRealPath(filename));
484: } catch (Exception e) {
485: }
486: }
487:
488: // If the config isn't in the servlet context, try the class loader
489: // which allows the config files to be stored in a jar
490: if (input == null) {
491: input = getClass().getResourceAsStream(filename);
492: }
493:
494: // If still nothing found, this mean no config file is associated
495: if (input == null) {
496: if (log.isDebugEnabled()) {
497: log.debug("Can't open file '" + filename + "'");
498: }
499: return xmlDefinitions;
500: }
501:
502: // Check if parser already exist.
503: // Doesn't seem to work yet.
504: //if( xmlParser == null )
505: if (true) {
506: xmlParser = new XmlParser();
507: xmlParser.setValidating(isValidatingParser);
508: }
509:
510: // Check if definition set already exist.
511: if (xmlDefinitions == null) {
512: xmlDefinitions = new XmlDefinitionsSet();
513: }
514:
515: xmlParser.parse(input, xmlDefinitions);
516:
517: } catch (SAXException ex) {
518: if (log.isDebugEnabled()) {
519: log.debug("Error while parsing file '" + filename
520: + "'.");
521: ex.printStackTrace();
522: }
523: throw new DefinitionsFactoryException(
524: "Error while parsing file '" + filename + "'. "
525: + ex.getMessage(), ex);
526:
527: } catch (IOException ex) {
528: throw new DefinitionsFactoryException(
529: "IO Error while parsing file '" + filename + "'. "
530: + ex.getMessage(), ex);
531: }
532:
533: return xmlDefinitions;
534: }
535:
536: /**
537: * Concat postfix to the name. Take care of existing filename extension.
538: * Transform the given name "name.ext" to have "name" + "postfix" + "ext".
539: * If there is no ext, return "name" + "postfix".
540: * @param name Filename.
541: * @param postfix Postfix to add.
542: * @return Concatenated filename.
543: */
544: private String concatPostfix(String name, String postfix) {
545: if (postfix == null) {
546: return name;
547: }
548:
549: // Search file name extension.
550: // take care of Unix files starting with .
551: int dotIndex = name.lastIndexOf(".");
552: int lastNameStart = name
553: .lastIndexOf(java.io.File.pathSeparator);
554: if (dotIndex < 1 || dotIndex < lastNameStart) {
555: return name + postfix;
556: }
557:
558: String ext = name.substring(dotIndex);
559: name = name.substring(0, dotIndex);
560: return name + postfix + ext;
561: }
562:
563: /**
564: * Return String representation.
565: * @return String representation.
566: */
567: public String toString() {
568: StringBuffer buff = new StringBuffer("I18nFactorySet : \n");
569: buff.append("--- default factory ---\n");
570: buff.append(defaultFactory.toString());
571: buff.append("\n--- other factories ---\n");
572: Iterator i = factories.values().iterator();
573: while (i.hasNext()) {
574: buff.append(i.next().toString()).append("---------- \n");
575: }
576: return buff.toString();
577: }
578:
579: }
|