/* * 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.runtime.ui.vdb; import static org.teiid.designer.runtime.ui.DqpUiConstants.UTIL; import java.util.Collection; import org.eclipse.core.resources.IFile; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.NullProgressMonitor; import org.eclipse.jface.dialogs.MessageDialog; import org.eclipse.swt.widgets.Shell; import org.teiid.core.designer.util.CoreArgCheck; import org.teiid.core.designer.util.CoreStringUtil; import org.teiid.core.designer.util.FileUtils; import org.teiid.core.designer.util.I18nUtil; import org.teiid.core.designer.util.StringUtilities; import org.teiid.designer.core.workspace.ModelResource; import org.teiid.designer.core.workspace.ModelUtil; import org.teiid.designer.datatools.connection.ConnectionInfoProviderFactory; import org.teiid.designer.metamodels.core.ModelType; import org.teiid.designer.runtime.DqpPlugin; import org.teiid.designer.runtime.TeiidDataSourceFactory; import org.teiid.designer.runtime.spi.FailedTeiidDataSource; import org.teiid.designer.runtime.spi.ITeiidDataSource; import org.teiid.designer.runtime.spi.ITeiidServer; import org.teiid.designer.vdb.TranslatorOverride; import org.teiid.designer.vdb.Vdb; import org.teiid.designer.vdb.VdbEntry; import org.teiid.designer.vdb.VdbModelEntry; /** * * * @since 8.0 */ public class VdbDeployer { static final String PREFIX = I18nUtil.getPropertyPrefix(VdbDeployer.class); private static final String JNDI_PROPERTY_KEY = "jndi-name"; //$NON-NLS-1$ private static final String JNDI_CONTEXT = "java:/"; //$NON-NLS-1$ /** * A VDB deployment status. */ public enum DeployStatus { /** * Indicates Teiid failed to create a DS. */ CREATE_DATA_SOURCE_FAILED, /** * Indicates the VDB was deployed to Teiid. */ DEPLOYED_VDB, /** * Indicates the VDB deployment was canceled by the user. */ DEPLOY_VDB_CANCELED, /** * Indicates Teiid failed to deploy the VDB. */ DEPLOY_VDB_FAILED, /** * Indicates the an unexpected exception was caught. */ EXCEPTION, /** * Indicates the user conceled the progress monitor. */ MONITOR_CANCELLED, /** * Indicates there are missing translator names or translator names that are not on the current Teiid Instance. */ TRANSLATOR_PROBLEM, /** * Indicates one or more sources has missing connection info. */ SOURCE_CONNECTION_INFO_PROBLEM; /** * @return <code>true</code> if status indicates the VDB was successfully depoloyed */ public boolean isDeployed() { return (this == DEPLOYED_VDB); } /** * @return <code>true</code> if status is an error */ public boolean isError() { return ((this == CREATE_DATA_SOURCE_FAILED) || (this == DEPLOY_VDB_FAILED) || (this == EXCEPTION) || (this == TRANSLATOR_PROBLEM) || this == SOURCE_CONNECTION_INFO_PROBLEM); } } private final ITeiidServer teiidServer; // the current Teiid Instance private final boolean autoCreateDsOnServer; // indicates if data source should be auto-created on server without asking user private Exception error; // non-null if error caught while deploying private final Shell shell; private DeployStatus status; // non-null after deploying private final Vdb vdb; // the workspace VDB /** * @param shell the shell to use for any UI (may not be <code>null</code>) * @param vdbBeingDeployed the VDB being deployed (may not be <code>null</code>) * @param defaultServer the server (may not be <code>null</code>) * @param shouldAutoCreateDataSourceOnServer indicates if data sources that match the default name should be auto-created if * they don't exist on Teiid Instance */ public VdbDeployer( Shell shell, Vdb vdbBeingDeployed, ITeiidServer defaultServer, boolean shouldAutoCreateDataSourceOnServer ) { CoreArgCheck.isNotNull(vdbBeingDeployed, "Vdb is null"); //$NON-NLS-1$ CoreArgCheck.isNotNull(defaultServer, "Default Teiid Instance is null"); //$NON-NLS-1$ CoreArgCheck.isNotNull(shell, "Shell is null"); //$NON-NLS-1$ this.teiidServer = defaultServer; this.shell = shell; this.vdb = vdbBeingDeployed; this.autoCreateDsOnServer = shouldAutoCreateDataSourceOnServer; } /** * @return the error caught during deploying the VDB or <code>null</code> */ public Exception getException() { return this.error; } /** * @return the deploy status (<code>null</code> if called before deploy is called) */ public DeployStatus getStatus() { return this.status; } /** * Deploy the selected VDB to the default Teiid Instance. * * @param monitor the progress monitor (can be <code>null</code>) * @return if fails then returns the model name otherwise null */ public String deploy( IProgressMonitor monitor ) { if (monitor == null) { monitor = new NullProgressMonitor(); } String failedModelName = null; try { // since we need to deploy lets first check to make sure all the data sources exist on server if (!this.vdb.getModelEntries().isEmpty()) { monitor.beginTask(UTIL.getString(PREFIX + "deployMainTask", getVdbName()), IProgressMonitor.UNKNOWN); //$NON-NLS-1$ boolean hasJndiProblems = false; boolean foundFirstOne = false; // determines if create DS dialog is shown boolean createOnServer = false; // user's choice on if they want DSs auto-created on server for (VdbEntry modelEntry : this.vdb.getModelEntries()) { // see if user canceled monitor if (monitor.isCanceled()) { this.status = DeployStatus.MONITOR_CANCELLED; return null; // don't do anything else } String modelName = modelEntry.getPath().toFile().getName(); monitor.subTask(UTIL.getString(PREFIX + "checkModelTypeTask", modelName)); //$NON-NLS-1$ boolean autoCreate = false; // based on DS name and preference value String modelType = ((VdbModelEntry)modelEntry).getType(); boolean sourceModel = (modelType.equalsIgnoreCase(ModelType.PHYSICAL_LITERAL.getName())); // only source models have a data source and translator if (!sourceModel) { continue; // go on to next model (only care about source models) } // check translator monitor.subTask(UTIL.getString(PREFIX + "checkModelTranslatorTask", modelName)); //$NON-NLS-1$ if (!hasValidTranslator((VdbModelEntry)modelEntry)) { this.status = DeployStatus.TRANSLATOR_PROBLEM; break; // translator problems are fatal (deployment will fail) } IFile modelFile = modelEntry.findFileInWorkspace(); // Check that Source Model has Connection Info if (this.vdb.isPreview() && !hasConnectionInfo(modelFile)) { this.status = DeployStatus.SOURCE_CONNECTION_INFO_PROBLEM; break; } // check DS monitor.subTask(UTIL.getString(PREFIX + "checkModelDataSourceTask", modelName)); //$NON-NLS-1$ String sourceName = ((VdbModelEntry)modelEntry).getSourceInfo().getSource(0).getName(); String jndiName = ((VdbModelEntry)modelEntry).getSourceInfo().getSource(0).getJndiName(); // DS with matching jndi not found on server if (!StringUtilities.isEmpty(jndiName) && !dataSourceWithJndiExists(jndiName)) { // auto-create if jndiName not different than sourceName String jndiNameWithoutContext = jndiName; // incoming jndiName may not have context, so try that also since server can match it if(jndiName.startsWith(JNDI_CONTEXT)) { jndiNameWithoutContext = jndiName.substring(JNDI_CONTEXT.length()); } if (sourceName.equals(jndiNameWithoutContext) && this.autoCreateDsOnServer) { autoCreate = true; // create without asking user } if (!autoCreate && !foundFirstOne) { // if this is the first DS that isn't found on server ask user if DS should be // auto-created on the server (and do this for any others found to not be on // server) foundFirstOne = true; // if user OK's dialog they want DSs auto-created final boolean[] result = new boolean[1]; // make sure in UI thread this.shell.getDisplay().syncExec(new Runnable() { /** * {@inheritDoc} * * @see java.lang.Runnable#run() */ @Override public void run() { if (MessageDialog.openQuestion(getShell(), UTIL.getString(PREFIX + "createDataSourcesConfirmation.title"), //$NON-NLS-1$ UTIL.getString(PREFIX + "createDataSourcesConfirmation.message", //$NON-NLS-1$ getVdbName()))) { result[0] = true; } } }); if (result[0]) { createOnServer = true; } } // TODO must also be able to create DS even if model not found in workspace // if model found in workspace create data source on server if ((autoCreate || createOnServer) && (modelFile != null)) { monitor.subTask(UTIL.getString(PREFIX + "createModelDataSourceTask", modelName)); //$NON-NLS-1$ TeiidDataSourceFactory factory = new TeiidDataSourceFactory(); ITeiidDataSource ds = factory.createDataSource(teiidServer, modelFile, jndiNameWithoutContext, false); if( ds == null ) { this.status = DeployStatus.CREATE_DATA_SOURCE_FAILED; break; // don't try again to create a DS } else if( ds instanceof FailedTeiidDataSource ) { this.status = DeployStatus.CREATE_DATA_SOURCE_FAILED; failedModelName = FileUtils.getNameWithoutExtension(modelFile); break; // don't try again to create a DS } } else if (!hasJndiProblems) { // DS doesn't exist and won't be auto-created, or model not in workspace hasJndiProblems = true; } } } // ask user if they still want to deploy even though there are JNDI problems if (hasJndiProblems) { // make sure in UI thread this.shell.getDisplay().syncExec(new Runnable() { /** * {@inheritDoc} * * @see java.lang.Runnable#run() */ @Override public void run() { if (MessageDialog.openQuestion(getShell(), UTIL.getString(PREFIX + "deployWithErrorsConfirmation.title"), //$NON-NLS-1$ UTIL.getString(PREFIX + "deployWithErrorsConfirmation.message"))) { //$NON-NLS-1$ setStatus(null); // user wants to deploy regardless of errors } else { setStatus(DeployStatus.DEPLOY_VDB_CANCELED); } } }); } } if (this.status == null) { monitor.subTask(UTIL.getString(PREFIX + "deployVdbTask", getVdbName())); //$NON-NLS-1$ // VDB name can contain an integer value // VDB can also have a version in it's manifest (vdb.xml) // // EXAMPLE: Customers.2.vdb // String version = vdb.getVersion(); // Manifest version String versionInName = getVdbVersion(getVdbName()); // version in name if (versionInName != null) { // If version in name, then use it (i.e. ignore manifest version) version = versionInName; } teiidServer.deployVdb(vdb.getSourceFile(), version); // VDB name may have a version in it, so need to strip off any extension this.status = (teiidServer.hasVdb(getVdbName()) ? DeployStatus.DEPLOYED_VDB : DeployStatus.DEPLOY_VDB_FAILED); } } catch (Exception e) { this.status = DeployStatus.EXCEPTION; this.error = e; } finally { monitor.done(); } if( failedModelName != null ) { return failedModelName; } return null; } private String getVdbVersion(String originalVdbName) throws Exception { String vdbName = originalVdbName; String vdbVersionStr = null; int firstIndex = vdbName.indexOf('.'); int lastIndex = vdbName.lastIndexOf('.'); if (firstIndex != -1) { if (firstIndex != lastIndex) { // TODO: throw new Exception(UTIL.getString(PREFIX + "vdbNameContainsTooManyDotsErrorMessage", originalVdbName)); //$NON-NLS-1$"VBD Version contains more than one '.'"); //Messages.getString(Messages.ExecutionAdmin.invalidVdbName, originalVdbName)); } vdbVersionStr = vdbName.substring(firstIndex+1); return vdbVersionStr; } return null; } /* * Check the server sources to see if a datasource with the provided JNDI name exists. * @param jndiName the jndi name to check * @return 'true' if a source with a matching name is found, 'false' if not. */ private boolean dataSourceWithJndiExists(String jndiName) { if(jndiName==null || jndiName.isEmpty()) return false; boolean hasSourceWithJndi = false; Collection<ITeiidDataSource> serverSources = null; try { serverSources = teiidServer.getDataSources(); } catch (Exception ex) { DqpPlugin.Util.log(ex); } if(serverSources!=null && !serverSources.isEmpty()) { for(ITeiidDataSource serverSource : serverSources) { String serverJndiName = serverSource.getPropertyValue(JNDI_PROPERTY_KEY); if(!CoreStringUtil.isEmpty(serverJndiName)) { // Straight check if(serverJndiName.equalsIgnoreCase(jndiName)) { hasSourceWithJndi = true; break; } // incoming jndiName may not have context, so try that also since server can match it if(!jndiName.startsWith(JNDI_CONTEXT)) { String jndiNameWithContext = JNDI_CONTEXT+jndiName; if(serverJndiName.equalsIgnoreCase(jndiNameWithContext)) { hasSourceWithJndi = true; break; } } } } } return hasSourceWithJndi; } /* * Method to test whether the supplied source model has connection info * @param modelFile the supplied model file * @return 'true' if the modelResource has connection info, 'false' if not. */ private boolean hasConnectionInfo(IFile modelFile) { boolean hasConnInfo = true; // Try to get the ConnectionInfoProvider. Exception is thrown if the provider cannot be obtained (no connection info) try { ModelResource modelResource = ModelUtil.getModelResource(modelFile, true); ConnectionInfoProviderFactory manager = new ConnectionInfoProviderFactory(); manager.getProvider(modelResource); } catch (Exception ex) { hasConnInfo = false; } return hasConnInfo; } /** * @return the name of the VDB being deployed (never <code>null</code>) */ public String getVdbName() { return this.vdb.getName(); } /** * @return the shell the deployer is using (never <code>null</code>) */ Shell getShell() { return this.shell; } /** * @param modelEntry the model entry whose translator entry is being validated * @return <code>true</code> if model entry has a valid translator * @throws Exception */ private boolean hasValidTranslator( VdbModelEntry modelEntry ) throws Exception { // assertion: must be a source model String translatorName = modelEntry.getSourceInfo().getSource(0).getTranslatorName(); // must have a translator if (StringUtilities.isEmpty(translatorName)) { return false; } // make sure server has translator with that name boolean isValid = (teiidServer.getTranslator(translatorName) != null); // Check for overridden translator names if( !isValid && !this.vdb.getTranslators().isEmpty()) { for( TranslatorOverride override : this.vdb.getTranslators() ) { if( override.getName().equalsIgnoreCase(translatorName) ) { isValid = (teiidServer.getTranslator(override.getType()) != null); break; } } } return isValid; } /** * @param status the new status (can be <code>null</code>) */ void setStatus( DeployStatus status ) { this.status = status; } }