/* * 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.vdb.dynamic; import java.io.File; import java.io.FileInputStream; import java.io.FileWriter; import java.io.InputStream; import java.io.Writer; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Set; import javax.xml.bind.Marshaller; import javax.xml.bind.Unmarshaller; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.transform.OutputKeys; import javax.xml.transform.Transformer; import javax.xml.transform.TransformerFactory; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; import javax.xml.transform.stream.StreamSource; import javax.xml.validation.Schema; import javax.xml.validation.Validator; import org.eclipse.core.resources.IContainer; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IProject; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.NullProgressMonitor; import org.eclipse.core.runtime.Path; import org.teiid.core.designer.util.CoreArgCheck; import org.teiid.core.designer.util.OperationUtil; import org.teiid.core.designer.util.StringUtilities; import org.teiid.core.designer.util.OperationUtil.Unreliable; import org.teiid.designer.core.ModelerCore; import org.teiid.designer.core.builder.ModelBuildUtil; import org.teiid.designer.core.workspace.ModelResource; import org.teiid.designer.core.workspace.ModelUtil; import org.teiid.designer.datatools.connection.IConnectionInfoHelper; import org.teiid.designer.datatools.profiles.jbossds.IJBossDsProfileConstants; import org.teiid.designer.ddl.importer.DdlImporter; import org.teiid.designer.metamodels.core.ModelAnnotation; import org.teiid.designer.metamodels.core.ModelType; import org.teiid.designer.metamodels.relational.RelationalPackage; import org.teiid.designer.roles.DataRole; import org.teiid.designer.roles.Permission; import org.teiid.designer.vdb.BasicVdb; import org.teiid.designer.vdb.TranslatorOverride; import org.teiid.designer.vdb.Vdb; import org.teiid.designer.vdb.VdbEntry; import org.teiid.designer.vdb.VdbFileEntry; import org.teiid.designer.vdb.VdbImportVdbEntry; import org.teiid.designer.vdb.VdbModelEntry; import org.teiid.designer.vdb.VdbPlugin; import org.teiid.designer.vdb.VdbSchemaEntry; import org.teiid.designer.vdb.VdbSource; import org.teiid.designer.vdb.VdbSourceInfo; import org.teiid.designer.vdb.VdbUtil; import org.teiid.designer.vdb.XmiVdb; import org.teiid.designer.vdb.dynamic.DynamicModel.Type; import org.teiid.designer.vdb.manifest.ConditionElement; import org.teiid.designer.vdb.manifest.DataRoleElement; import org.teiid.designer.vdb.manifest.ImportVdbElement; import org.teiid.designer.vdb.manifest.MaskElement; import org.teiid.designer.vdb.manifest.MetadataElement; import org.teiid.designer.vdb.manifest.ModelElement; import org.teiid.designer.vdb.manifest.PermissionElement; import org.teiid.designer.vdb.manifest.PropertyElement; import org.teiid.designer.vdb.manifest.SourceElement; import org.teiid.designer.vdb.manifest.TranslatorElement; import org.teiid.designer.vdb.manifest.VdbElement; import org.w3c.dom.Document; /** * Dynamic VDB needs to manage a *-vdb.xml file * This represents the manifest with embedded model <metadata/> element * * @author blafond * */ public class DynamicVdb extends BasicVdb { private Map<String, DynamicModel> models; /** * Default constructor */ public DynamicVdb() { super(); } /** * Constructor for Eclipse-based use-cases where an IResource/IFile is available * * @param file * @throws Exception */ public DynamicVdb(IFile file) throws Exception { super(file); } private Map<String, DynamicModel> models() { if (models == null) models = new HashMap<String, DynamicModel>(); return models; } @Override public void read(final IFile file) throws Exception { CoreArgCheck.isNotNull(file); setSourceFile(file); if(! file.exists() ) { return; } final File dynVdbFile = file.getLocation().toFile(); if (dynVdbFile.length() == 0) return; // file is empty so don't bother reading InputStream xml = null; try { xml = new FileInputStream(dynVdbFile); validate(xml); } finally { if (xml != null) xml.close(); } OperationUtil.perform(new Unreliable() { InputStream fileStream = null; @Override public void doIfFails() { // Nothing to do } @Override public void finallyDo() throws Exception { if (fileStream != null) fileStream.close(); } @Override public void tryToDo() throws Exception { try { fileStream = new FileInputStream(dynVdbFile); DynamicVdb vdb = DynamicVdb.this; // Initialize using manifest final Unmarshaller unmarshaller = vdb.getJaxbContext().createUnmarshaller(); unmarshaller.setSchema(vdb.getManifestSchema()); final VdbElement manifest = (VdbElement)unmarshaller.unmarshal(fileStream); CommentReader reader = new CommentReader(manifest); reader.read(dynVdbFile); vdb.setDescription(manifest.getDescription()); vdb.setVersion(manifest.getVersion()); vdb.setName(manifest.getName()); vdb.addComments(manifest.getComments()); // VDB properties for (final PropertyElement property : manifest.getProperties()) { final String name = property.getName(); final String value = property.getValue(); vdb.addPropertyComments(name, property.getComments()); if (Xml.PREVIEW.equals(name)) { vdb.setPreview(Boolean.parseBoolean(value)); // The stored timeout is in milliseconds. We are converting to seconds for display in Designer } else if (Xml.QUERY_TIMEOUT.equals(name)) { int timeoutMillis = Integer.parseInt(value); if (timeoutMillis > 0) { vdb.setQueryTimeout(timeoutMillis / 1000); } } else if (Xml.ALLOWED_LANGUAGES.equals(name)) { /* * EXAMPLE XML FRAGMENT * multiple properties allowed with SAME KEY different values * Need to discover and treat these differently <property name="allowed-languages" value="javascript, perl, php"/> */ vdb.getAllowedLanguages().addAllowedLanguage(value); } else if (Xml.SECURITY_DOMAIN.equals(name)) { vdb.setSecurityDomain(value); } else if (Xml.GSS_PATTERN.equals(name)) { vdb.setGssPattern(value); } else if (Xml.PASSWORD_PATTERN.equals(name)) { vdb.setPasswordPattern(value); } else if (Xml.AUTHENTICATION_TYPE.equals(name)) { vdb.setAuthenticationType(value); } else if (Xml.AUTO_GENERATE_REST_WAR.equals(name)) { vdb.setAutoGenerateRESTWar(Boolean.parseBoolean(value)); } else { vdb.setProperty(name, value); } } for (final ModelElement element : manifest.getModels()) { DynamicModel model = new DynamicModel(); model.setName(element.getName()); model.setVisible(element.isVisible()); model.setDescription(element.getDescription()); model.addComments(element.getComments()); for (final PropertyElement property : element.getProperties()) { final String name = property.getName(); final String value = property.getValue(); model.setProperty(name, value); model.addPropertyComments(name, property.getComments()); } if (element.getMetadata() != null && element.getMetadata().size() > 0) { MetadataElement metadataElement = element.getMetadata().get(0); String schemaText = metadataElement.getSchemaText(); String metadataType = element.getMetadata().get(0).getType(); Metadata metadata = new Metadata(); metadata.setSchemaText(schemaText); metadata.setType(metadataType); metadata.addComments(metadataElement.getComments()); model.setMetadata(metadata); } model.setModelType(element.getType()); if (element.getSources() != null && !element.getSources().isEmpty()) { for (final SourceElement sourceElement : element.getSources()) { VdbSource modelSource = new VdbSource( vdb, sourceElement.getName(), sourceElement.getJndiName() == null ? EMPTY_STRING : sourceElement.getJndiName(), sourceElement.getTranslatorName() == null ? EMPTY_STRING : sourceElement.getTranslatorName()); modelSource.addComments(sourceElement.getComments()); model.addSource(modelSource); } } vdb.addDynamicModel(model); } // Vdb Import entries for (final ImportVdbElement element : manifest.getImportVdbEntries()) { VdbImportVdbEntry vdbImport = new VdbImportVdbEntry(vdb, element.getName()); vdbImport.setImportDataPolicies(false); vdbImport.setVersion(vdb.getVersion()); vdbImport.addComments(element.getComments()); vdb.addImport(vdbImport); } // load translator overrides for (final TranslatorElement translatorElement : manifest.getTranslators()) { TranslatorOverride translator = new TranslatorOverride(vdb, translatorElement); translator.addComments(translatorElement.getComments()); for (final PropertyElement property : translatorElement.getProperties()) { final String name = property.getName(); final String value = property.getValue(); translator.setProperty(name, value); translator.addPropertyComments(name, property.getComments()); } vdb.addTranslator(translator); } for (final DataRoleElement element : manifest.getDataPolicies()) { DataRole role = new DataRole(element.getName()); role.setVdb(vdb); role.setAllowCreateTempTables(element.allowCreateTempTables()); role.setAnyAuthenticated(element.isAnyAuthenticated()); role.setGrantAll(element.doGrantAll()); role.setDescription(element.getDescription()); role.addComments(element.getComments()); { // Handle Permissions for (PermissionElement pe : element.getPermissions()) { boolean allow = false; if (pe != null) { allow = pe.isAllowLanguage(); } Permission permission = new Permission(pe.getResourceName(), pe.isCreate(), pe.isRead(), pe.isUpdate(), pe.isDelete(), pe.isExecute(), pe.isAlter()); permission.addComments(pe.getComments()); ConditionElement condition = pe.getCondition(); if (condition != null) { permission.setCondition(condition.getSql()); Boolean constraint = condition.getConstraint(); permission.setConstraint(constraint == null ? false : constraint); permission.setConditionComments(condition.getComments()); } MaskElement mask = pe.getMask(); if (mask != null) { if (mask.getOrder() != null) { permission.setOrder(Integer.valueOf(mask.getOrder())); } permission.setMask(mask.getSql()); permission.setMaskComments(mask.getComments()); } if (allow) { permission.setAllowLanguage(true); } role.addPermission(permission); } } vdb.addDataRole(role); for (String mappedRoleName : element.getMappedRoleNames()) { role.addRoleName(mappedRoleName); } } } finally { if (fileStream != null) fileStream.close(); } } }); } /** * @param xml * @return true if vdb file is valid * @throws Exception */ private boolean validate(InputStream xml) throws Exception { if (xml == null) return false; Schema schema = VdbUtil.getManifestSchema(); Validator validator = schema.newValidator(); validator.validate(new StreamSource(xml)); return true; } private void marshallComments(VdbElement vdbElement, Document document) { CommentWriter writer = new CommentWriter(document); writer.visit(vdbElement); } /** * Export vdb to destination writer. If null then use source file} * @param destination * @throws Exception */ public void write(Writer destination) throws Exception { if( destination == null ) { File destFile = getSourceFile().getFullPath().toFile(); destination = new FileWriter(destFile); } VdbElement vdbElement = new VdbElement(this); try { final Marshaller marshaller = getJaxbContext().createMarshaller(); marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE); marshaller.setSchema(getManifestSchema()); // // To get around the lack of CDATA support in jaxb, first marshall // to a DOM then use an XSL transformer to include the CDATA pragmas // DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory.newInstance(); Document document = docBuilderFactory.newDocumentBuilder().newDocument(); // Marshall the feed object into the empty document. marshaller.marshal(vdbElement, document); marshallComments(vdbElement, document); // Transform the DOM to the output stream TransformerFactory transformerFactory = TransformerFactory.newInstance(); Transformer transformer = transformerFactory.newTransformer(); transformer.setOutputProperty(OutputKeys.INDENT, "yes"); //$NON-NLS-1$ transformer.setOutputProperty(OutputKeys.CDATA_SECTION_ELEMENTS, "metadata"); //$NON-NLS-1$ transformer.transform(new DOMSource(document), new StreamResult(destination)); } finally { destination.close(); } } /** (non-Javadoc) * @see org.teiid.designer.vdb.Vdb#getDynamicModels() */ @Override public Collection<DynamicModel> getDynamicModels() { return Collections.unmodifiableCollection(models().values()); } /** (non-Javadoc) * @see org.teiid.designer.vdb.Vdb#addDynamicModel(org.teiid.designer.vdb.dynamic.DynamicModel) */ @Override public void addDynamicModel(DynamicModel model) { DynamicModel existing = models().put(model.getName(), model); model.setVdb(this); setChanged(existing != null); } @Override public boolean isSynchronized() { return isChanged(); } @Override public void synchronize() throws Exception { // TODO // Not sure how to synchronize atm } @Override public void save() throws Exception { write(null); } /** (non-Javadoc) * @see org.teiid.designer.vdb.Vdb#removeDynamicModel(java.lang.String) */ @Override public void removeDynamicModel(String modelToRemove) { DynamicModel removed = models().remove(modelToRemove); setChanged(removed != null); } @Override public <T extends VdbEntry> T addEntry(IPath name) throws Exception { throw new UnsupportedOperationException(); } @Override public void synchronizeUdfJars(Set<VdbFileEntry> newJarEntries) { throw new UnsupportedOperationException(); } @Override public Set<VdbSchemaEntry> getSchemaEntries() { throw new UnsupportedOperationException(); } @Override public Set<VdbFileEntry> getUdfJarEntries() { throw new UnsupportedOperationException(); } @Override public Set<String> getUdfJarNames() { throw new UnsupportedOperationException(); } @Override public Set<VdbFileEntry> getUserFileEntries() { throw new UnsupportedOperationException(); } @Override public Collection<VdbEntry> getEntries() { throw new UnsupportedOperationException(); } @Override public Set<VdbModelEntry> getModelEntries() { throw new UnsupportedOperationException(); } @Override public Collection<File> getModelFiles() { throw new UnsupportedOperationException(); } @Override public Collection<File> getSchemaFiles() { throw new UnsupportedOperationException(); } @Override public boolean removeEntry(VdbEntry entry) { throw new UnsupportedOperationException(); } @Override public DynamicVdb clone() { DynamicVdb clone = new DynamicVdb(); populateVdb(clone); for (DynamicModel dynModel : getDynamicModels()) { DynamicModel cloneModel = dynModel.clone(); clone.addDynamicModel(cloneModel); } return clone; } @Override public DynamicVdb dynVdbConvert(IFile destination, Properties properties) throws Exception { CoreArgCheck.isNotNull(destination); File newVdbFile = destination.getLocation().toFile(); FileWriter writer = null; try { writer = new FileWriter(newVdbFile); this.write(writer); DynamicVdb vdb = new DynamicVdb(destination); return vdb; } finally { if (writer != null) writer.close(); } } @Override public XmiVdb xmiVdbConvert(IFile destination, Properties options) throws Exception { NullProgressMonitor monitor = new NullProgressMonitor(); try { // // Broadcast that a conversion is underway // VdbPlugin.singleton().setConversionInProgress(true); XmiVdb xmiVdb = new XmiVdb(destination); // // Populate the new vdb with the basic specification // populateVdb(xmiVdb); // // No external files coming from dynamic vdb so // no file entries to populate // List<DynamicModel> dynamicModels = new ArrayList<DynamicModel>(getDynamicModels()); if (! dynamicModels.isEmpty()) Collections.sort(dynamicModels, new DynamicModelComparator()); // NOTE that objects in one model may have references to objects in another model // Example is a Materialized Table Reference // So we'll need to: // - capture these references (simple model name and object name?) in each DdlImporter // - keep a map of all DdlImporters and their resulting ModelResource // - AFTER all models are created and saved // - Get the deferred reference objects for each importer // - If they exist, then run a utility to find the reference object and the target View and set the EMF reference Map<ModelResource, DdlImporter> importerModelMap = new HashMap<ModelResource, DdlImporter>(); for (DynamicModel dynModel : dynamicModels) { IFile sourceFile = this.getSourceFile(); IContainer parent = sourceFile.getParent(); String fileName = dynModel.getName() + DOT_XMI; ModelResource modelResource = null; // // Create the empty model // IFile modelFile = parent.getFile(new Path(fileName)); if (modelFile.exists()) modelFile.delete(true, monitor); modelResource = ModelerCore.create(modelFile); if (modelResource == null) throw new Exception("Failed to create model resource"); //$NON-NLS-1$ // // Apply the model annotation // ModelAnnotation annotation = modelResource.getModelAnnotation(); annotation.setPrimaryMetamodelUri(RelationalPackage.eNS_URI); annotation.setModelType(ModelType.get(dynModel.getModelType().getType())); // // Inject the source properties into the model // VdbSource[] sources = dynModel.getSources(); if (sources != null) { for (VdbSource source : sources) { String translatorProperty = IConnectionInfoHelper.TRANSLATOR_NAMESPACE + IConnectionInfoHelper.TRANSLATOR_NAME_KEY; ModelUtil.setModelAnnotationPropertyValue(modelResource, translatorProperty, source.getTranslatorName()); String jndiProperty = IConnectionInfoHelper.CONNECTION_NAMESPACE + IJBossDsProfileConstants.JNDI_PROP_ID; ModelUtil.setModelAnnotationPropertyValue(modelResource, jndiProperty, source.getJndiName()); } } // // Save the resource // modelResource.save(monitor, false); // // Index the resource // ModelBuildUtil.indexResources(monitor, Collections.singleton(modelResource.getCorrespondingResource())); // // If we have a some DDL then importer it into the model resource // Metadata metadata = dynModel.getMetadata(); if (metadata != null) { IProject project = parent.getProject(); DdlImporter importer = new DdlImporter(new IProject[] {project}); // Set the destination the model file importer.setModelFolder(parent); importer.setModelName(fileName); // Set some options importer.setOptToCreateModelEntitiesForUnsupportedDdl(false); String ddlAsDescriptionOption = options.getProperty(Vdb.SET_DDL_AS_DESCRIPTION, Boolean.FALSE.toString()); importer.setOptToSetModelEntityDescription(Boolean.parseBoolean(ddlAsDescriptionOption)); // Set the model type Type dynModelType = dynModel.getModelType(); String modelType = dynModelType.toString(); importer.setModelType(ModelType.get(modelType)); importer.setGenerateDefaultSQL(Type.VIRTUAL.equals(dynModelType)); // Not actually used by the importer but better to // just populate it in case used in the future. importer.setDdlFileName(getSourceFile().getLocation().toOSString()); // Limit the importer to Teiid-only syntax importer.setSpecifiedParser("TEIID"); //$NON-NLS-1$ // // Import the ddl // importer.importDdl(metadata.getSchemaText(), monitor, 1, new Properties()); if (importer.hasParseError()) { StringBuffer buffer = new StringBuffer(); buffer.append("Error Message:").append(TAB); //$NON-NLS-1$ buffer.append(importer.getParseErrorMessage().trim()).append(NEW_LINE); buffer.append(TAB).append(TAB).append(SPACE).append(SPACE).append("Error Line Number:").append(TAB); //$NON-NLS-1$ buffer.append(importer.getParseErrorLineNumber()).append(NEW_LINE); buffer.append(TAB).append(TAB).append(SPACE).append(SPACE).append("Error Column Number:").append(TAB); //$NON-NLS-1$ buffer.append(importer.getParseErrorColNumber()).append(NEW_LINE); buffer.append(TAB).append(TAB).append(SPACE).append(SPACE).append("Error Index:").append(TAB); //$NON-NLS-1$ buffer.append(importer.getParseErrorIndex()).append(NEW_LINE); throw new Exception(buffer.toString()); } if (!importer.noDdlImported()) { importer.save(monitor, 1); } modelResource = importer.model(); // Add model description String desc = dynModel.getDescription(); if( !StringUtilities.isEmpty(desc) ) { modelResource.getModelAnnotation().setDescription(desc); modelResource.save(monitor, false); } importerModelMap.put(modelResource, importer); } VdbModelEntry modelEntry = xmiVdb.addEntry(modelFile.getFullPath()); // // Set any model properties // for (Map.Entry<Object, Object> prop : dynModel.getProperties().entrySet()) { modelEntry.setProperty(prop.getKey().toString(), prop.getValue().toString()); } String desc = dynModel.getDescription(); if( !StringUtilities.isEmpty(desc) ) { modelEntry.setDescription(desc); } modelEntry.setVisible(dynModel.isVisible()); VdbSourceInfo sourceInfo = modelEntry.getSourceInfo(); sourceInfo.setIsMultiSource(dynModel.isMultiSource()); sourceInfo.setAddColumn(dynModel.doAddColumn()); sourceInfo.setColumnAlias(dynModel.getColumnAlias()); // // Check to ensure the sources are added to the VdbModelEntry // for (VdbSource source : dynModel.getSources()) { sourceInfo.add(source.getName(), source.getJndiName(), source.getTranslatorName()); } // copy any model properties into archive VDB for (Map.Entry<Object, Object> entry : dynModel.getProperties().entrySet()) { modelEntry.setProperty(entry.getKey().toString(), entry.getValue().toString()); } } // Now process any materialized table references for( DdlImporter importer : importerModelMap.values() ) { importer.setMaterializedTableReferences(importerModelMap.keySet()); } return xmiVdb; } finally { VdbPlugin.singleton().setConversionInProgress(false); } } }