blob: 840d21e0615441040d7447c8993bf1dcde544a8f [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.apache.openjpa.persistence.jdbc;
import static org.apache.openjpa.meta.MetaDataModes.MODE_MAPPING;
import java.sql.Types;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import javax.persistence.EnumType;
import javax.persistence.TemporalType;
import org.apache.openjpa.jdbc.conf.JDBCConfiguration;
import org.apache.openjpa.jdbc.meta.ClassMapping;
import org.apache.openjpa.jdbc.meta.ClassMappingInfo;
import org.apache.openjpa.jdbc.meta.DiscriminatorMappingInfo;
import org.apache.openjpa.jdbc.meta.FieldMapping;
import org.apache.openjpa.jdbc.meta.MappingInfo;
import org.apache.openjpa.jdbc.meta.MappingRepository;
import org.apache.openjpa.jdbc.meta.QueryResultMapping;
import org.apache.openjpa.jdbc.meta.QueryResultMapping.PCResult;
import org.apache.openjpa.jdbc.meta.SequenceMapping;
import org.apache.openjpa.jdbc.meta.ValueMappingImpl;
import org.apache.openjpa.jdbc.meta.ValueMappingInfo;
import org.apache.openjpa.jdbc.meta.strats.EnumValueHandler;
import org.apache.openjpa.jdbc.meta.strats.FlatClassStrategy;
import org.apache.openjpa.jdbc.meta.strats.FullClassStrategy;
import org.apache.openjpa.jdbc.meta.strats.VerticalClassStrategy;
import org.apache.openjpa.jdbc.schema.Column;
import org.apache.openjpa.jdbc.schema.Unique;
import org.apache.openjpa.jdbc.sql.DBDictionary;
import org.apache.openjpa.lib.meta.SourceTracker;
import org.apache.openjpa.lib.util.ClassUtil;
import org.apache.openjpa.lib.util.StringUtil;
import org.apache.openjpa.meta.ClassMetaData;
import org.apache.openjpa.meta.FieldMetaData;
import org.apache.openjpa.meta.JavaTypes;
import org.apache.openjpa.meta.MetaDataRepository;
import org.apache.openjpa.meta.SequenceMetaData;
import org.apache.openjpa.persistence.PersistenceStrategy;
import org.apache.openjpa.persistence.XMLPersistenceMetaDataSerializer;
import org.xml.sax.SAXException;
/**
* Serializes persistence mapping to XML.
*
* @since 0.4.0
* @author Steve Kim
*/
public class XMLPersistenceMappingSerializer
extends XMLPersistenceMetaDataSerializer {
private static final int TYPE_RESULTMAP = TYPE_QUERY + 1;
private static final Map<ColType, String> _names;
static {
_names = new EnumMap<>(ColType.class);
_names.put(ColType.COL, "column");
_names.put(ColType.JOIN, "join-column");
_names.put(ColType.INVERSE, "inverse-join-column");
_names.put(ColType.PK_JOIN, "primary-key-join-column");
_names.put(ColType.DISC, "discriminator-column");
}
private List<QueryResultMapping> _results = null;
private boolean _sync = false;
/**
* Constructor. Supply configuration.
*/
public XMLPersistenceMappingSerializer(JDBCConfiguration conf) {
super(conf);
}
/**
* Whether to automatically synchronize mapping info with data available
* from mapped components before serialization. Defaults to false.
*/
public boolean getSyncMappingInfo() {
return _sync;
}
/**
* Whether to automatically synchronize mapping info with data available
* from mapped components before serialization. Defaults to false.
*/
public void setSyncMappingInfo(boolean sync) {
_sync = sync;
}
/**
* Adds the given result set mapping to local cache.
*/
public void addQueryResultMapping(QueryResultMapping meta) {
if (_results == null)
_results = new ArrayList<>();
_results.add(meta);
}
/**
* Removes given result set mapping from the local cache.
*/
public boolean removeQueryResultMapping(QueryResultMapping meta) {
return _results != null && _results.remove(meta);
}
@Override
public void addAll(MetaDataRepository repos) {
super.addAll(repos);
for (QueryResultMapping res : ((MappingRepository) repos).getQueryResultMappings())
addQueryResultMapping(res);
}
@Override
public boolean removeAll(MetaDataRepository repos) {
boolean removed = super.removeAll(repos);
for (QueryResultMapping res : ((MappingRepository) repos).getQueryResultMappings())
removed |= removeQueryResultMapping(res);
return removed;
}
@Override
public void clear() {
super.clear();
if (_results != null)
_results.clear();
}
protected void addCommments(Object obj)
throws SAXException {
if (isMappingMode() && !isMetaDataMode()) {
if (obj instanceof ClassMapping)
obj = ((ClassMapping) obj).getMappingInfo();
else if (obj instanceof FieldMapping)
obj = ((FieldMapping) obj).getMappingInfo();
}
super.addComments(obj);
}
@Override
protected void serializeClass(ClassMetaData meta, boolean access)
throws SAXException {
if (_sync && isMappingMode() && meta instanceof ClassMapping) {
// sync if resolved and mapped
ClassMapping cls = (ClassMapping) meta;
if ((cls.getResolve() & MODE_MAPPING) != 0 && cls.isMapped()) {
cls.syncMappingInfo();
cls.getDiscriminator().syncMappingInfo();
cls.getVersion().syncMappingInfo();
FieldMapping[] fields;
if (cls.getEmbeddingMetaData() == null)
fields = cls.getDefinedFieldMappings();
else
fields = cls.getFieldMappings();
for (FieldMapping f : fields)
f.syncMappingInfo();
}
}
super.serializeClass(meta, access);
}
@Override
protected void serializeClassMappingContent(ClassMetaData mapping)
throws SAXException {
ClassMapping cls = (ClassMapping) mapping;
ClassMappingInfo info = cls.getMappingInfo();
serializeTable(info.getTableName(), "table", ClassUtil.getClassName(mapping.getDescribedType()), null,
info.getUniques(info.getTableName()));
for (String second : info.getSecondaryTableNames())
serializeTable(second, "secondary-table", null, info,
info.getUniques(second));
serializeColumns(info, ColType.PK_JOIN, null);
}
@Override
protected void serializeInheritanceContent(ClassMetaData mapping)
throws SAXException {
ClassMapping cls = (ClassMapping) mapping;
ClassMappingInfo info = cls.getMappingInfo();
DiscriminatorMappingInfo dinfo = cls.getDiscriminator()
.getMappingInfo();
String strat = info.getHierarchyStrategy();
if (FlatClassStrategy.ALIAS.equals(strat))
addAttribute("strategy", "SINGLE_TABLE");
else if (VerticalClassStrategy.ALIAS.equals(strat))
addAttribute("strategy", "JOINED");
else if (FullClassStrategy.ALIAS.equals(strat))
addAttribute("strategy", "TABLE_PER_CLASS");
if (strat != null) {
startElement("inheritance");
endElement("inheritance");
}
if (dinfo.getValue() != null) {
startElement("discriminator-value");
addText(dinfo.getValue());
endElement("discriminator-value");
}
serializeColumns(dinfo, ColType.DISC, null);
}
/**
* Serialize table optionally listing primary-key-joins stored
* in the given {@link ClassMappingInfo}.
*/
private void serializeTable(String table, String elementName,
String defaultName, ClassMappingInfo secondaryInfo, Unique[] uniques)
throws SAXException {
List<Column> cols = null;
if (secondaryInfo != null)
cols = (List<Column>) secondaryInfo.getSecondaryTableJoinColumns
(table);
boolean print = (cols != null && cols.size() > 0) ||
(uniques != null && uniques.length > 0);
if (table != null
&& (defaultName == null || !defaultName.equals(table))) {
print = true;
int index = table.indexOf('.');
if (index < 0)
addAttribute("name", table);
else {
Map<String, ClassMetaData> classMetaData = getClassMetaData();
Object[] keySet = null;
if(classMetaData != null)
{
keySet = classMetaData.keySet().toArray();
}
if((keySet != null) && (keySet.length > 0) && classMetaData.get(keySet[0]).getUseSchemaElement())
{
addAttribute("schema", table.substring(0, index));
}
addAttribute("name", table.substring(index + 1));
}
}
if (print) {
startElement(elementName);
if (cols != null) {
for (Column col : cols)
serializeColumn(col, ColType.PK_JOIN, null, false);
}
if (uniques != null)
for (Unique unique: uniques)
serializeUniqueConstraint(unique);
endElement(elementName);
}
}
@Override
protected boolean serializeAttributeOverride(FieldMetaData fmd,
FieldMetaData orig) {
if (orig == null || fmd == orig)
return false;
FieldMapping field = (FieldMapping) fmd;
FieldMapping field2 = (FieldMapping) orig;
if (field.getMappingInfo().hasSchemaComponents()
|| field2.getMappingInfo().hasSchemaComponents())
return true;
ValueMappingInfo info = field.getValueInfo();
List<Column> cols = (List<Column>) info.getColumns();
if (cols == null || cols.size() == 0)
return false;
ValueMappingInfo info2 = field2.getValueInfo();
List<Column> cols2 = (List<Column>) info2.getColumns();
if (cols2 == null || cols2.size() != cols.size())
return true;
if (cols.size() != 1)
return true;
Column col;
Column col2;
for (int i = 0; i < cols.size(); i++) {
col = cols.get(i);
col2 = cols2.get(i);
if (!Objects.equals(col.getName(), col2.getName()))
return true;
if (!Objects.equals(col.getTypeName(), col2.getTypeName()))
return true;
if (col.getSize() != col2.getSize())
return true;
if (col.getDecimalDigits() != col2.getDecimalDigits())
return true;
if (col.getFlag(Column.FLAG_UNINSERTABLE)
!= col2.getFlag(Column.FLAG_UNINSERTABLE))
return true;
if (col.getFlag(Column.FLAG_UNUPDATABLE)
!= col2.getFlag(Column.FLAG_UNUPDATABLE))
return true;
}
return false;
}
@Override
protected void serializeAttributeOverrideMappingContent(FieldMetaData fmd,
FieldMetaData orig)
throws SAXException {
FieldMapping fm = (FieldMapping) fmd;
serializeColumns(fm.getValueInfo(), ColType.COL, fm.getMappingInfo()
.getTableName());
}
@Override
protected PersistenceStrategy getStrategy(FieldMetaData fmd) {
PersistenceStrategy strat = super.getStrategy(fmd);
FieldMapping field = (FieldMapping) fmd;
switch (strat) {
case MANY_MANY:
// we can differentiate a one-many by the fact that there is no
// secondary table join, or the fk is unique
if (field.getMappedBy() == null
&& (field.getMappingInfo().getJoinDirection()
== MappingInfo.JOIN_NONE
|| field.getElementMapping().getValueInfo().getUnique()
!= null))
return PersistenceStrategy.ONE_MANY;
break;
case MANY_ONE:
// inverse join cols or unique fk?
if (field.getValueInfo().getJoinDirection()
== MappingInfo.JOIN_INVERSE
|| field.getValueInfo().getUnique() != null)
return PersistenceStrategy.ONE_ONE;
// scan for primary-key-join-column
List<Column> cols = field.getValueInfo().getColumns();
boolean pkJoin = cols != null && cols.size() > 0;
for (int i = 0; pkJoin && i < cols.size(); i++)
pkJoin = cols.get(i).getFlag(Column.FLAG_PK_JOIN);
if (pkJoin)
return PersistenceStrategy.ONE_ONE;
break;
}
return strat;
}
@Override
protected void serializeFieldMappingContent(FieldMetaData fmd,
PersistenceStrategy strategy)
throws SAXException {
if (fmd.getMappedBy() != null)
return;
// while I'd like to do auto detection based on join directions, etc.
// the distinguished column / table / etc names forces our hand
// esp for OpenJPA custom mappings.
FieldMapping field = (FieldMapping) fmd;
switch (strategy) {
case ONE_ONE:
case MANY_ONE:
serializeColumns(field.getValueInfo(), ColType.JOIN,
field.getMappingInfo().getTableName());
return;
case ONE_MANY:
if (field.getMappingInfo().getJoinDirection() ==
MappingInfo.JOIN_NONE) {
serializeColumns(field.getElementMapping().getValueInfo(),
ColType.JOIN, null);
return;
}
// else no break
case MANY_MANY:
if (field.getMappingInfo().hasSchemaComponents()
|| field.getElementMapping().getValueInfo()
.hasSchemaComponents()) {
String table = field.getMappingInfo().getTableName();
if (table != null) {
int index = table.indexOf('.');
if (index < 0)
addAttribute("name", table);
else {
addAttribute("schema", table.substring(0, index));
addAttribute("name", table.substring(index + 1));
}
}
startElement("join-table");
serializeColumns(field.getMappingInfo(), ColType.JOIN,
null);
serializeColumns(field.getElementMapping().getValueInfo(),
ColType.INVERSE, null);
endElement("join-table");
}
return;
case ELEM_COLL:
if (field.getMappingInfo().hasSchemaComponents()
|| field.getElementMapping().getValueInfo()
.hasSchemaComponents()) {
String table = field.getMappingInfo().getTableName();
if (table != null) {
int index = table.indexOf('.');
if (index < 0)
addAttribute("name", table);
else {
addAttribute("schema", table.substring(0, index));
addAttribute("name", table.substring(index + 1));
}
}
startElement("collection-table");
ValueMappingImpl elem =
(ValueMappingImpl) field.getElement();
serializeColumns(elem.getValueInfo(), ColType.COL,
null);
endElement("collection-table");
}
return;
}
serializeColumns(field.getValueInfo(), ColType.COL,
field.getMappingInfo().getTableName());
if (strategy == PersistenceStrategy.BASIC && isLob(field)) {
startElement("lob");
endElement("lob");
}
TemporalType temporal = getTemporal(field);
if (temporal != null) {
startElement("temporal");
addText(temporal.toString());
endElement("temporal");
}
EnumType enumType = getEnumType(field);
if (enumType != null && enumType != EnumType.ORDINAL) {
startElement("enumerated");
addText(enumType.toString());
endElement("enumerated");
}
}
/**
* Serialize order column.
*/
@Override
protected void serializeOrderColumn(FieldMetaData fmd)
throws SAXException {
FieldMapping field = (FieldMapping) fmd;
Column orderCol = field.getOrderColumn();
if (orderCol != null) {
if (orderCol.getName() != null)
addAttribute("name", orderCol.getName());
if (orderCol.isNotNull())
addAttribute("nullable", "false");
if (orderCol.getFlag(Column.FLAG_UNINSERTABLE))
addAttribute("insertable", "false");
if (orderCol.getFlag(Column.FLAG_UNUPDATABLE))
addAttribute("updatable", "false");
if (orderCol.getTypeName() != null)
addAttribute("column-definition", orderCol.getTypeName());
startElement("order-column");
endElement("order-column");
}
}
/**
* Determine if the field is a lob.
*/
private boolean isLob(FieldMapping field) {
for (Column col : (List<Column>) field.getValueInfo().getColumns())
if (col.getType() == Types.BLOB || col.getType() == Types.CLOB)
return true;
return false;
}
/**
* Return field's temporal type.
*/
private TemporalType getTemporal(FieldMapping field) {
if (field.getDeclaredTypeCode() != JavaTypes.DATE
&& field.getDeclaredTypeCode() != JavaTypes.CALENDAR)
return null;
DBDictionary dict = ((JDBCConfiguration) getConfiguration())
.getDBDictionaryInstance();
int def = dict.getJDBCType(field.getTypeCode(), false);
for (Column col : (List<Column>) field.getValueInfo().getColumns()) {
if (col.getType() == def)
continue;
switch (col.getType()) {
case Types.DATE:
return TemporalType.DATE;
case Types.TIME:
return TemporalType.TIME;
case Types.TIMESTAMP:
return TemporalType.TIMESTAMP;
}
}
return null;
}
/**
* Return enum type for the field.
*/
protected EnumType getEnumType(FieldMapping field) {
if (field.getDeclaredTypeCode() != JavaTypes.OBJECT)
return null;
if (!(field.getHandler() instanceof EnumValueHandler))
return null;
return ((EnumValueHandler) field.getHandler()).getStoreOrdinal()
? EnumType.ORDINAL : EnumType.STRING;
}
/**
* Serialize the columns in the given mapping info.
*/
private void serializeColumns(MappingInfo info, ColType type,
String secondary)
throws SAXException {
List<Column> cols = (List<Column>) info.getColumns();
if (cols == null)
return;
for (Column col : cols)
serializeColumn(col, type, secondary, info.getUnique() != null);
}
/**
* Serialize a single column.
*/
private void serializeColumn(Column col, ColType type, String secondary,
boolean unique)
throws SAXException {
if (col.getName() != null)
addAttribute("name", col.getName());
if (col.getTypeName() != null)
addAttribute("column-definition", col.getTypeName());
if (col.getTarget() != null
&& (type == ColType.JOIN || type == ColType.INVERSE
|| type == ColType.PK_JOIN))
addAttribute("referenced-column-name", col.getTarget());
if (type == ColType.COL || type == ColType.JOIN
|| type == ColType.PK_JOIN) {
if (unique)
addAttribute("unique", "true");
if (col.isNotNull())
addAttribute("nullable", "false");
if (col.getFlag(Column.FLAG_UNINSERTABLE))
addAttribute("insertable", "false");
if (col.getFlag(Column.FLAG_UNUPDATABLE))
addAttribute("updatable", "false");
if (secondary != null)
addAttribute("table", secondary);
if (type == ColType.COL) {
if (col.getSize() > 0 && col.getSize() != 255)
addAttribute("length", col.getSize() + "");
if (col.getDecimalDigits() != 0)
addAttribute("scale", col.getDecimalDigits() + "");
}
}
if (type != ColType.COL || getAttributes().getLength() > 0) {
String name = col.getFlag(Column.FLAG_PK_JOIN) ? _names
.get(ColType.PK_JOIN) : _names.get(type);
startElement(name);
endElement(name);
}
}
private void serializeUniqueConstraint(Unique unique) throws SAXException {
if (StringUtil.isNotEmpty(unique.getName())) {
addAttribute("name", unique.getName());
}
startElement("unique-constraint");
Column[] columns = unique.getColumns();
for (Column column:columns) {
startElement("column-name");
addText(column.getName());
endElement("column-name");
}
endElement("unique-constraint");
}
@Override
protected SerializationComparator newSerializationComparator() {
return new MappingSerializationComparator();
}
@Override
protected void addSystemMappingElements(Collection toSerialize) {
if (isQueryMode())
toSerialize.addAll(getQueryResultMappings(null));
}
@Override
protected int type(Object o) {
int type = super.type(o);
if (type == -1 && o instanceof QueryResultMapping)
return TYPE_RESULTMAP;
return type;
}
/**
* Return the result set mappings for the given scope.
*/
private List<QueryResultMapping> getQueryResultMappings(ClassMetaData cm) {
if (_results == null || _results.isEmpty())
return (List<QueryResultMapping>) Collections.EMPTY_LIST;
List<QueryResultMapping> result = null;
for (int i = 0; i < _results.size(); i++) {
QueryResultMapping element = _results.get(i);
if ((cm == null && element.getSourceScope() != null) || (cm != null
&& element.getSourceScope() != cm.getDescribedType()))
continue;
if (result == null)
result = new ArrayList<>(_results.size() - i);
result.add(element);
}
return (result == null)
? (List<QueryResultMapping>) Collections.EMPTY_LIST : result;
}
@Override
protected void serializeSystemMappingElement(Object obj)
throws SAXException {
if (obj instanceof QueryResultMapping)
serializeQueryResultMapping((QueryResultMapping) obj);
}
@Override
protected void serializeQueryMappings(ClassMetaData meta)
throws SAXException {
for (QueryResultMapping res : getQueryResultMappings(meta))
serializeQueryResultMapping(res);
}
/**
* Serialize given result set mapping.
*/
private void serializeQueryResultMapping(QueryResultMapping meta)
throws SAXException {
if (!getSerializeAnnotations()
&& meta.getSourceType() == SourceTracker.SRC_ANNOTATIONS)
return;
addAttribute("name", meta.getName());
startElement("sql-result-set-mapping");
for (QueryResultMapping.PCResult pc : meta.getPCResults()) {
addAttribute("entity-class", pc.getCandidateType().getName());
Object discrim = pc.getMapping(PCResult.DISCRIMINATOR);
if (discrim != null)
addAttribute("discriminator-column", discrim.toString());
startElement("entity-result");
for (String path : pc.getMappingPaths()) {
addAttribute("name", path);
addAttribute("column", pc.getMapping(path).toString());
startElement("field-result");
endElement("field-result");
}
endElement("entity-result");
}
for (Object col : meta.getColumnResults()) {
addAttribute("name", col.toString());
startElement("column-result");
endElement("column-result");
}
endElement("sql-result-set-mapping");
}
@Override
protected void serializeSequence(SequenceMetaData meta)
throws SAXException {
if (!getSerializeAnnotations()
&& meta.getSourceType() == SourceTracker.SRC_ANNOTATIONS)
return;
if (SequenceMapping.IMPL_VALUE_TABLE.equals(meta.getSequencePlugin())) {
super.serializeSequence(meta);
return;
}
SequenceMapping seq = (SequenceMapping) meta;
addAttribute("name", seq.getName());
String table = seq.getTable();
if (table != null) {
int dotIdx = table.indexOf('.');
if (dotIdx == -1)
addAttribute("table", table);
else {
addAttribute("table", table.substring(dotIdx + 1));
addAttribute("schema", table.substring(0, dotIdx));
}
}
if (!StringUtil.isEmpty(seq.getPrimaryKeyColumn()))
addAttribute("pk-column-name", seq.getPrimaryKeyColumn());
if (!StringUtil.isEmpty(seq.getSequenceColumn()))
addAttribute("value-column-name", seq.getSequenceColumn());
if (!StringUtil.isEmpty(seq.getPrimaryKeyValue()))
addAttribute("pk-column-value", seq.getPrimaryKeyValue());
if (seq.getAllocate() != 50 && seq.getAllocate() != -1)
addAttribute("allocation-size", seq.getAllocate() + "");
if (seq.getInitialValue() != 0 && seq.getInitialValue() != -1)
addAttribute("initial-value", seq.getInitialValue() + "");
startElement("table-generator");
endElement("table-generator");
}
/**
* Column types serialized under different names.
*/
private static enum ColType {
COL,
JOIN,
INVERSE,
PK_JOIN,
DISC
}
/**
* Extends {@link SerializationComparator} for store-specific tags such
* as &lt;sql-result-set-mapping&gt;.
*
* @author Pinaki Poddar
*/
protected class MappingSerializationComparator
extends SerializationComparator {
private static final long serialVersionUID = 1L;
@Override
protected int compareUnknown(Object o1, Object o2) {
if (!(o1 instanceof QueryResultMapping))
return super.compareUnknown(o1, o2);
QueryResultMapping res1 = (QueryResultMapping) o1;
QueryResultMapping res2 = (QueryResultMapping) o2;
// system scope before class scope
Object scope1 = res1.getSourceScope();
Object scope2 = res2.getSourceScope();
if (scope1 == null && scope2 != null)
return -1;
if (scope1 != null && scope2 == null)
return 1;
// compare on listing index, or if none/same, use name
int listingIndex1 = res1.getListingIndex();
int listingIndex2 = res2.getListingIndex();
if (listingIndex1 != listingIndex2)
return listingIndex1 - listingIndex2;
return res1.getName ().compareTo (res2.getName ());
}
}
}