/*
* JBoss, Home of Professional Open Source.
*
* See the LEGAL.txt file distributed with this work for information regarding copyright ownership and licensing.
*
* See the AUTHORS.txt file distributed with this work for a full listing of individual contributors.
*/
package org.teiid.designer.metamodels.xsd;
import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Stack;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.MultiStatus;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.Status;
import org.eclipse.emf.common.util.URI;
import org.eclipse.xsd.XSDComplexTypeDefinition;
import org.eclipse.xsd.XSDCompositor;
import org.eclipse.xsd.XSDConstraint;
import org.eclipse.xsd.XSDElementDeclaration;
import org.eclipse.xsd.XSDFactory;
import org.eclipse.xsd.XSDImport;
import org.eclipse.xsd.XSDInclude;
import org.eclipse.xsd.XSDModelGroup;
import org.eclipse.xsd.XSDParticle;
import org.eclipse.xsd.XSDSchema;
import org.eclipse.xsd.XSDTypeDefinition;
import org.eclipse.xsd.util.XSDConstants;
import org.eclipse.xsd.util.XSDResourceImpl;
import org.teiid.core.designer.TeiidDesignerRuntimeException;
import org.teiid.core.designer.util.CoreArgCheck;
import org.teiid.designer.core.ModelerCore;
import org.teiid.designer.core.container.Container;
import org.teiid.designer.core.validation.rules.StringNameValidator;
import org.teiid.designer.core.workspace.ModelResource;
import org.teiid.designer.metamodels.relational.AccessPattern;
import org.teiid.designer.metamodels.relational.BaseTable;
import org.teiid.designer.metamodels.relational.Column;
import org.teiid.designer.metamodels.relational.DirectionKind;
import org.teiid.designer.metamodels.relational.ForeignKey;
import org.teiid.designer.metamodels.relational.NullableType;
import org.teiid.designer.metamodels.relational.PrimaryKey;
import org.teiid.designer.metamodels.relational.Procedure;
import org.teiid.designer.metamodels.relational.ProcedureParameter;
import org.teiid.designer.metamodels.relational.ProcedureResult;
import org.teiid.designer.metamodels.relational.RelationalEntity;
import org.teiid.designer.metamodels.relational.UniqueKey;
import org.teiid.designer.metamodels.relational.View;
/**
* Build XSD models from a set of Relational Entities.
*
* @since 8.0
*/
public class XsdSchemaBuilderImpl {
// Constants for creating entity names
private static final String TYPE_SUFFIX = XsdPlugin.Util.getString("XsdSchemaBuilderImpl.type"); //$NON-NLS-1$
private static final String ROOT_SUFFIX = XsdPlugin.Util.getString("XsdSchemaBuilderImpl.rootSuffix"); //$NON-NLS-1$
private static final String INPUT_SUFFIX = XsdPlugin.Util.getString("XsdSchemaBuilderImpl.inputSuffix"); //$NON-NLS-1$
private static final String OUTPUT_SUFFIX = XsdPlugin.Util.getString("XsdSchemaBuilderImpl.outputSuffix"); //$NON-NLS-1$
// private static final String XSD_EXTENSION = ".xsd"; //$NON-NLS-1$
private static final String MM_URI = "http://www.metamatrix.com/"; //$NON-NLS-1$
// Used to enable Unit Testing.
public static boolean HEADLESS = false;
private final XSDFactory factory = XSDFactory.eINSTANCE;
private final XsdBuilderOptions ops;
private final StringNameValidator nameValidator = new StringNameValidator();
private final StringNameValidator inputNameValidator = new StringNameValidator();
private final Collection addedNamespaces = new HashSet();
private final Collection includedSchemas = new HashSet();
private final Collection completedRoots = new HashSet();
private final HashMap scoreMap = new HashMap();
private final Collection outputRoots = new HashSet();
private final boolean doFlat;
private MultiStatus result;
private XSDSchema reusableSchema;
private XSDResourceImpl currentResource;
private ModelResource modelResource;
private Collection rootElements;
private Collection inputRootElements;
private HashMap outputToInputMap;
private HashMap tableToOutputMap;
private HashMap refMap;
private boolean trackMappings = false;
private int currentScore;
/**
* Public constructor
*/
public XsdSchemaBuilderImpl( final XsdBuilderOptions ops ) {
super();
CoreArgCheck.isNotNull(ops);
CoreArgCheck.isNotNull(ops.getRoots());
this.ops = ops;
this.doFlat = ops.isFlat();
}
/**
* Static method to use for building XSDs
*
* @param ops - Options to use
* @param monitor - Progress Monitor
* @return - The StringBuffer of messages for the user.
*/
public static MultiStatus buildSchemas( final XsdBuilderOptions ops, // NO_UCD
IProgressMonitor monitor ) {
XsdSchemaBuilderImpl builder = new XsdSchemaBuilderImpl(ops);
return builder.buildSchemas(monitor, null);
}
/**
* Return the set of Root Elements
*/
public Collection getRootElements() {
if (this.rootElements == null) {
this.rootElements = new ArrayList();
}
return this.rootElements;
}
/**
* Return the set of Root Input Elements
*/
public Collection getInputRootElements() {
if (this.inputRootElements == null) {
this.inputRootElements = new ArrayList();
}
return this.inputRootElements;
}
/**
* Root method for building XSDs.
*
* @param monitor
* @return the StringBuffer containg messages to the user.
*/
public MultiStatus buildSchemas( IProgressMonitor monitor,
MultiStatus result ) {
// Initialize Instance variables
this.result = result == null ? new MultiStatus(XsdPlugin.PLUGIN_ID, 0,
XsdPlugin.Util.getString("XsdSchemaBuilderImpl.result"), null) : result; //$NON-NLS-1$
rootElements = new ArrayList();
inputRootElements = new ArrayList();
outputToInputMap = new HashMap();
tableToOutputMap = new HashMap();
refMap = new HashMap();
trackMappings = ops.genInput() && ops.genOutput();
monitor = monitor == null ? new NullProgressMonitor() : monitor;
// Intialize Txn if required
final String txnDescr = XsdPlugin.Util.getString("XsdSchemaBuilderImpl.genxsd"); //$NON-NLS-1$
final boolean startedTxn = ModelerCore.startTxn(true, true, txnDescr, this);
final Collection inputRoots = new ArrayList();
try {
Collection roots = null;
// Process the roots. If flat, just get them from the Ops. Else,
// Score the roots and re-order in the collection by score
if (doFlat) {
roots = ops.getRoots();
} else {
buildScoreMap(ops.getRoots());
roots = buildScoredRootsCollection();
}
monitor.beginTask(XsdPlugin.Util.getString("XsdSchemaBuilderImpl.creating", roots.size()), roots.size()); //$NON-NLS-1$
// Must have at least one root.
if (roots.isEmpty()) {
final String noRoots = XsdPlugin.Util.getString("XsdSchemaBuilderImpl.noRoots"); //$NON-NLS-1$
addStatus(IStatus.ERROR, noRoots, null);
}
// Ensure all staring objects are Tables or Procedure Results.
validateRoots(roots);
// Create the output model
if (ops.genOutput()) {
currentResource = createModel(false);
}
// Process all the roots, building the Output.xsd
// and Capturing roots that need to be processed as inputs.
final Iterator rootIt = roots.iterator();
while (rootIt.hasNext()) {
final RelationalEntity root = (RelationalEntity)rootIt.next();
// Capture the Item that should be used for Inputs
// exclude tables that do not have primary key or access pattern
// and procedures that do not have input parameter
if (root instanceof ProcedureResult) {
Procedure proc = ((ProcedureResult)root).getProcedure();
if (hasInputParameter(proc)) {
inputRoots.add(proc);
}
} else if (root instanceof BaseTable) {
final BaseTable table = (BaseTable)root;
if (table.getPrimaryKey() != null) {
inputRoots.add(root);
} else if (!table.getAccessPatterns().isEmpty()) {
inputRoots.add(root);
}
} else if (root instanceof View) {
final View view = (View)root;
if (!view.getAccessPatterns().isEmpty()) {
inputRoots.add(root);
}
} else if (root instanceof Procedure) {
if (hasInputParameter((Procedure)root)) {
inputRoots.add(root);
}
}
if (ops.genOutput()) {
// Execute the build for the current root object
doBuild(currentResource, root, false);
}
// If we are done processing roots, do the save
boolean doSave = ops.genOutput() && !rootIt.hasNext();
try {
if (doSave && modelResource != null) {
modelResource.save(new NullProgressMonitor(), true); // Use the ModelResource to save (related to defect
// 13670)
modelResource.getEmfResource().setModified(false);
}
} catch (Exception e) {
final String saveError = XsdPlugin.Util.getString("XsdSchemaBuilderImpl.errSave", modelResource.getItemName()); //$NON-NLS-1$
addStatus(IStatus.ERROR, saveError, e);
}
monitor.worked(1);
}
// Reset the completed roots variable prior to processing inputs
completedRoots.clear();
// If we are creating input docs, execute that build.
if (ops.genInput()) {
createInputXsd(inputRoots);
}
// Cleanup all instance variables to release memory
cleanup();
return this.result;
} catch (Exception e) {
final String err = XsdPlugin.Util.getString("XsdSchemaBuilderImpl.err1"); //$NON-NLS-1$
addStatus(IStatus.ERROR, err, e);
return this.result;
} finally {
// This Txn is not undoable and not significant. Always commit.
if (startedTxn) {
ModelerCore.commitTxn();
}
}
}
private void cleanup() {
completedRoots.clear();
currentResource = null;
addedNamespaces.clear();
includedSchemas.clear();
outputRoots.clear();
this.inputNameValidator.clearExistingNames();
inputRootElements = null;
modelResource = null;
reusableSchema = null;
tableToOutputMap = null;
refMap = null;
}
/*
* Private method used to create the input / output xsd resource(s)
*/
private XSDResourceImpl createModel( boolean isInput ) throws CoreException {
final String parentLocation = ops.getParentPath();
String rootName;
String fileName;
// Create the model name using the user prefs
if (isInput) {
rootName = ops.getInputModelName();
fileName = parentLocation + File.separator + rootName;
} else {
rootName = ops.getOutputModelName();
fileName = parentLocation + File.separator + rootName;
}
// check for existing file
IWorkspaceRoot wsroot = ModelerCore.getWorkspace().getRoot();
File check = new File(fileName);
if (reusableSchema == null && check.exists()) {
final String fileExists = XsdPlugin.Util.getString("XsdSchemaBuilderImpl.nameExists", rootName, fileName); //$NON-NLS-1$
addStatus(IStatus.ERROR, fileExists, null);
return null;
}
IFile resource = wsroot.getFileForLocation(new Path(fileName));
XSDResourceImpl rsrc = null;
if (HEADLESS) {
// Don't assume ModelResources so we can UnitTest
Container cntr = ModelerCore.getModelContainer();
rsrc = (XSDResourceImpl)cntr.getOrCreateResource(URI.createFileURI(fileName));
} else if (reusableSchema != null) {
// We are already working with a model, just get that model
modelResource = ModelerCore.getModelWorkspace().findModelResource(resource);
if (modelResource != null) {
rsrc = (XSDResourceImpl)modelResource.getEmfResource();
}
} else {
// No model created yet, so create one
modelResource = ModelerCore.create(resource);
if (modelResource != null) {
// Add a status that the model was created.
addStatus(IStatus.INFO, XsdPlugin.Util.getString("XsdSchemaBuilderImpl.newModel", resource.getName()), null); //$NON-NLS-1$
rsrc = (XSDResourceImpl)modelResource.getEmfResource();
}
}
return rsrc;
}
/*
* Helper method to return true of the given procedure has input params
*/
private boolean hasInputParameter( Procedure proc ) {
final Iterator params = proc.getParameters().iterator();
while (params.hasNext()) {
final ProcedureParameter param = (ProcedureParameter)params.next();
final DirectionKind dir = param.getDirection();
if (dir.equals(DirectionKind.IN_LITERAL) || dir.equals(DirectionKind.INOUT_LITERAL)) {
return true;
}
}
return false;
}
/*
* Helper to add a new child status to the instance multistatus result
*/
private void addStatus( final int severity,
final String msg,
final Throwable ex ) {
result.add(new Status(severity, result.getPlugin(), 0, msg, ex));
}
/*
* Create the input documents from the given Procedures using their input parameters
* to build the document
*/
private void createInputXsd( final Collection inputRoots ) throws CoreException {
// If no inputs, return
if (inputRoots.isEmpty()) {
return;
}
// Reset the reference map
refMap.clear();
// Reset the reusable schema instance variable
reusableSchema = null;
// Set the current resource to the newly created input xsd
currentResource = createModel(true);
// Process the input roots
final Iterator rootIt = inputRoots.iterator();
while (rootIt.hasNext()) {
final RelationalEntity root = (RelationalEntity)rootIt.next();
if (doFlat || outputRoots.contains(root)) {
doBuild(currentResource, root, true);
}
}
// Force save after setting these properties.
try {
if (modelResource != null) {
modelResource.save(new NullProgressMonitor(), true); // Use the ModelResource to save (related to defect 13670)
modelResource.getEmfResource().setModified(false);
}
} catch (Exception e) {
final String saveError = XsdPlugin.Util.getString("XsdSchemaBuilderImpl.errSave", modelResource.getItemName()); //$NON-NLS-1$
addStatus(IStatus.ERROR, saveError, e);
}
}
/*
* Helper method to score the given roots for nested xsd creation.
* Add one to the current score for each child base table that can be
* found by navigating fk - pk relationships
*/
private void buildScoreMap( final Collection roots ) {
final Iterator rootIt = roots.iterator();
while (rootIt.hasNext()) {
Object next = rootIt.next();
currentScore = 0;
scoreEntity(next);
final Integer score = new Integer(currentScore);
Collection entities = (Collection)scoreMap.get(score);
if (entities == null) {
entities = new HashSet();
scoreMap.put(score, entities);
}
entities.add(next);
}
}
/*
* Build a new collection ordered by score so that we process entities
* with highest score first. This will produce the most likely intended
* result for the user
*/
private Collection buildScoredRootsCollection() {
final List scoredRoots = new ArrayList();
int count = 0;
int score = 0;
while (count < scoreMap.size()) {
final Integer nextScore = new Integer(score++);
final Collection nextRoots = (Collection)scoreMap.get(nextScore);
if (nextRoots != null && !nextRoots.isEmpty()) {
scoredRoots.addAll(0, nextRoots);
count++;
}
}
return scoredRoots;
}
/*
* Recurse down through any base tables, adding to the current score
* for any child tables that can be found by navigationg fk - pk relationships
*/
private void scoreEntity( Object entity ) {
if (entity instanceof BaseTable) {
final HashSet processed = new HashSet();
scoreTable((BaseTable)entity, processed);
}
}
/*
* Recurse down through the give base table, adding to the current score
* for any child tables that can be found by navigationg fk - pk relationships
*/
private void scoreTable( BaseTable table,
final HashSet processed ) {
processed.add(table);
final Iterator fks = table.getForeignKeys().iterator();
while (fks.hasNext()) {
final ForeignKey fk = (ForeignKey)fks.next();
final UniqueKey pk = fk.getUniqueKey();
final BaseTable bt = pk == null ? null : pk.getTable();
if (!processed.contains(bt) && pk.getTable() != null) {
currentScore++;
scoreTable(pk.getTable(), processed);
}
}
}
/*
* Execute the actual build for the given relational root entity
*/
private void doBuild( final XSDResourceImpl rsrc,
final RelationalEntity root,
final boolean isInput ) {
// If we don't have a resource, return
if (rsrc == null) {
return;
}
// If we are building nested xsd and we've already built this root as a child
// somewhere else, don't rebuild it as a root entity
if (isInput || !completedRoots.contains(root)) {
buildEntities(root, new Stack(), isInput, null);
}
}
/*
* Create the xsd structure under the given type for the given relational entity
*/
private void addStructure( final XSDComplexTypeDefinition type,
final RelationalEntity entity,
final boolean isInput ) {
// Add an Element for each Column from the Table, PrimaryKey
// Procedure Result, or AccessPattern or Input Parameter from the Procedure.
final Collection inParams = new ArrayList();
Collection atts = new ArrayList();
if (entity instanceof BaseTable) {
final BaseTable table = (BaseTable)entity;
if (isInput) {
// Use the Primary Key and AccessPatterns to build the Input Document
final PrimaryKey pk = table.getPrimaryKey();
if (pk != null) {
final Iterator pkCols = pk.getColumns().iterator();
while (pkCols.hasNext()) {
final Object nextCol = pkCols.next();
if (!atts.contains(nextCol)) {
atts.add(nextCol);
}
}
}
final Iterator accessPatterns = table.getAccessPatterns().iterator();
while (accessPatterns.hasNext()) {
final AccessPattern ap = (AccessPattern)accessPatterns.next();
final Iterator apCols = ap.getColumns().iterator();
while (apCols.hasNext()) {
final Object nextCol = apCols.next();
if (!atts.contains(nextCol)) {
atts.add(nextCol);
}
}
}
} else {
atts = new ArrayList(table.getColumns());
}
} else if (entity instanceof View) {
final View view = (View)entity;
if (isInput) {
// Use the AccessPatterns to build the Input Document
final Iterator accessPatterns = view.getAccessPatterns().iterator();
while (accessPatterns.hasNext()) {
final AccessPattern ap = (AccessPattern)accessPatterns.next();
final Iterator apCols = ap.getColumns().iterator();
while (apCols.hasNext()) {
final Object nextCol = apCols.next();
if (!atts.contains(nextCol)) {
atts.add(nextCol);
}
}
}
} else {
atts = new ArrayList(view.getColumns());
}
} else if (entity instanceof ProcedureResult) {
final ProcedureResult procResult = (ProcedureResult)entity;
if (result != null) {
atts = new ArrayList(procResult.getColumns());
}
// Also add the procedure input params to the sequence
final Procedure p = procResult.getProcedure();
final Iterator params = p.getParameters().iterator();
while (params.hasNext()) {
final ProcedureParameter param = (ProcedureParameter)params.next();
final DirectionKind dir = param.getDirection();
if (dir.equals(DirectionKind.IN_LITERAL) || dir.equals(DirectionKind.INOUT_LITERAL)) {
atts.add(param);
}
}
} else if (entity instanceof Procedure) {
final Iterator params = ((Procedure)entity).getParameters().iterator();
while (params.hasNext()) {
final ProcedureParameter param = (ProcedureParameter)params.next();
final DirectionKind dir = param.getDirection();
if (isInput && dir.equals(DirectionKind.IN_LITERAL) || dir.equals(DirectionKind.INOUT_LITERAL)) {
atts.add(param);
} else if (!isInput) {
// Add OUT params to the Element first and then the INs after as we assume
// During Doc generation that the out params are first.
if (dir.equals(DirectionKind.OUT_LITERAL) || dir.equals(DirectionKind.INOUT_LITERAL)) {
atts.add(param);
} else {
inParams.add(param);
}
}
}
} else {
final String msg = XsdPlugin.Util.getString("XsdSchemaBuilderImpl.invalidRoot", entity.getClass().getName()); //$NON-NLS-1$
addStatus(IStatus.ERROR, msg, null);
}
atts.addAll(inParams);
final Iterator attsIterator = atts.iterator();
while (attsIterator.hasNext()) {
final RelationalEntity att = (RelationalEntity)attsIterator.next();
final XSDElementDeclaration next = factory.createXSDElementDeclaration();
next.setName(att.getName());
XSDTypeDefinition attType = null;
if (att instanceof Column) {
final Column col = (Column)att;
attType = (XSDTypeDefinition)col.getType();
final NullableType nullable = col.getNullable();
next.setNillable(NullableType.NULLABLE_LITERAL == nullable);
} else {
final ProcedureParameter param = (ProcedureParameter)att;
attType = (XSDTypeDefinition)param.getType();
final NullableType nullable = param.getNullable();
next.setNillable(NullableType.NULLABLE_LITERAL == nullable);
final String defaultVal = param.getDefaultValue();
if (defaultVal != null) {
next.setLexicalValue(defaultVal);
next.setConstraint(XSDConstraint.DEFAULT_LITERAL);
}
}
// Add imports for the datatypes if required
addImportForType(reusableSchema, attType);
next.setTypeDefinition(attType);
// Add the new element as a child of the complex type
addChild(type, next, false);
}
}
/*
* Create the elements and complex types for the given relational element
*/
private boolean buildEntities( final RelationalEntity entity,
final Stack recursionStack,
final boolean isInput,
XSDComplexTypeDefinition parent ) {
// Check to see if we are back to a starting point if building a loop of
// inter-related fk - pk tables (nested xsd path only)
if (recursionStack.contains(entity)) {
return false;
}
// Add this entity to the recursion check and as a completed root entity
recursionStack.push(entity);
completedRoots.add(entity);
String rootName = entity.getName();
String uniqueName = null;
if (isInput) {
uniqueName = this.inputNameValidator.createValidUniqueName(rootName + INPUT_SUFFIX);
} else {
uniqueName = this.nameValidator.createValidUniqueName(rootName + OUTPUT_SUFFIX);
}
// if null, that means original name was unique and valid
if (uniqueName == null) {
if (isInput) {
uniqueName = rootName + INPUT_SUFFIX;
} else {
uniqueName = rootName + OUTPUT_SUFFIX;
}
}
// If a base table's primary key is referenced by more than one foreign key,
// we need to build it as a global element that is referenced by all the
// referencing foreign keys.
boolean doRef = false;
if (!isInput && !doFlat && (entity instanceof BaseTable)) {
final BaseTable bt = (BaseTable)entity;
if (bt.getPrimaryKey() != null && bt.getPrimaryKey().getForeignKeys().size() > 1) {
doRef = true;
}
}
// Create the new element. If there is a passed in parent, the element will be
// added as a child of that complex type, else, it will be a global element
final XSDElementDeclaration element = createElement(isInput, parent, entity, uniqueName);
XSDElementDeclaration refd = null;
if (doRef) {
// Check to see if we've already built the global element
refd = (XSDElementDeclaration)refMap.get(entity);
if (refd != null) {
// No need to do anything else, return
element.setResolvedElementDeclaration(refd);
return true;
}
// Referenced global element does not exist yet, create and populate it.
refd = createElement(isInput, null, entity, uniqueName);
refMap.put(entity, refd);
}
// Create the complex type
XSDComplexTypeDefinition type = factory.createXSDComplexTypeDefinition();
if (parent == null || doRef) {
reusableSchema.getContents().add(type);
// If this is going to be a ref set type on the referenced element
if (refd != null) {
refd.setTypeDefinition(type);
} else {
element.setTypeDefinition(type);
}
type.setName(uniqueName + TYPE_SUFFIX);
} else {
// This is not a global type, set it as an anonymous type
element.setAnonymousTypeDefinition(type);
}
// Add the structure for the entity to the type
addStructure(type, entity, isInput);
// For the output XSD recurse through all primary key - foreign key relationships adding structure
if (!isInput && !doFlat && entity instanceof BaseTable) {
final BaseTable bt = (BaseTable)entity;
final Iterator fks = bt.getForeignKeys().iterator();
while (fks.hasNext()) {
final ForeignKey fk = (ForeignKey)fks.next();
if (fk.getUniqueKey() != null && fk.getUniqueKey().getTable() != null) {
final BaseTable child = fk.getUniqueKey().getTable();
// recursive call to build child element structure
final boolean added = buildEntities(child, recursionStack, isInput, type);
if (added && !recursionStack.isEmpty()) {
recursionStack.pop();
}
}
}
}
// If this is a reference and we had to create the referenced element,
// set the resolved element declaration now.
if (doRef && refd != null) {
element.setResolvedElementDeclaration(refd);
}
return true;
}
/*
* Helper to create an element under the given type for the given relational entity
*/
private XSDElementDeclaration createElement( final boolean isInput,
final XSDComplexTypeDefinition parent,
final RelationalEntity entity,
final String uniqueName ) {
// Init the reusable schema
if (reusableSchema == null) {
initReusableSchema(uniqueName);
}
XSDElementDeclaration element = factory.createXSDElementDeclaration();
XSDElementDeclaration wrapper = null;
if (isInput) {
element.setName(uniqueName);
if (parent == null) {
// Parent == null means this is a root entity
reusableSchema.getContents().add(element);
inputRootElements.add(element);
final XSDElementDeclaration outGlobal = (XSDElementDeclaration)tableToOutputMap.get(entity);
if (trackMappings && outGlobal != null) {
// Track mappings when building input, ouput AND XML
outputToInputMap.put(outGlobal, element);
}
} else {
// Add as a child of the given parent
addChild(parent, element, false);
}
} else {
element.setName(uniqueName);
// Create the Global Element and Wrapper for the output
if (parent == null) {
// Parent == null means this is a root entity...
// Create a wrapper global element and anonymous type
if (entity instanceof ProcedureResult) {
outputRoots.add(((ProcedureResult)entity).getProcedure());
} else {
outputRoots.add(entity);
}
wrapper = factory.createXSDElementDeclaration();
wrapper.setName(uniqueName + ROOT_SUFFIX);
final XSDComplexTypeDefinition elementType = factory.createXSDComplexTypeDefinition();
element.setAnonymousTypeDefinition(elementType);
addChild(elementType, wrapper, true);
reusableSchema.getContents().add(element);
rootElements.add(element);
// Capture the mapping between table and element to be used downstream when
// creating xml view mappings
if (trackMappings) {
// Track mappings when building input, output AND XML
if (entity instanceof ProcedureResult) {
// Use the proc instead of the result as the proc will be used
// as the root for generating the input.
final Procedure tmp = ((ProcedureResult)entity).getProcedure();
tableToOutputMap.put(tmp, element);
} else {
tableToOutputMap.put(entity, element);
}
}
} else {
// Add as a child of the given parent
addChild(parent, element, false);
}
}
if (wrapper != null) {
// Return the wrapper instead of the global as we want to build below the wrapper
// NOT the global element
return wrapper;
}
return element;
}
/*
* Helper to add an element as a child of the given complex type
*/
private void addChild( final XSDComplexTypeDefinition parent,
final XSDElementDeclaration element,
boolean isRoot ) {
XSDParticle particle = null;
XSDModelGroup sequence = null;
if (parent.getContent() != null && parent.getContent() instanceof XSDParticle) {
particle = (XSDParticle)parent.getContent();
if (particle.getContent() instanceof XSDModelGroup) {
sequence = (XSDModelGroup)particle.getContent();
} else {
sequence = factory.createXSDModelGroup();
sequence.setCompositor(XSDCompositor.SEQUENCE_LITERAL);
particle.setContent(sequence);
}
} else {
particle = factory.createXSDParticle();
sequence = factory.createXSDModelGroup();
sequence.setCompositor(XSDCompositor.SEQUENCE_LITERAL);
particle.setContent(sequence);
parent.setContent(particle);
}
final XSDParticle holder = factory.createXSDParticle();
holder.setContent(element);
if (isRoot) {
holder.setMaxOccurs(-1);
} else {
holder.setMaxOccurs(1);
}
holder.setMinOccurs(0);
sequence.getContents().add(holder);
}
/*
* Initialize the reusable schema for the current resource
*/
private XSDSchema initReusableSchema( final String rootName ) {
if (reusableSchema != null) {
// reusableSchema.setIncrementalUpdate(false);
return reusableSchema;
}
// Create the Schema
reusableSchema = XSDFactory.eINSTANCE.createXSDSchema();
currentResource.getContents().add(reusableSchema);
Map map = reusableSchema.getQNamePrefixToNamespaceMap();
String schemaForSchemaPrefixText = "xs"; //$NON-NLS-1$
map.put(schemaForSchemaPrefixText, XSDConstants.SCHEMA_FOR_SCHEMA_URI_2001);
reusableSchema.setSchemaForSchemaQNamePrefix(schemaForSchemaPrefixText);
this.includedSchemas.clear();
this.addedNamespaces.clear();
this.addedNamespaces.add(XSDConstants.SCHEMA_FOR_SCHEMA_URI_2001);
final String ns = MM_URI + rootName;
reusableSchema.setTargetNamespace(ns);
map.put(null, ns);
// reusableSchema.setIncrementalUpdate(false);
return reusableSchema;
}
/*
* Update the XSD Imports if necessary
*/
private void addImportForType( final XSDSchema target,
final XSDTypeDefinition type ) {
if (target == null || type == null || type.getSchema() == null) {
return;
}
final String ns = type.getTargetNamespace();
try {
if (addedNamespaces.contains(ns) || includedSchemas.contains(type.getSchema())) {
return;
}
if (ns == null) {
final XSDInclude xsdInclude = XSDFactory.eINSTANCE.createXSDInclude();
xsdInclude.setSchemaLocation(type.eResource().getURI().toFileString());
target.getContents().add(0, xsdInclude);
includedSchemas.add(type.getSchema());
} else {
final XSDImport xsdImport = XSDFactory.eINSTANCE.createXSDImport();
xsdImport.setNamespace(ns);
xsdImport.setSchemaLocation(ns);
target.getContents().add(0, xsdImport);
addedNamespaces.add(ns);
}
} catch (Exception err) {
final String msg = XsdPlugin.Util.getString("XsdSchemaBuilderImpl.importErr", ns); //$NON-NLS-1$
addStatus(IStatus.ERROR, msg, err);
}
}
/*
* All roots must be Tables or Procedure Results
*/
private void validateRoots( final Collection roots ) {
if (roots == null || roots.isEmpty()) {
return;
}
final Iterator rootIT = roots.iterator();
while (rootIT.hasNext()) {
final Object next = rootIT.next();
if (!(next instanceof View) && !(next instanceof BaseTable) && !(next instanceof ProcedureResult) && !(next instanceof Procedure)) {
final String invalidRoot = XsdPlugin.Util.getString("XsdSchemaBuilderImpl.invalidRoot", next.getClass().getName()); //$NON-NLS-1$
throw new TeiidDesignerRuntimeException(invalidRoot);
}
}
}
/**
* Return a map of Output Global Element to Input Global Element. The map is keyed by the Output Global Element and the values
* and keys are all XSDElementDeclarations.
*
* @return
* @since 4.3
*/
public HashMap getOutputToInputMappings() {
if (outputToInputMap == null) {
outputToInputMap = new HashMap();
}
return outputToInputMap;
}
}