/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2017 Serge Rider (serge@jkiss.org)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jkiss.dbeaver.model.virtual;
import org.jkiss.code.NotNull;
import org.jkiss.dbeaver.DBException;
import org.jkiss.dbeaver.model.DBPDataSource;
import org.jkiss.dbeaver.model.DBPDataSourceContainer;
import org.jkiss.dbeaver.model.DBUtils;
import org.jkiss.dbeaver.model.data.DBDAttributeTransformerDescriptor;
import org.jkiss.dbeaver.model.exec.DBCLogicalOperator;
import org.jkiss.dbeaver.model.runtime.DBRProgressMonitor;
import org.jkiss.dbeaver.model.struct.DBSEntity;
import org.jkiss.dbeaver.model.struct.DBSEntityConstraintType;
import org.jkiss.dbeaver.model.struct.DBSObject;
import org.jkiss.dbeaver.model.struct.DBSObjectContainer;
import org.jkiss.dbeaver.utils.GeneralUtils;
import org.jkiss.utils.ArrayUtils;
import org.jkiss.utils.CommonUtils;
import org.jkiss.utils.xml.SAXListener;
import org.jkiss.utils.xml.SAXReader;
import org.jkiss.utils.xml.XMLBuilder;
import org.jkiss.utils.xml.XMLException;
import org.xml.sax.Attributes;
import java.io.IOException;
import java.util.Map;
/**
* Virtual database model
*/
public class DBVModel extends DBVContainer {
private static final String TAG_CONTAINER = "container"; //$NON-NLS-1$
private static final String TAG_ENTITY = "entity"; //$NON-NLS-1$
private static final String TAG_CONSTRAINT = "constraint"; //$NON-NLS-1$
private static final String TAG_ATTRIBUTE = "attribute"; //$NON-NLS-1$
private static final String ATTR_ID = "id"; //$NON-NLS-1$
private static final String ATTR_NAME = "name"; //$NON-NLS-1$
private static final String ATTR_DESCRIPTION = "description"; //$NON-NLS-1$
private static final String ATTR_CUSTOM = "custom"; //$NON-NLS-1$
private static final String TAG_PROPERTY = "property"; //$NON-NLS-1$
private static final String ATTR_VALUE = "value"; //$NON-NLS-1$
private static final String ATTR_TYPE = "type"; //$NON-NLS-1$
private static final String TAG_COLORS = "colors";
private static final String TAG_COLOR = "color";
private static final String ATTR_OPERATOR = "operator";
private static final String ATTR_FOREGROUND = "foreground";
private static final String ATTR_BACKGROUND = "background";
private static final String TAG_VALUE = "value";
private static final String TAG_TRANSFORM = "transform";
private static final String TAG_INCLUDE = "include";
private static final String TAG_EXCLUDE = "exclude";
private DBPDataSourceContainer dataSourceContainer;
public DBVModel(DBPDataSourceContainer dataSourceContainer) {
super(null, "model");
this.dataSourceContainer = dataSourceContainer;
}
// Copy constructor
public DBVModel(DBPDataSourceContainer dataSourceContainer, DBVModel source) {
super(null, source);
this.dataSourceContainer = dataSourceContainer;
}
@Override
public DBSObjectContainer getRealContainer(DBRProgressMonitor monitor) throws DBException {
DBPDataSource dataSource = dataSourceContainer.getDataSource();
if (dataSource instanceof DBSObjectContainer) {
return (DBSObjectContainer) dataSource;
}
log.warn("Datasource '" + dataSource + "' is not an object container");
return null;
}
@NotNull
@Override
public DBPDataSource getDataSource() {
return dataSourceContainer.getDataSource();
}
/**
* Search for virtual entity descriptor
*
* @param entity entity
* @param createNew create new entity if missing
* @return entity virtual entity
*/
public DBVEntity findEntity(DBSEntity entity, boolean createNew) {
DBSObject[] path = DBUtils.getObjectPath(entity, false);
if (path.length == 0) {
log.warn("Empty entity path");
return null;
}
if (path[0] != dataSourceContainer) {
log.warn("Entity's root must be datasource container '" + dataSourceContainer.getName() + "'");
return null;
}
DBVContainer container = this;
for (int i = 1; i < path.length; i++) {
DBSObject item = path[i];
container = container.getContainer(item.getName(), createNew);
if (container == null) {
return null;
}
}
return container.getEntity(entity.getName(), createNew);
}
public void serialize(XMLBuilder xml) throws IOException {
serializeContainer(xml, this);
}
static private void serializeContainer(XMLBuilder xml, DBVContainer object) throws IOException {
if (!object.hasValuableData()) {
// nothing to save
return;
}
xml.startElement(TAG_CONTAINER);
xml.addAttribute(ATTR_NAME, object.getName());
// Containers
for (DBVContainer container : object.getContainers()) {
serializeContainer(xml, container);
}
for (DBVEntity entity : object.getEntities()) {
if (entity.hasValuableData()) {
serializeEntity(xml, entity);
}
}
xml.endElement();
}
private static void serializeEntity(XMLBuilder xml, DBVEntity entity) throws IOException {
xml.startElement(TAG_ENTITY);
xml.addAttribute(ATTR_NAME, entity.getName());
if (!CommonUtils.isEmpty(entity.getDescriptionColumnNames())) {
xml.addAttribute(ATTR_DESCRIPTION, entity.getDescriptionColumnNames());
}
if (!CommonUtils.isEmpty(entity.properties)) {
for (Map.Entry<String, String> prop : entity.properties.entrySet()) {
xml.startElement(TAG_PROPERTY);
xml.addAttribute(ATTR_NAME, prop.getKey());
xml.addAttribute(ATTR_VALUE, prop.getValue());
xml.endElement();
}
}
// Attributes
for (DBVEntityAttribute attr : CommonUtils.safeCollection(entity.entityAttributes)) {
try (final XMLBuilder.Element e3 = xml.startElement(TAG_ATTRIBUTE)) {
xml.addAttribute(ATTR_NAME, attr.getName());
final DBVTransformSettings transformSettings = attr.getTransformSettings();
if (transformSettings != null && transformSettings.hasValuableData()) {
try (final XMLBuilder.Element e4 = xml.startElement(TAG_TRANSFORM)) {
if (!CommonUtils.isEmpty(transformSettings.getCustomTransformer())) {
xml.addAttribute(ATTR_CUSTOM, transformSettings.getCustomTransformer());
}
for (String id : CommonUtils.safeCollection(transformSettings.getIncludedTransformers())) {
try (final XMLBuilder.Element e5 = xml.startElement(TAG_INCLUDE)) {
xml.addAttribute(ATTR_ID, id);
}
}
for (String id : CommonUtils.safeCollection(transformSettings.getExcludedTransformers())) {
try (final XMLBuilder.Element e5 = xml.startElement(TAG_EXCLUDE)) {
xml.addAttribute(ATTR_ID, id);
}
}
final Map<String, String> transformOptions = transformSettings.getTransformOptions();
if (transformOptions != null) {
for (Map.Entry<String, String> prop : transformOptions.entrySet()) {
try (final XMLBuilder.Element e5 = xml.startElement(TAG_PROPERTY)) {
if (prop.getValue() != null) {
xml.addAttribute(ATTR_NAME, prop.getKey());
xml.addAttribute(ATTR_VALUE, prop.getValue());
}
}
}
}
}
}
}
}
// Constraints
for (DBVEntityConstraint c : CommonUtils.safeCollection(entity.entityConstraints)) {
if (c.hasAttributes()) {
xml.startElement(TAG_CONSTRAINT);
xml.addAttribute(ATTR_NAME, c.getName());
xml.addAttribute(ATTR_TYPE, c.getConstraintType().getName());
for (DBVEntityConstraintColumn cc : CommonUtils.safeCollection(c.getAttributeReferences(null))) {
xml.startElement(TAG_ATTRIBUTE);
xml.addAttribute(ATTR_NAME, cc.getAttributeName());
xml.endElement();
}
xml.endElement();
}
}
// Colors
if (!CommonUtils.isEmpty(entity.colorOverrides)) {
xml.startElement(TAG_COLORS);
for (DBVColorOverride color : entity.colorOverrides) {
xml.startElement(TAG_COLOR);
xml.addAttribute(ATTR_NAME, color.getAttributeName());
xml.addAttribute(ATTR_OPERATOR, color.getOperator().name());
if (color.getColorForeground() != null) {
xml.addAttribute(ATTR_FOREGROUND, color.getColorForeground());
}
if (color.getColorBackground() != null) {
xml.addAttribute(ATTR_BACKGROUND, color.getColorBackground());
}
if (!ArrayUtils.isEmpty(color.getAttributeValues())) {
for (Object value : color.getAttributeValues()) {
if (value == null) {
continue;
}
xml.startElement(TAG_VALUE);
xml.addText(GeneralUtils.serializeObject(value));
xml.endElement();
}
}
xml.endElement();
}
xml.endElement();
}
xml.endElement();
}
public SAXListener getModelParser() {
return new ModelParser();
}
public void copyFrom(DBVModel model) {
super.copyFrom(model);
}
class ModelParser implements SAXListener {
private DBVContainer curContainer = null;
private DBVEntity curEntity = null;
private DBVEntityAttribute curAttribute = null;
private DBVTransformSettings curTransformSettings = null;
private DBVEntityConstraint curConstraint;
private DBVColorOverride curColor;
private boolean colorValue = false;
@Override
public void saxStartElement(SAXReader reader, String namespaceURI, String localName, Attributes atts)
throws XMLException {
switch (localName) {
case TAG_CONTAINER:
if (curContainer == null) {
curContainer = DBVModel.this;
} else {
DBVContainer container = new DBVContainer(
curContainer,
atts.getValue(ATTR_NAME));
curContainer.addContainer(container);
curContainer = container;
}
break;
case TAG_ENTITY:
curEntity = new DBVEntity(
curContainer,
atts.getValue(ATTR_NAME),
atts.getValue(ATTR_DESCRIPTION));
curContainer.addEntity(curEntity);
break;
case TAG_PROPERTY:
if (curTransformSettings != null) {
curTransformSettings.setTransformOption(
atts.getValue(ATTR_NAME),
atts.getValue(ATTR_VALUE));
} else if (curEntity != null) {
curEntity.setProperty(
atts.getValue(ATTR_NAME),
atts.getValue(ATTR_VALUE));
}
break;
case TAG_CONSTRAINT:
if (curEntity != null) {
curConstraint = new DBVEntityConstraint(
curEntity,
DBSEntityConstraintType.VIRTUAL_KEY,
atts.getValue(ATTR_NAME));
curEntity.addConstraint(curConstraint);
}
break;
case TAG_ATTRIBUTE:
if (curConstraint != null) {
curConstraint.addAttribute(atts.getValue(ATTR_NAME));
} else if (curAttribute != null) {
DBVEntityAttribute childAttribute = new DBVEntityAttribute(curEntity, curAttribute, atts.getValue(ATTR_NAME));
curAttribute.addChild(childAttribute);
curAttribute = childAttribute;
} else if (curEntity != null) {
curAttribute = new DBVEntityAttribute(curEntity, null, atts.getValue(ATTR_NAME));
curEntity.addVirtualAttribute(curAttribute);
}
break;
case TAG_TRANSFORM:
curTransformSettings = new DBVTransformSettings();
curTransformSettings.setCustomTransformer(atts.getValue(ATTR_CUSTOM));
if (curAttribute != null) {
curAttribute.setTransformSettings(curTransformSettings);
} else if (curEntity != null) {
curEntity.setTransformSettings(curTransformSettings);
}
break;
case TAG_INCLUDE:
case TAG_EXCLUDE:
String transformerId = atts.getValue(ATTR_ID);
if (curTransformSettings != null && !CommonUtils.isEmpty(transformerId)) {
final DBDAttributeTransformerDescriptor transformer = dataSourceContainer.getPlatform().getValueHandlerRegistry().getTransformer(transformerId);
if (transformer == null) {
log.warn("Transformer '" + transformerId + "' not found");
} else {
curTransformSettings.enableTransformer(transformer, TAG_INCLUDE.equals(localName));
}
}
break;
case TAG_COLOR:
if (curEntity != null) {
try {
curColor = new DBVColorOverride(
atts.getValue(ATTR_NAME),
DBCLogicalOperator.valueOf(atts.getValue(ATTR_OPERATOR)),
null,
atts.getValue(ATTR_FOREGROUND),
atts.getValue(ATTR_BACKGROUND)
);
curEntity.addColorOverride(curColor);
} catch (Throwable e) {
log.warn("Error reading color settings", e);
}
}
break;
case TAG_VALUE:
if (curColor != null) {
colorValue = true;
}
break;
}
}
@Override
public void saxText(SAXReader reader, String data) {
if (colorValue) {
curColor.addAttributeValue(GeneralUtils.deserializeObject(data));
}
}
@Override
public void saxEndElement(SAXReader reader, String namespaceURI, String localName) {
switch (localName) {
case TAG_CONTAINER:
curContainer = curContainer.getParentObject();
break;
case TAG_ENTITY:
curEntity = null;
break;
case TAG_CONSTRAINT:
curConstraint = null;
break;
case TAG_ATTRIBUTE:
if (curAttribute != null) {
curAttribute = curAttribute.getParent();
}
break;
case TAG_TRANSFORM:
curTransformSettings = null;
case TAG_COLOR:
curColor = null;
break;
case TAG_VALUE:
if (curColor != null) {
colorValue = false;
}
break;
}
}
}
}