From a29eff544b99fd5b13e9a239e133045f8958f2a6 Mon Sep 17 00:00:00 2001 From: Max Schenkelberg Date: Fri, 29 Mar 2013 13:25:09 -0500 Subject: [PATCH] Issue #1638 Added Auto updating localization file object and added more methods to RecordFactory to make more versatile Amend: Fixed setting of nested field properties Amend: Moved populating and creating map into PluginDataObject, fixed listener leak in AbstractVizResource Amend: Added comments Change-Id: I699ab6a669a11656044c1d55f0bca9d258528835 Former-commit-id: a508e9c8cb1c28df82b0056a5c8264b17c5c154b [formerly 321a29ffa481211f83a9fef7a34e05defbeab4d0] [formerly a508e9c8cb1c28df82b0056a5c8264b17c5c154b [formerly 321a29ffa481211f83a9fef7a34e05defbeab4d0] [formerly 51d976e5e23faa24923ca45794ffa8276a31e57d [formerly 9eaa57677f77ff919f1a55ee7ad4b44a0613b97a]]] Former-commit-id: 51d976e5e23faa24923ca45794ffa8276a31e57d Former-commit-id: 8de3a709626e3c06f5bd855f8c181e7b0ba3e0d9 [formerly 1c35a54a9fd137cb67d96a9567bd3c4254c4eaa6] Former-commit-id: 01c2d29c1bd2495841fd7f273f5309a833b78a02 --- .../raytheon/uf/viz/core/RecordFactory.java | 54 +++- .../rsc/AbstractRequestableResourceData.java | 7 +- .../uf/viz/core/rsc/AbstractVizResource.java | 34 ++- .../common/dataplugin/PluginDataObject.java | 82 ++++++ .../dataplugin/annotations/DataURIUtil.java | 18 ++ .../AutoUpdatingLocalizationFile.java | 270 ++++++++++++++++++ 6 files changed, 441 insertions(+), 24 deletions(-) create mode 100644 edexOsgi/com.raytheon.uf.common.localization/src/com/raytheon/uf/common/localization/AutoUpdatingLocalizationFile.java diff --git a/cave/com.raytheon.uf.viz.core/src/com/raytheon/uf/viz/core/RecordFactory.java b/cave/com.raytheon.uf.viz.core/src/com/raytheon/uf/viz/core/RecordFactory.java index 08721927b9..a29701b60d 100644 --- a/cave/com.raytheon.uf.viz.core/src/com/raytheon/uf/viz/core/RecordFactory.java +++ b/cave/com.raytheon.uf.viz.core/src/com/raytheon/uf/viz/core/RecordFactory.java @@ -24,9 +24,8 @@ import java.util.HashMap; import java.util.Map; import java.util.TreeSet; -import org.apache.commons.beanutils.ConstructorUtils; - import com.raytheon.uf.common.dataplugin.PluginDataObject; +import com.raytheon.uf.common.dataplugin.PluginException; import com.raytheon.uf.common.dataplugin.annotations.DataURI; import com.raytheon.uf.common.dataplugin.request.GetPluginRecordMapRequest; import com.raytheon.uf.common.status.IUFStatusHandler; @@ -47,6 +46,7 @@ import com.raytheon.uf.viz.core.requests.ThriftClient; * 7/24/07 353 bphillip Initial creation * 10/8/2008 1532 bphillip Refactored to incorporate annotation support * Mar 5, 2013 1753 njensen Improved debug message + * Mar 29, 2013 1638 mschenke Added dataURI mapping methods * * * @@ -206,15 +206,53 @@ public class RecordFactory { */ public PluginDataObject loadRecordFromUri(String dataURI) throws VizException { + return loadRecordFromMap(loadMapFromUri(dataURI)); + } - String pluginName = dataURI.substring(dataURI.indexOf("/") + 1, - dataURI.indexOf("/", 2)); - PluginDataObject record = null; + /** + * Creates a partially populated PluginDataObject from the given a map of + * attributes + * + * @param map + * The map used for populating the object + * @return A PluginDataObject populated from the provided dataURI + * @throws VizException + * If the PluginDataObject cannot be created + */ + public PluginDataObject loadRecordFromMap(Map map) + throws VizException { + Class pdoClass = getPluginClass((String) map + .get("pluginName")); + if (pdoClass == null) { + throw new VizException( + "Unable to load record from dataURI, PDO class for plugin (" + + map.get("pluginName") + ") not found"); + } + + return loadRecordFromMap(map, pdoClass); + } + + /** + * Populates a record type object from map + * + * @param map + * @param type + * @return + * @throws VizException + */ + public T loadRecordFromMap(Map map, Class type) + throws VizException { + T record; try { - record = (PluginDataObject) ConstructorUtils - .invokeExactConstructor(getPluginClass(pluginName), dataURI); + record = type.newInstance(); } catch (Exception e) { - throw new VizException("Unable to instantiate record class", e); + throw new VizException("Unable to create new record for type: " + + type, e); + } + try { + PluginDataObject.populateFromMap(record, map); + } catch (PluginException e) { + throw new VizException(e); } return record; } diff --git a/cave/com.raytheon.uf.viz.core/src/com/raytheon/uf/viz/core/rsc/AbstractRequestableResourceData.java b/cave/com.raytheon.uf.viz.core/src/com/raytheon/uf/viz/core/rsc/AbstractRequestableResourceData.java index 4198e138b7..de89e192cb 100644 --- a/cave/com.raytheon.uf.viz.core/src/com/raytheon/uf/viz/core/rsc/AbstractRequestableResourceData.java +++ b/cave/com.raytheon.uf.viz.core/src/com/raytheon/uf/viz/core/rsc/AbstractRequestableResourceData.java @@ -79,6 +79,7 @@ import com.raytheon.uf.viz.core.rsc.IResourceDataChanged.ChangeType; * Feb 26, 2009 2032 jsanchez Added loadWithNoData condition. * April 6, 2011 njensen Moved binning times to edex * April 13, 2011 njensen Caching available times + * Mar 29, 2013 1638 mschenke Switched to create PDO from dataURI mapping instead of dataURI string * * * @@ -110,13 +111,13 @@ public abstract class AbstractRequestableResourceData extends Object objectToSend = null; Map attribs = new HashMap( message.decodedAlert); - String dataURI = message.dataURI; + attribs.put("dataURI", message.dataURI); + if (reqResourceData.isUpdatingOnMetadataOnly()) { PluginDataObject record = RecordFactory.getInstance() - .loadRecordFromUri(dataURI); + .loadRecordFromMap(attribs); objectToSend = record; } else { - attribs.put("dataURI", message.dataURI); objectToSend = Loader.loadData(attribs); } return objectToSend; diff --git a/cave/com.raytheon.uf.viz.core/src/com/raytheon/uf/viz/core/rsc/AbstractVizResource.java b/cave/com.raytheon.uf.viz.core/src/com/raytheon/uf/viz/core/rsc/AbstractVizResource.java index 1d953662f9..0d2cd37eed 100644 --- a/cave/com.raytheon.uf.viz.core/src/com/raytheon/uf/viz/core/rsc/AbstractVizResource.java +++ b/cave/com.raytheon.uf.viz.core/src/com/raytheon/uf/viz/core/rsc/AbstractVizResource.java @@ -62,6 +62,7 @@ import com.raytheon.uf.viz.core.rsc.capabilities.Capabilities; * ------------ ---------- ----------- -------------------------- * Feb 4, 2009 chammack Initial creation from original IVizResource * Mar 3, 2009 2032 jsanchez Added getDescriptor and paintProps. + * Mar 29, 2013 1638 mschenke Fixed leak of data change listener * * * @@ -138,6 +139,15 @@ public abstract class AbstractVizResource disposeListeners; + private IResourceDataChanged removeListener = new IResourceDataChanged() { + @Override + public void resourceChanged(ChangeType type, Object object) { + if (type == ChangeType.DATA_REMOVE && object instanceof DataTime) { + remove((DataTime) object); + } + } + }; + /** * The base constructor * @@ -156,18 +166,6 @@ public abstract class AbstractVizResource(); paintStatusListeners = new CopyOnWriteArraySet(); disposeListeners = new CopyOnWriteArraySet(); - - if (resourceData != null) { - resourceData.addChangeListener(new IResourceDataChanged() { - @Override - public void resourceChanged(ChangeType type, Object object) { - if (type == ChangeType.DATA_REMOVE - && object instanceof DataTime) { - remove((DataTime) object); - } - } - }); - } } /** @@ -329,6 +327,10 @@ public abstract class AbstractVizResource * @@ -206,6 +209,85 @@ public abstract class PluginDataObject extends PersistableDataObject implements return uriBuffer; } + /** + * Populates the record object from a data map + * + * @param dataMap + * @throws PluginException + */ + public void populateFromMap(Map dataMap) + throws PluginException { + populateFromMap(this, dataMap); + } + + /** + * Creates a mapping of dataURI fields to objects set in the record + * + * @return + * @throws PluginException + */ + public Map createDataURIMap() throws PluginException { + try { + Map map = new HashMap(); + map.put("pluginName", getPluginName()); + Field[] fields = DataURIUtil.getInstance().getAllDataURIFields( + getClass()); + for (int i = 0; i < fields.length; ++i) { + String fieldName = PluginDataObject.getDataURIFieldName( + getClass(), i); + String[] nested = fieldName.split("[.]"); + Object source = this; + if (nested.length > 0) { + for (int j = 0; j < nested.length; ++j) { + source = PropertyUtils.getProperty(source, nested[j]); + } + map.put(fieldName, source); + } + } + return map; + } catch (Exception e) { + throw new PluginException("Error constructing dataURI mapping", e); + } + } + + /** + * Populates object from data mapping + * + * @param object + * @param dataMap + */ + public static void populateFromMap(Object object, + Map dataMap) throws PluginException { + try { + for (String property : dataMap.keySet()) { + String[] nested = property.split("[.]"); + if (nested.length > 0) { + Object source = object; + for (int i = 0; i < nested.length - 1; ++i) { + String field = nested[i]; + Object obj = PropertyUtils.getProperty(source, field); + if (obj == null) { + obj = PropertyUtils.getPropertyType(source, field) + .newInstance(); + PropertyUtils.setProperty(source, field, obj); + } + source = obj; + } + String sourceProperty = nested[nested.length - 1]; + Object value = dataMap.get(property); + if (value != null) { + PropertyUtils + .setProperty(source, sourceProperty, value); + } + } + } + } catch (Exception e) { + throw new PluginException("Error populating record type: " + + (object != null ? object.getClass() : null) + + " from map: " + dataMap, e); + } + } + /** * Recursive method to populate an object from the elements in a dataURI * string diff --git a/edexOsgi/com.raytheon.uf.common.dataplugin/src/com/raytheon/uf/common/dataplugin/annotations/DataURIUtil.java b/edexOsgi/com.raytheon.uf.common.dataplugin/src/com/raytheon/uf/common/dataplugin/annotations/DataURIUtil.java index 15a0e6a5c3..c727ac4719 100644 --- a/edexOsgi/com.raytheon.uf.common.dataplugin/src/com/raytheon/uf/common/dataplugin/annotations/DataURIUtil.java +++ b/edexOsgi/com.raytheon.uf.common.dataplugin/src/com/raytheon/uf/common/dataplugin/annotations/DataURIUtil.java @@ -35,6 +35,8 @@ import java.util.Map; * Date Ticket# Engineer Description * ------------ ---------- ----------- -------------------------- * 10/07/2008 1533 bphillip Initial Checkin + * Mar 29, 2013 1638 mschenke Added method for recursively getting all + * dataURI fields for an object * * * @@ -68,6 +70,22 @@ public class DataURIUtil { return instance; } + public Field[] getAllDataURIFields(Class obj) { + List fields = new ArrayList(); + getAllDataURIFields(obj, fields); + return fields.toArray(new Field[0]); + } + + private void getAllDataURIFields(Class obj, List fields) { + for (Field field : getDataURIFields(obj)) { + if (field.getAnnotation(DataURI.class).embedded()) { + getAllDataURIFields(field.getType(), fields); + } else { + fields.add(field); + } + } + } + /** * Retrieves an ordered listing of all fields annotated as DataURI fields. * diff --git a/edexOsgi/com.raytheon.uf.common.localization/src/com/raytheon/uf/common/localization/AutoUpdatingLocalizationFile.java b/edexOsgi/com.raytheon.uf.common.localization/src/com/raytheon/uf/common/localization/AutoUpdatingLocalizationFile.java new file mode 100644 index 0000000000..45ece3f1a8 --- /dev/null +++ b/edexOsgi/com.raytheon.uf.common.localization/src/com/raytheon/uf/common/localization/AutoUpdatingLocalizationFile.java @@ -0,0 +1,270 @@ +/** + * This software was developed and / or modified by Raytheon Company, + * pursuant to Contract DG133W-05-CQ-1067 with the US Government. + * + * U.S. EXPORT CONTROLLED TECHNICAL DATA + * This software product contains export-restricted data whose + * export/transfer/disclosure is restricted by U.S. law. Dissemination + * to non-U.S. persons whether in the United States or abroad requires + * an export license or other authorization. + * + * Contractor Name: Raytheon Company + * Contractor Address: 6825 Pine Street, Suite 340 + * Mail Stop B8 + * Omaha, NE 68106 + * 402.291.0100 + * + * See the AWIPS II Master Rights File ("Master Rights File.pdf") for + * further licensing information. + **/ +package com.raytheon.uf.common.localization; + +import java.lang.ref.ReferenceQueue; +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import com.raytheon.uf.common.localization.LocalizationContext.LocalizationLevel; +import com.raytheon.uf.common.localization.LocalizationContext.LocalizationType; +import com.raytheon.uf.common.localization.exception.LocalizationException; +import com.raytheon.uf.common.serialization.JAXBManager; +import com.raytheon.uf.common.serialization.SerializationException; + +/** + * Wrapper class that auto updates the internal LocalizationFile with the + * highest {@link LocalizationLevel} version. Has update listening mechanism to + * notify when that changes. + * + *
+ * 
+ * SOFTWARE HISTORY
+ * 
+ * Date         Ticket#    Engineer    Description
+ * ------------ ---------- ----------- --------------------------
+ * Mar 28, 2013            mschenke     Initial creation
+ * 
+ * 
+ * + * @author mschenke + * @version 1.0 + */ + +public class AutoUpdatingLocalizationFile { + + public static interface AutoUpdatingFileChangedListener { + + public void fileChanged(AutoUpdatingLocalizationFile file); + + } + + private static class AutoUpdatingFileObserver extends + WeakReference implements + ILocalizationFileObserver { + + private static ReferenceQueue queue = new ReferenceQueue(); + + private LocalizationFile registeredFile; + + /** + * @param referent + */ + public AutoUpdatingFileObserver(AutoUpdatingLocalizationFile referent) { + super(referent, queue); + this.registeredFile = referent.getFile(); + this.registeredFile.addFileUpdatedObserver(this); + } + + /* + * (non-Javadoc) + * + * @see + * com.raytheon.uf.common.localization.ILocalizationFileObserver#fileUpdated + * (com.raytheon.uf.common.localization.FileUpdatedMessage) + */ + @Override + public void fileUpdated(FileUpdatedMessage message) { + AutoUpdatingLocalizationFile file = get(); + if (file != null) { + switch (message.getChangeType()) { + case ADDED: + case DELETED: + if (registeredFile != null) { + registeredFile.removeFileUpdatedObserver(this); + } + LocalizationFile newFile = file.internalFile = file + .getHighestLevelFile(file.filePath, file.type); + if (newFile == null) { + // Always ensure we are still listening even when no + // file exists so if file is created, we get notified + IPathManager mgr = PathManagerFactory.getPathManager(); + newFile = mgr.getLocalizationFile(mgr.getContext( + file.type, LocalizationLevel.BASE), + file.filePath); + } + registeredFile = newFile; + registeredFile.addFileUpdatedObserver(this); + } + file.notifyListeners(); + } else { + cleanupReferences(); + } + } + + private void cleanup() { + if (this.registeredFile != null) { + this.registeredFile.removeFileUpdatedObserver(this); + this.registeredFile = null; + } + } + + public static void cleanupReferences() { + AutoUpdatingFileObserver observer; + while ((observer = (AutoUpdatingFileObserver) queue.poll()) != null) { + observer.cleanup(); + } + } + } + + private final String filePath; + + private final LocalizationType type; + + private final Set listeners = new LinkedHashSet(); + + private LocalizationFile internalFile; + + /** + * Constructs an auto updating localization file from an existing file + * + * @param file + */ + public AutoUpdatingLocalizationFile(LocalizationFile file) { + this.filePath = file.getName(); + this.type = file.getContext().getLocalizationType(); + this.internalFile = file; + new AutoUpdatingFileObserver(this); + } + + /** + * Constructs and auto updating localization file from a file path and type + * + * @param filePath + * @param type + * @throws LocalizationException + */ + public AutoUpdatingLocalizationFile(String filePath, LocalizationType type) + throws LocalizationException { + this.filePath = filePath; + this.type = type; + LocalizationFile file = getHighestLevelFile(filePath, type); + if (file == null) { + throw new LocalizationException("File with path, " + filePath + + ", could not be found"); + } + internalFile = file; + new AutoUpdatingFileObserver(this); + } + + private LocalizationFile getHighestLevelFile(String filePath, + LocalizationType type) { + Map hierarchy = PathManagerFactory + .getPathManager().getTieredLocalizationFile(type, filePath); + LocalizationFile file = null; + for (LocalizationLevel level : LocalizationLevel.values()) { + LocalizationFile levelFile = hierarchy.get(level); + if (levelFile != null) { + file = levelFile; + } + } + return file; + } + + /** + * Gets the localization file path of the updating file + * + * @return + */ + public String getFilePath() { + return filePath; + } + + /** + * Gets the localization type of the updating file + * + * @return + */ + public LocalizationType getType() { + return type; + } + + /** + * Gets the {@link LocalizationFile} object from the updating file + * + * @return + */ + public LocalizationFile getFile() { + return internalFile; + } + + /** + * Loads the localization file as an Object given the manager and expected + * type + * + * @param manager + * @param type + * @return + * @throws SerializationException + */ + public T loadObject(JAXBManager manager, Class type) + throws SerializationException { + if (internalFile == null) { + return null; + } + try { + return type.cast(manager.jaxbUnmarshalFromInputStream(internalFile + .openInputStream())); + } catch (LocalizationException e) { + throw new SerializationException( + "Error opening LocalizationFile input stream", e); + } + } + + /** + * Adds a listener for file changed events + * + * @param listener + */ + public void addListener(AutoUpdatingFileChangedListener listener) { + synchronized (listeners) { + listeners.add(listener); + } + } + + /** + * Removes a listener for file changed events + * + * @param listener + */ + public void removeListener(AutoUpdatingFileChangedListener listener) { + synchronized (listeners) { + listeners.remove(listener); + } + } + + /** + * Notifies the listeners the file has changed + */ + private void notifyListeners() { + List toNotify = new ArrayList(); + synchronized (listeners) { + toNotify.addAll(listeners); + } + for (AutoUpdatingFileChangedListener listener : toNotify) { + listener.fileChanged(this); + } + } + +}