001: /*
002: * Copyright 2006 Luca Garulli (luca.garulli@assetdata.it)
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016:
017: package org.romaframework.aspect.i18n.rb;
018:
019: import java.io.File;
020: import java.io.FilenameFilter;
021: import java.text.DateFormat;
022: import java.text.SimpleDateFormat;
023: import java.util.ArrayList;
024: import java.util.Collection;
025: import java.util.HashMap;
026: import java.util.List;
027: import java.util.Locale;
028: import java.util.MissingResourceException;
029: import java.util.ResourceBundle;
030:
031: import org.romaframework.aspect.i18n.I18NAspect;
032: import org.romaframework.aspect.i18n.I18NAspectAbstract;
033: import org.romaframework.aspect.i18n.feature.I18NFieldFeatures;
034: import org.romaframework.aspect.session.SessionAspect;
035: import org.romaframework.aspect.session.SessionInfo;
036: import org.romaframework.core.GlobalConstants;
037: import org.romaframework.core.Utility;
038: import org.romaframework.core.config.Configurable;
039: import org.romaframework.core.config.RomaApplicationContext;
040: import org.romaframework.core.flow.Controller;
041: import org.romaframework.core.flow.UserObjectEventListener;
042: import org.romaframework.core.resource.AutoReloadListener;
043: import org.romaframework.core.resource.AutoReloadManager;
044: import org.romaframework.core.schema.SchemaClass;
045: import org.romaframework.core.schema.SchemaElement;
046: import org.romaframework.core.schema.SchemaField;
047: import org.romaframework.core.schema.SchemaObject;
048:
049: /**
050: * Internazionalization Manager. It gets the label checking in the configured I18N resource bundle.
051: *
052: * @author Luca Garulli (luca.garulli@assetdata.it), Luigi Dell'Aquila (luigi.dellaquila@assetdata.it)
053: */
054: public class I18NAspectResourceBundleImpl extends I18NAspectAbstract
055: implements UserObjectEventListener, AutoReloadListener {
056:
057: public static final String DEFAULT_FILE_NAME = "default_messages";
058: public static final String DOMAIN_NAME = "domain";
059: public static final String DOMAIN_FILE_NAME = "domain_messages";
060: public static final String SEPARATOR = CONTEXT_SEPARATOR;
061: public static final String LABEL_NAME = "label";
062:
063: private static final String DATE_TIME_FORMAT_VAR = "Application.DateTimeFormat";
064: private static final String TIME_FORMAT_VAR = "Application.TimeFormat";
065: private static final String DATE_FORMAT_VAR = "Application.DateFormat";
066:
067: private HashMap<String, I18NLocaleContext> resources;
068: private AutoReloadManager autoReloadManager;
069:
070: public I18NAspectResourceBundleImpl() {
071: this (null);
072: }
073:
074: public I18NAspectResourceBundleImpl(
075: AutoReloadManager iAutoReloadManager) {
076: autoReloadManager = iAutoReloadManager;
077:
078: resources = new HashMap<String, I18NLocaleContext>();
079:
080: String i18nPkg = Utility
081: .getApplicationAspectPackage(aspectName());
082: String i18nDir = Utility.getResourcePath(i18nPkg)
083: + Utility.PATH_SEPARATOR;
084: String i18nAbsoluteDir = Utility.PATH_SEPARATOR_STRING
085: + I18NAspectResourceBundleImpl.class.getResource(
086: Utility.PATH_SEPARATOR_STRING + i18nDir)
087: .getFile();
088:
089: for (String fileName : getAllResources(i18nAbsoluteDir)) {
090: fileName = fileName.substring(0, fileName.length()
091: - ".properties".length()); // TODO create a constant for ".properties"
092: addConfiguration(fileName, i18nPkg
093: + Utility.PACKAGE_SEPARATOR + fileName);
094: }
095: }
096:
097: /**
098: * returns all the resource file names contained in the path
099: *
100: * @param absoluteDir
101: * the path where to search for resources
102: * @return all the resource file names contained in the path
103: */
104: private List<String> getAllResources(String absoluteDir) {
105: List<String> returnList = new ArrayList<String>();
106: try {
107: File dir = new File(absoluteDir);
108: if (!dir.exists())
109: return returnList;
110: if (!dir.isDirectory())
111: return returnList;
112: FilenameFilter filter = new FilenameFilter() {
113: public boolean accept(File dir, String name) {
114: if (name == null)
115: return false;
116: if (!name.endsWith(".properties"))
117: return false;
118: if (name.charAt(name.length()
119: - (3 + ".properties".length())) != '_')
120: return true;
121: return false;
122: }
123: };
124: File[] dirFiles = dir.listFiles(filter);
125: for (File file : dirFiles) {
126: if (!file.isDirectory())
127: returnList.add(file.getName());
128: }
129: } catch (Exception e) {
130: }
131: return returnList;
132: }
133:
134: public DateFormat getDateTimeFormat() {
135: return getDateTimeFormat(RomaApplicationContext.getInstance()
136: .getBean(SessionAspect.class).getActiveLocale());
137: }
138:
139: public DateFormat getDateTimeFormat(Locale iLocale) {
140: return new SimpleDateFormat(getString(DATE_TIME_FORMAT_VAR));
141: }
142:
143: public DateFormat getDateFormat() {
144: return getDateFormat(RomaApplicationContext.getInstance()
145: .getBean(SessionAspect.class).getActiveLocale());
146: }
147:
148: public DateFormat getDateFormat(Locale iLocale) {
149: return new SimpleDateFormat(
150: getString(DATE_FORMAT_VAR, iLocale), iLocale);
151: }
152:
153: public DateFormat getTimeFormat() {
154: return getTimeFormat(RomaApplicationContext.getInstance()
155: .getBean(SessionAspect.class).getActiveLocale());
156: }
157:
158: public DateFormat getTimeFormat(Locale iLocale) {
159: return new SimpleDateFormat(getString(TIME_FORMAT_VAR));
160: }
161:
162: /**
163: * Find the label walking on entity inheritance tree.
164: *
165: * @param resource
166: * @param iObject
167: * @param iElementName
168: * @return
169: */
170: public String getLabel(SchemaObject iObject, String iElementName,
171: String iElementLabel) {
172: if (iElementLabel != null)
173: // CUSTOM LABEL FOUND: RETURN
174: return resolveString(iObject.getSchemaClass().getClazz(),
175: iElementLabel);
176:
177: StringBuffer nameToSearch = new StringBuffer();
178: String label = null;
179: SchemaClass clazz = iObject.getSchemaClass();
180:
181: while (clazz != null) {
182: // COMPOSE THE NAME TO SEARCH
183: nameToSearch.setLength(0);
184: nameToSearch.append(clazz.getSchemaClass().getName());
185: if (iElementName != null)
186: nameToSearch.append(SEPARATOR).append(iElementName);
187: nameToSearch.append(SEPARATOR).append(LABEL_NAME);
188:
189: label = getString(nameToSearch.toString());
190: if (label != null)
191: break;
192:
193: clazz = clazz.getSchemaClass().getParent();
194: }
195:
196: if (label == null) {
197: // AUTO COMPOSE LABEL IF NOT EXISTS
198: label = Utility
199: .getClearName(iElementName != null ? iElementName
200: : iObject.getSchemaClass().getName());
201: }
202:
203: return label;
204: }
205:
206: /**
207: * Resolve a string using the I18N. If the string starts with $ prefix, then search that name in the resource bundle configured,
208: * otherwise returns the same string passed in input.
209: *
210: * @param iObjectClass
211: * The class of object for tree search
212: * @param iText
213: * The string to analyze and search
214: * @return The I18N string if it starts with $ prefix
215: */
216: public String resolveString(Class<?> iObjectClass, String iText,
217: Object... iArgs) {
218: if (!iText.startsWith(VARNAME_PREFIX))
219: return null;
220:
221: String result = null;
222:
223: String varName = iText.substring(VARNAME_PREFIX.length());
224:
225: StringBuffer buffer = new StringBuffer();
226: Class<?> currentClass = iObjectClass;
227:
228: // SEARCH BROWSING ALL CLASS TREE UNTIL OBJECT CLASS
229: while (currentClass != null && currentClass != Object.class) {
230: buffer.setLength(0);
231: buffer.append(VARNAME_PREFIX);
232: buffer.append(currentClass.getSimpleName());
233: buffer.append(CONTEXT_SEPARATOR);
234: buffer.append(varName);
235:
236: result = resolveString(buffer.toString());
237: if (result != null)
238: return result;
239:
240: currentClass = currentClass.getSuperclass();
241: }
242:
243: // NOT FOUND, TRY WITH DEFAULT
244: result = resolveString(VARNAME_PREFIX
245: + GlobalConstants.ROOT_CLASS
246: + Utility.PACKAGE_SEPARATOR + varName);
247:
248: return result;
249: }
250:
251: /**
252: * Resolve a string using the I18N. If the string starts with $ prefix, then search that name in the resource bundle configured,
253: * otherwise returns the same string passed in input.
254: *
255: * @param iText
256: * The string to analyze and search
257: * @return The I18N string if it starts with $ prefix, otherwise iLabel
258: */
259: public String resolveString(String iText, Object... iArgs) {
260: if (iText != null && iText.startsWith(VARNAME_PREFIX))
261: // VARNAME_PREFIX FOUND: RESOLVE THE LABEL WITH I18N SETTINGS
262: return getString(iText.substring(VARNAME_PREFIX.length()));
263:
264: return iText;
265: }
266:
267: public String getString(String iText) {
268: List<ResourceBundle> resource = getResourceBundle();
269: return searchStringInResources(resource, iText);
270: }
271:
272: public String getString(String iText, Locale iLocale) {
273: List<ResourceBundle> resource = getResourceBundle(iLocale);
274: return searchStringInResources(resource, iText);
275: }
276:
277: public String searchStringInResources(
278: List<ResourceBundle> iResources, String iText) {
279: String result;
280: for (ResourceBundle rb : iResources) {
281: try {
282: result = rb.getString(iText);
283: return result;
284: } catch (MissingResourceException e) {
285: }
286: }
287: return null;
288: }
289:
290: @Override
291: public void startup() {
292: // REGISTER MYSELF AS EVENT LISTENER FROM OBJECTCONTEXT
293: Controller.getInstance().registerListener(
294: UserObjectEventListener.class, this );
295: }
296:
297: protected List<ResourceBundle> getResourceBundle() {
298: return getResourceBundle(RomaApplicationContext.getInstance()
299: .getBean(SessionAspect.class).getActiveLocale());
300: }
301:
302: protected List<ResourceBundle> getResourceBundle(Locale iLocale) {
303: return getResourceBundle(iLocale, Configurable.DEFAULT_CONFIG);
304: }
305:
306: /**
307: * Search the ResourceBundle by context name. Once loaded, ResourceBundles are cached by Locale settings.
308: *
309: * @param iLocale
310: * Locale object identify I18N region
311: * @param iContextName
312: * Name of context to search
313: * @return
314: */
315: protected synchronized List<ResourceBundle> getResourceBundle(
316: Locale iLocale, String iContextName) {
317: List<ResourceBundle> rbs = null;
318:
319: // TRY TO GET RESOURCE BUNDLE FROM CACHE
320: I18NLocaleContext localizedContext = resources
321: .get(iContextName);
322:
323: if (localizedContext == null) {
324: // NOT IN CACHE: TRY TO INSTANCE IT
325: localizedContext = new I18NLocaleContext();
326: resources.put(iContextName, localizedContext);
327: } else
328: rbs = localizedContext.getResources(iLocale);
329:
330: if (rbs == null) {
331: rbs = new ArrayList<ResourceBundle>();
332: File file = loadResourceBundle(rbs, iLocale,
333: localizedContext);
334:
335: if (autoReloadManager != null)
336: autoReloadManager.addResource(file, this );
337: }
338:
339: return rbs;
340: }
341:
342: private File loadResourceBundle(
343: List<ResourceBundle> iResourceBundles, Locale iLocale,
344: I18NLocaleContext localizedContext) {
345: // LOAD ALL CONFIGURED FILES
346: Collection<String> cfg = getConfigurationValues();
347: ResourceBundle rb;
348:
349: for (String resourceName : cfg) {
350: // LOAD RESOURCE BUNDLE
351: if (iLocale != null)
352: rb = ResourceBundle.getBundle(Utility
353: .getPackagePath(resourceName), iLocale);
354: else
355: rb = ResourceBundle.getBundle(Utility
356: .getPackagePath(resourceName));
357:
358: iResourceBundles.add(rb);
359: }
360: // CACHE IT IN LOCALIZED MAP
361: localizedContext.addResources(iLocale, iResourceBundles);
362:
363: return null;
364: }
365:
366: /**
367: * Reload I18N configuration from file. This event is invoked when the file is changed.
368: */
369: public void signalUpdatedFile(File iFile) {
370: }
371:
372: public boolean onBeforeActionExecution(Object iContent,
373: SchemaElement iAction) {
374: return true;
375: }
376:
377: public void onAfterActionExecution(Object iContent,
378: SchemaElement iAction) {
379: }
380:
381: public Object onBeforeFieldRead(Object iContent,
382: SchemaField iField, Object iCurrentValue) {
383: if (iContent == null || iCurrentValue == null)
384: return null;
385:
386: String key = (String) iField.getFeature(I18NAspect.ASPECT_NAME,
387: I18NFieldFeatures.KEY);
388:
389: if (key == null)
390: return iCurrentValue;
391:
392: StringBuilder keyToSearch = new StringBuilder(VARNAME_PREFIX);
393:
394: int pos = key.indexOf(I18NFieldFeatures.CONTENT_VAR);
395: if (pos == -1) {
396: keyToSearch.append(key);
397: } else {
398: // REPLACE CONTENT_VAR WITH REAL VALUE
399: keyToSearch.append(key.substring(0, pos));
400: keyToSearch.append(iCurrentValue.toString());
401: keyToSearch.append(key.substring(pos
402: + I18NFieldFeatures.CONTENT_VAR.length()));
403: }
404:
405: Object ret = resolveString(iContent.getClass(), keyToSearch
406: .toString());
407: if (ret != null)
408: return ret;
409: else
410: return iCurrentValue;
411: }
412:
413: public Object onAfterFieldRead(Object content, SchemaField field,
414: Object currentValue) {
415: return currentValue;
416: }
417:
418: public void onFieldRefresh(SessionInfo iSession, Object iContent,
419: SchemaField iField) {
420: }
421:
422: public Object onBeforeFieldWrite(Object content, SchemaField field,
423: Object currentValue) {
424: return currentValue;
425: }
426:
427: public Object onAfterFieldWrite(Object content, SchemaField field,
428: Object currentValue) {
429: return currentValue;
430: }
431: }
|