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: 51d976e5e2 [formerly 321a29ffa4 [formerly 9eaa57677f77ff919f1a55ee7ad4b44a0613b97a]]
Former-commit-id: 321a29ffa4
Former-commit-id: a508e9c8cb
This commit is contained in:
Max Schenkelberg 2013-03-29 13:25:09 -05:00
parent 466fa3fd41
commit 6c1c9f25d5
6 changed files with 441 additions and 24 deletions

View file

@ -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
*
* </pre>
*
@ -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<String, Object> map)
throws VizException {
Class<PluginDataObject> 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> T loadRecordFromMap(Map<String, Object> map, Class<T> 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;
}

View file

@ -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
*
* </pre>
*
@ -110,13 +111,13 @@ public abstract class AbstractRequestableResourceData extends
Object objectToSend = null;
Map<String, Object> attribs = new HashMap<String, Object>(
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;

View file

@ -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
*
* </pre>
*
@ -138,6 +139,15 @@ public abstract class AbstractVizResource<T extends AbstractResourceData, D exte
*/
private Set<IDisposeListener> 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<T extends AbstractResourceData, D exte
paintListeners = new CopyOnWriteArraySet<IPaintListener>();
paintStatusListeners = new CopyOnWriteArraySet<IPaintStatusChangedListener>();
disposeListeners = new CopyOnWriteArraySet<IDisposeListener>();
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<T extends AbstractResourceData, D exte
initInternal(target);
status = ResourceStatus.INITIALIZED;
if (resourceData != null) {
resourceData.addChangeListener(removeListener);
}
for (IInitListener listener : initListeners) {
listener.inited(this);
}
@ -349,7 +351,13 @@ public abstract class AbstractVizResource<T extends AbstractResourceData, D exte
public final void dispose() {
if (status == ResourceStatus.INITIALIZED) {
status = ResourceStatus.DISPOSED;
disposeInternal();
try {
disposeInternal();
} finally {
if (resourceData != null) {
resourceData.removeChangeListener(removeListener);
}
}
for (IDisposeListener listener : disposeListeners) {
listener.disposed(this);
}

View file

@ -22,6 +22,7 @@ package com.raytheon.uf.common.dataplugin;
import java.lang.reflect.Field;
import java.util.Calendar;
import java.util.HashMap;
import java.util.Map;
import javax.persistence.Column;
@ -72,6 +73,8 @@ import com.raytheon.uf.common.util.ConvertUtil;
* 2/6/09 1990 bphillip Added database index on dataURI
* 3/18/09 2105 jsanchez Added getter for id.
* Removed unused getIdentfier().
* Mar 29, 2013 1638 mschenke Added methods for loading from data map and creating data map from
* dataURI fields
*
* </pre>
*
@ -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<String, Object> dataMap)
throws PluginException {
populateFromMap(this, dataMap);
}
/**
* Creates a mapping of dataURI fields to objects set in the record
*
* @return
* @throws PluginException
*/
public Map<String, Object> createDataURIMap() throws PluginException {
try {
Map<String, Object> map = new HashMap<String, Object>();
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<String, Object> 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

View file

@ -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
*
* </pre>
*
@ -68,6 +70,22 @@ public class DataURIUtil {
return instance;
}
public Field[] getAllDataURIFields(Class<?> obj) {
List<Field> fields = new ArrayList<Field>();
getAllDataURIFields(obj, fields);
return fields.toArray(new Field[0]);
}
private void getAllDataURIFields(Class<?> obj, List<Field> 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.
*

View file

@ -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.
*
* <pre>
*
* SOFTWARE HISTORY
*
* Date Ticket# Engineer Description
* ------------ ---------- ----------- --------------------------
* Mar 28, 2013 mschenke Initial creation
*
* </pre>
*
* @author mschenke
* @version 1.0
*/
public class AutoUpdatingLocalizationFile {
public static interface AutoUpdatingFileChangedListener {
public void fileChanged(AutoUpdatingLocalizationFile file);
}
private static class AutoUpdatingFileObserver extends
WeakReference<AutoUpdatingLocalizationFile> implements
ILocalizationFileObserver {
private static ReferenceQueue<AutoUpdatingLocalizationFile> queue = new ReferenceQueue<AutoUpdatingLocalizationFile>();
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<AutoUpdatingFileChangedListener> listeners = new LinkedHashSet<AutoUpdatingFileChangedListener>();
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<LocalizationLevel, LocalizationFile> 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> T loadObject(JAXBManager manager, Class<T> 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<AutoUpdatingFileChangedListener> toNotify = new ArrayList<AutoUpdatingFileChangedListener>();
synchronized (listeners) {
toNotify.addAll(listeners);
}
for (AutoUpdatingFileChangedListener listener : toNotify) {
listener.fileChanged(this);
}
}
}