/*
* Copyright (c) 2006 Stiftung Deutsches Elektronen-Synchroton,
* Member of the Helmholtz Association, (DESY), HAMBURG, GERMANY.
*
* THIS SOFTWARE IS PROVIDED UNDER THIS LICENSE ON AN "../AS IS" BASIS.
* WITHOUT WARRANTY OF ANY KIND, EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED
* TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR PARTICULAR PURPOSE AND
* NON-INFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
* FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR
* THE USE OR OTHER DEALINGS IN THE SOFTWARE. SHOULD THE SOFTWARE PROVE DEFECTIVE
* IN ANY RESPECT, THE USER ASSUMES THE COST OF ANY NECESSARY SERVICING, REPAIR OR
* CORRECTION. THIS DISCLAIMER OF WARRANTY CONSTITUTES AN ESSENTIAL PART OF THIS LICENSE.
* NO USE OF ANY SOFTWARE IS AUTHORIZED HEREUNDER EXCEPT UNDER THIS DISCLAIMER.
* DESY HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS,
* OR MODIFICATIONS.
* THE FULL LICENSE SPECIFYING FOR THE SOFTWARE THE REDISTRIBUTION, MODIFICATION,
* USAGE AND OTHER RIGHTS AND OBLIGATIONS IS INCLUDED WITH THE DISTRIBUTION OF THIS
* PROJECT IN THE FILE LICENSE.HTML. IF THE LICENSE IS NOT INCLUDED YOU MAY FIND A COPY
* AT HTTP://WWW.DESY.DE/LEGAL/LICENSE.HTM
*/
package org.csstudio.sds.internal.persistence;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Stack;
import org.csstudio.dal.DynamicValueState;
import org.csstudio.platform.simpledal.ConnectionState;
import org.csstudio.sds.SdsPlugin;
import org.csstudio.sds.eventhandling.EventType;
import org.csstudio.sds.internal.model.Layer;
import org.csstudio.sds.internal.model.LayerSupport;
import org.csstudio.sds.internal.rules.ParameterDescriptor;
import org.csstudio.sds.model.AbstractWidgetModel;
import org.csstudio.sds.model.ContainerModel;
import org.csstudio.sds.model.DisplayModel;
import org.csstudio.sds.model.DynamicsDescriptor;
import org.csstudio.sds.model.GroupingContainerModel;
import org.csstudio.sds.model.PropertyTypesEnum;
import org.csstudio.sds.model.WidgetModelFactoryService;
import org.jdom.DataConversionException;
import org.jdom.Element;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;
/**
*
* SAX handler for display models. <br>
* This handler incrementally parses <code>widget</code> tags by transforming
* them into DOM elements. After these DOM elements have been parsed, the
* underlying display model is updated concurrently.
*
* @author Alexander Will
* @version $Revision: 1.27 $
*
*/
public final class SaxDisplayModelHandler extends DefaultHandler {
private static final Logger LOG = LoggerFactory.getLogger(SaxDisplayModelHandler.class);
/**
* Element stack for the created DOM elements.
*/
private Stack<Element> _elementStack;
/**
* Display model.
*/
private DisplayModel _displayModel;
/**
* Optional listener that will be notified of model loading events.
*/
private IDisplayModelLoadListener _loadListener;
/**
* Flag that indicates if the model properties have been loaded completely.
*/
private boolean _modelPropertiesLoaded;
/**
* Standard constructor.
*
* @param displayModel
* The underlying display model.
* @param loadListener
* Optional listener that will be notified of model loading
* events (can be null).
*/
public SaxDisplayModelHandler(final DisplayModel displayModel,
final IDisplayModelLoadListener loadListener) {
assert displayModel != null : "displayModel != null"; //$NON-NLS-1$
_elementStack = new Stack<Element>();
_displayModel = displayModel;
_loadListener = loadListener;
_modelPropertiesLoaded = false;
}
/**
* {@inheritDoc}
*/
@Override
public void startElement(final String uri,
final String localName,
final String qName,
final Attributes attributes) throws SAXException {
Element element = createElement(qName, attributes);
if (!_modelPropertiesLoaded && XmlConstants.XML_ELEMENT_WIDGET.equals(element.getName())) {
_modelPropertiesLoaded = true;
if (_loadListener != null) {
_loadListener.onDisplayPropertiesLoaded();
}
}
if (_elementStack.size() > 0) {
Element parentElement = _elementStack.peek();
parentElement.addContent(element);
}
_elementStack.push(element);
}
/**
* {@inheritDoc}
*/
@Override
public void endElement(final String uri, final String localName, final String qName) throws SAXException {
Element element = _elementStack.pop();
assert qName.equals(element.getName());
if (_elementStack.size() == 1) {
if (XmlConstants.XML_ELEMENT_WIDGET.equals(element.getName())) {
final AbstractWidgetModel widgetModel = parseWidgetModel(element);
_displayModel.addWidget(widgetModel);
} else if (XmlConstants.XML_ELEMENT_PROPERTY.equals(element.getName())) {
setProperty(_displayModel, element);
} else if (XmlConstants.XML_LAYER.equals(element.getName())) {
parseLayer(element, _displayModel);
}
}
}
/**
* Parses the Layers contained in the given Element to thr
* {@link ContainerModel}.
*
* @param element
* The Element
* @param container
* The ContainerModel
* @throws SAXException
* if an error occurred during parsing.
*/
private void parseLayer(final Element element, final ContainerModel container) throws SAXException {
String layerId = element.getAttributeValue(XmlConstants.XML_LAYER_ID);
String layerName = element.getAttributeValue(XmlConstants.XML_LAYER_NAME);
boolean visibility = true;
try {
visibility = element.getAttribute(XmlConstants.XML_LAYER_VISIBILITY).getBooleanValue();
} catch (DataConversionException e) {
throw new SAXException("Unable to read <" + XmlConstants.XML_LAYER_VISIBILITY
+ "> property from layer element", e);
}
final boolean isVisible = visibility;
int index = -1;
try {
index = element.getAttribute(XmlConstants.XML_LAYER_INDEX).getIntValue();
} catch (DataConversionException e) {
throw new SAXException("Unable to read <" + XmlConstants.XML_LAYER_INDEX
+ "> property from layer element", e);
}
final int layerIndex = index;
if (layerId == null && LayerSupport.DEFAULT_NAME.equals(layerName)) {
layerId = LayerSupport.DEFAULT_LAYER_ID;
}
Layer layer = new Layer(layerId, layerName);
layer.setVisible(isVisible);
container.getLayerSupport().addLayer(layer, layerIndex);
}
/**
* Parse the widget model form the given DOM element.
*
* @param element
* The widget model DOM element.
* @return The widget model that was parsed from the given DOM element.
* @throws SAXException
* if an error occurred during parsing.
*/
private AbstractWidgetModel parseWidgetModel(final Element element) throws SAXException {
String elementType = element.getAttributeValue(XmlConstants.XML_ATTRIBUTE_WIDGET_TYPE);
// .. let the model factory create the base model
AbstractWidgetModel result = WidgetModelFactoryService.getInstance()
.getWidgetModel(elementType);
// .. configure model details (properties, dynamics) from xml
fillWidgetModel(result, element);
// .. update model to ensure invariants that have been declared by {@link SdsPlugin#EXTPOINT_WIDGET_PROPERTY_POSTPROCESSORS}
SdsPlugin.getDefault().getWidgetPropertyPostProcessingService()
.applyForAllProperties(result, EventType.ON_DISPLAY_MODEL_LOADED);
return result;
}
/**
* Fill the given widget model with the values that are parsed from its DOM
* representation.
*
* @param widgetModel
* The widget model that is to be filled.
* @param widgetElement
* The DOM representation of the widget model.
* @throws SAXException
* if an error occurred during parsing.
*/
@SuppressWarnings("rawtypes")
private void fillWidgetModel(final AbstractWidgetModel widgetModel, final Element widgetElement) throws SAXException {
// 1. alias descriptors
widgetModel.setAliases(parseAliasDescriptors(widgetElement
.getChild(XmlConstants.XML_ELEMENT_ALIAS_DESCRIPTORS)));
// 2. layer
if (widgetModel instanceof ContainerModel) {
List layerList = widgetElement.getChildren(XmlConstants.XML_LAYER);
for (Object o : layerList) {
Element layerElement = (Element) o;
parseLayer(layerElement, (ContainerModel) widgetModel);
}
}
// 3. properties
List propertiesList = widgetElement.getChildren(XmlConstants.XML_ELEMENT_PROPERTY);
for (Object o : propertiesList) {
setProperty(widgetModel, (Element) o);
}
// 4. widgets
if (widgetModel instanceof DisplayModel || widgetModel instanceof GroupingContainerModel) {
List widgetList = widgetElement.getChildren(XmlConstants.XML_ELEMENT_WIDGET);
for (Object o : widgetList) {
Element childElement = (Element) o;
final AbstractWidgetModel childModel = parseWidgetModel(childElement);
((ContainerModel) widgetModel).addWidget(childModel);
}
}
}
/**
* Set the property that is described by the given DOM element to the given
* widget model.
*
* @param widgetModel
* the widget model to set the property to.
* @param propertyElement
* the DOM element describing the propery.
* @throws SAXException
* if an error occurred during parsing.
*/
private void setProperty(final AbstractWidgetModel widgetModel, final Element propertyElement) throws SAXException {
String propertyType = propertyElement
.getAttributeValue(XmlConstants.XML_ATTRIBUTE_PROPERTY_TYPE);
AbstractPropertyPersistenceHandler persistenceHandler = getPropertyPersistenceHandler(propertyType);
if (persistenceHandler != null) {
String propertyId = propertyElement
.getAttributeValue(XmlConstants.XML_ATTRIBUTE_PROPERTY_ID);
Object propertyValue = parsePropertyValue(persistenceHandler, propertyElement);
Element dynamicsDescriptorElement = propertyElement
.getChild(XmlConstants.XML_ELEMENT_DYNAMICS_DESCRIPTOR);
if (propertyId != null && propertyValue != null) {
widgetModel.setPropertyValue(propertyId, propertyValue);
}
if (dynamicsDescriptorElement != null) {
DynamicsDescriptor dynamicsDescriptor = parseDynamicsDescriptor(persistenceHandler,
dynamicsDescriptorElement);
widgetModel.setDynamicsDescriptor(propertyId, dynamicsDescriptor);
}
}
}
/**
* Parse the property value from the given DOM element.
*
* @param persistenceHandler
* The persistence handler that is used for parsing.
* @param propertyElement
* The property DOM element.
* @return The property value that was parsed from the given property DOM
* element.
*/
private Object parsePropertyValue(final AbstractPropertyPersistenceHandler persistenceHandler,
final Element propertyElement) {
Object result = null;
if (persistenceHandler != null) {
result = persistenceHandler.readProperty(propertyElement);
}
return result;
}
/**
* Parse the dynamics descriptor from the given DOM element.
*
* @param persistenceHandler
* The persistence handler that is used for reading included
* property values.
* @param dynamicsDescriptorElement
* The dynamics descriptor DOM element.
* @return The dynamics descriptor that was parsed from the given dynamics
* descriptor DOM element.
* @throws SAXException
* if the included state values could not be parsed.
*/
@SuppressWarnings("rawtypes")
private DynamicsDescriptor parseDynamicsDescriptor(final AbstractPropertyPersistenceHandler persistenceHandler,
final Element dynamicsDescriptorElement) throws SAXException {
String ruleId = dynamicsDescriptorElement
.getAttributeValue(XmlConstants.XML_ATTRIBUTE_RULE_ID);
String useConnectionStatesString = dynamicsDescriptorElement
.getAttributeValue(XmlConstants.XML_ATTRIBUTE_USE_CONNECTION_STATES);
boolean useConnectionStates = false;
if (useConnectionStatesString != null) {
try {
Boolean b = new Boolean(useConnectionStatesString);
useConnectionStates = b;
} catch (Exception e) {
// do nothing
}
}
DynamicsDescriptor result = new DynamicsDescriptor(ruleId);
result.setUsingOnlyConnectionStates(useConnectionStates);
// input channels
List inputChannelElements = dynamicsDescriptorElement
.getChildren(XmlConstants.XML_ELEMENT_INPUT_CHANNEL);
if (inputChannelElements != null) {
for (Object o : inputChannelElements) {
ParameterDescriptor pd = parseChannel((Element) o);
if (pd != null) {
result.addInputChannel(pd);
}
}
}
// output channel
Element outputChannelElement = dynamicsDescriptorElement
.getChild(XmlConstants.XML_ELEMENT_OUTPUT_CHANNEL);
if (outputChannelElement != null) {
ParameterDescriptor pd = parseChannel(outputChannelElement);
if (pd != null) {
result.setOutputChannel(pd);
}
}
// connection states
result.setConnectionStateDependentPropertyValues(parseConnectionStates(persistenceHandler,
dynamicsDescriptorElement));
// dynamic value states
result.setConditionStateDependentPropertyValues(parseDynamicValueStates(persistenceHandler,
dynamicsDescriptorElement));
return result;
}
/**
* Parse the connection state values from the given dynamics descriptor DOM
* element.
*
* @param persistenceHandler
* The persistence handler that is used for reading included
* property values.
* @param dynamicsDescriptorElement
* The dynamics descriptor DOM element.
* @return The connection state values that were parsed from the given
* dynamics descriptor DOM element.
* @throws SAXException
* if a connection state could not be parsed.
*/
@SuppressWarnings("rawtypes")
private HashMap<ConnectionState, Object> parseConnectionStates(final AbstractPropertyPersistenceHandler persistenceHandler,
final Element dynamicsDescriptorElement) throws SAXException {
HashMap<ConnectionState, Object> result = new HashMap<ConnectionState, Object>();
List connectionStateElements = dynamicsDescriptorElement
.getChildren(XmlConstants.XML_ELEMENT_CONNECTION_STATE);
if (connectionStateElements != null) {
for (Object o : connectionStateElements) {
Element connectionStateElement = (Element) o;
ConnectionState state = null;
try {
String attributeValue = connectionStateElement
.getAttributeValue(XmlConstants.XML_ATTRIBUTE_STATE);
state = ConnectionState.valueOf(attributeValue);
result.put(state, persistenceHandler.readProperty(connectionStateElement));
} catch (Exception e) {
throw new SAXException("Could not parse connection state < "
+ connectionStateElement.getAttributeValue(XmlConstants.XML_ATTRIBUTE_STATE)
+ ">",
e);
}
}
}
return result;
}
/**
* Parse the danymics value state values from the given dynamics descriptor
* DOM element.
*
* @param persistenceHandler
* The persistence handler that is used for reading included
* property values.
* @param dynamicsDescriptorElement
* The dynamics descriptor DOM element.
* @return The dynamic value state values that were parsed from the given
* dynamics descriptor DOM element.
* @throws SAXException
* if a connection state could not be parsed.
*/
@SuppressWarnings("rawtypes")
private HashMap<DynamicValueState, Object> parseDynamicValueStates(final AbstractPropertyPersistenceHandler persistenceHandler,
final Element dynamicsDescriptorElement) throws SAXException {
HashMap<DynamicValueState, Object> result = new HashMap<DynamicValueState, Object>();
List dynamicValueStateElements = dynamicsDescriptorElement
.getChildren(XmlConstants.XML_ELEMENT_DYNAMIC_VALUE_STATE);
if (dynamicValueStateElements != null) {
for (Object o : dynamicValueStateElements) {
Element dynamicValueStateElement = (Element) o;
DynamicValueState state = null;
try {
state = DynamicValueState.valueOf(dynamicValueStateElement
.getAttributeValue(XmlConstants.XML_ATTRIBUTE_STATE));
} catch (Exception e) {
throw new SAXException("Could not parse dynamic value state <"
+ dynamicValueStateElement.getAttributeValue(XmlConstants.XML_ATTRIBUTE_STATE)
+ ">",
e);
}
result.put(state, persistenceHandler.readProperty(dynamicValueStateElement));
}
}
return result;
}
/**
* Parse the channel from the given DOM element.
*
* @param channelElement
* The channel DOM element.
* @return The channel that was parsed from the given DOM element.
* @throws SAXException
* if an error occurred during parsing.
*/
private ParameterDescriptor parseChannel(final Element channelElement) throws SAXException {
String channelName = channelElement
.getAttributeValue(XmlConstants.XML_ATTRIBUTE_CHANNEL_NAME);
String value = channelElement.getAttributeValue(XmlConstants.XML_ATTRIBUTE_CHANNEL_VALUE);
if (value == null) {
value = "";
}
ParameterDescriptor result = null;
if ((channelName != null || value != null)) {
result = new ParameterDescriptor(channelName, value);
}
return result;
}
/**
* Parse the alias descriptors from the given JDOM element.
*
* @param element
* A JDOM representation of alias descriptors.
* @return The alias descriptors that were parsed from the given JDOM
* element.
*/
@SuppressWarnings("rawtypes")
private Map<String, String> parseAliasDescriptors(final Element element) {
Map<String, String> result = new HashMap<String, String>();
if (element != null) {
List aliases = element.getChildren(XmlConstants.XML_ELEMENT_ALIAS);
if (aliases != null) {
for (Object o : aliases) {
Element aliasElement = (Element) o;
String name = aliasElement
.getAttributeValue(XmlConstants.XML_ATTRIBUTE_ALIAS_NAME);
String value = aliasElement
.getAttributeValue(XmlConstants.XML_ATTRIBUTE_ALIAS_VALUE);
result.put(name, value);
}
}
}
return result;
}
/**
* Create a JDOM element from the given <code>qName</code> and the given
* <code>SAX attributes</code>.
*
* @param qName
* The given <code>qName</code>.
* @param attributes
* The given <code>SAX attributes</code>.
* @return The JDOM element that was created from the given
* <code>qName</code> and the given <code>SAX attributes</code>.
*/
private Element createElement(final String qName, final Attributes attributes) {
Element result = new Element(qName);
if (attributes != null) {
for (int i = 0; i < attributes.getLength(); i++) {
result.setAttribute(attributes.getQName(i), attributes.getValue(i));
}
}
return result;
}
/**
* Return the property persistence handler for the given property type.
*
* @param propertyType
* The property type.
* @return The property persistence handler for the given property type.
*/
private AbstractPropertyPersistenceHandler getPropertyPersistenceHandler(final String propertyType) {
AbstractPropertyPersistenceHandler result = null;
try {
result = PropertyPersistenceHandlerRegistry.getInstance()
.getPersistenceHandler(PropertyTypesEnum.createFromPortable(propertyType));
} catch (Exception e) {
LOG.error("Unknown property type <" + propertyType + ">");
}
return result;
}
}