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
[formerly321a29ffa4
[formerly 9eaa57677f77ff919f1a55ee7ad4b44a0613b97a]] Former-commit-id:321a29ffa4
Former-commit-id:a508e9c8cb
This commit is contained in:
parent
466fa3fd41
commit
6c1c9f25d5
6 changed files with 441 additions and 24 deletions
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
*
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
Loading…
Add table
Reference in a new issue