/* * JBoss, Home of Professional Open Source. * See the COPYRIGHT.txt file distributed with this work for information * regarding copyright ownership. Some portions may be licensed * to Red Hat, Inc. under one or more contributor license agreements. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ package org.teiid.jboss; import java.io.File; import java.io.IOException; import java.text.SimpleDateFormat; import java.util.Date; import java.util.HashSet; import java.util.IdentityHashMap; import java.util.List; import java.util.Map; import java.util.concurrent.Executor; import java.util.concurrent.atomic.AtomicInteger; import javax.xml.stream.XMLStreamException; import org.jboss.as.controller.OperationFailedException; import org.jboss.msc.service.LifecycleContext; import org.jboss.msc.service.Service; import org.jboss.msc.service.ServiceBuilder; import org.jboss.msc.service.ServiceContainer; import org.jboss.msc.service.ServiceController; import org.jboss.msc.service.StartContext; import org.jboss.msc.service.StartException; import org.jboss.msc.service.StopContext; import org.jboss.msc.value.InjectedValue; import org.teiid.adminapi.AdminProcessingException; import org.teiid.adminapi.Model; import org.teiid.adminapi.Translator; import org.teiid.adminapi.VDB.Status; import org.teiid.adminapi.impl.ModelMetaData; import org.teiid.adminapi.impl.ModelMetaData.Message.Severity; import org.teiid.adminapi.impl.VDBMetaData; import org.teiid.adminapi.impl.VDBMetadataParser; import org.teiid.adminapi.impl.VDBTranslatorMetaData; import org.teiid.deployers.CompositeVDB; import org.teiid.deployers.ContainerLifeCycleListener; import org.teiid.deployers.RuntimeVDB; import org.teiid.deployers.TranslatorUtil; import org.teiid.deployers.UDFMetaData; import org.teiid.deployers.VDBLifeCycleListener; import org.teiid.deployers.VDBRepository; import org.teiid.deployers.VDBStatusChecker; import org.teiid.deployers.VirtualDatabaseException; import org.teiid.dqp.internal.datamgr.ConnectorManager; import org.teiid.dqp.internal.datamgr.ConnectorManagerRepository; import org.teiid.dqp.internal.datamgr.ConnectorManagerRepository.ConnectorManagerException; import org.teiid.dqp.internal.datamgr.TranslatorRepository; import org.teiid.logging.LogConstants; import org.teiid.logging.LogManager; import org.teiid.metadata.Datatype; import org.teiid.metadata.MetadataFactory; import org.teiid.metadata.MetadataRepository; import org.teiid.metadata.MetadataStore; import org.teiid.metadata.index.IndexMetadataRepository; import org.teiid.query.metadata.VDBResources; import org.teiid.runtime.AbstractVDBDeployer; import org.teiid.translator.ExecutionFactory; import org.teiid.translator.TranslatorException; import org.teiid.vdb.runtime.VDBKey; class VDBService extends AbstractVDBDeployer implements Service<RuntimeVDB> { private VDBMetaData vdb; private RuntimeVDB runtimeVDB; protected final InjectedValue<VDBRepository> vdbRepositoryInjector = new InjectedValue<VDBRepository>(); protected final InjectedValue<TranslatorRepository> translatorRepositoryInjector = new InjectedValue<TranslatorRepository>(); protected final InjectedValue<Executor> executorInjector = new InjectedValue<Executor>(); protected final InjectedValue<ObjectSerializer> serializerInjector = new InjectedValue<ObjectSerializer>(); protected final InjectedValue<VDBStatusChecker> vdbStatusCheckInjector = new InjectedValue<VDBStatusChecker>(); private VDBLifeCycleListener vdbListener; private VDBResources vdbResources; private VDBKey vdbKey; public VDBService(VDBMetaData metadata, VDBResources vdbResources, ContainerLifeCycleListener shutdownListener) { this.vdb = metadata; this.vdbKey = new VDBKey(metadata.getName(), metadata.getVersion()); this.vdbResources = vdbResources; } @Override public void start(final StartContext context) throws StartException { ConnectorManagerRepository cmr = new ConnectorManagerRepository(); TranslatorRepository repo = new TranslatorRepository(); this.vdb.addAttchment(TranslatorRepository.class, repo); // check if this is a VDB with index files, if there are then build the TransformationMetadata UDFMetaData udf = this.vdb.getAttachment(UDFMetaData.class); // add required connector managers; if they are not already there for (Translator t: this.vdb.getOverrideTranslators()) { VDBTranslatorMetaData data = (VDBTranslatorMetaData)t; String type = data.getType(); VDBTranslatorMetaData parent = getTranslatorRepository().getTranslatorMetaData(type); data.setModuleName(parent.getModuleName()); data.addAttchment(ClassLoader.class, parent.getAttachment(ClassLoader.class)); data.setParent(parent); repo.addTranslatorMetadata(data.getName(), data); } createConnectorManagers(cmr, repo, this.vdb); final ServiceBuilder<Void> vdbService = addVDBFinishedService(context); this.vdbListener = new VDBLifeCycleListener() { @Override public void added(String name, CompositeVDB cvdb) { } @Override public void beforeRemove(String name, CompositeVDB cvdb) { } @Override public void removed(String name, CompositeVDB cvdb) { } @Override public void finishedDeployment(String name, CompositeVDB cvdb) { if (!VDBService.this.vdbKey.equals(cvdb.getVDBKey())) { return; } //clear out the indexmetadatarepository as it holds state that is no longer necessary repositories.put("index", new IndexMetadataRepository()); //$NON-NLS-1$ VDBMetaData vdbInstance = cvdb.getVDB(); if (vdbInstance.getStatus().equals(Status.ACTIVE)) { vdbService.install(); } } }; getVDBRepository().addListener(this.vdbListener); MetadataStore store = new MetadataStore(); try { //check to see if there is an index file. if there is then we assume //that index is the default metadata repo MetadataRepository<?, ?> defaultRepo = null; for (String s : this.vdbResources.getEntriesPlusVisibilities().keySet()) { if (s.endsWith(VDBResources.INDEX_EXT)) { defaultRepo = super.getMetadataRepository("index"); //$NON-NLS-1$ break; } } this.assignMetadataRepositories(vdb, defaultRepo); // add transformation metadata to the repository. getVDBRepository().addVDB(this.vdb, store, vdbResources.getEntriesPlusVisibilities(), udf, cmr); } catch (VirtualDatabaseException e) { cleanup(context); throw new StartException(e); } this.vdb.removeAttachment(UDFMetaData.class); try { loadMetadata(this.vdb, cmr, store, this.vdbResources); } catch (TranslatorException e) { cleanup(context); throw new StartException(e); } this.runtimeVDB = buildRuntimeVDB(this.vdb, context.getController().getServiceContainer()); } private RuntimeVDB buildRuntimeVDB(final VDBMetaData vdbMetadata, final ServiceContainer serviceContainer) { RuntimeVDB.VDBModificationListener modificationListener = new RuntimeVDB.VDBModificationListener() { @Override public void dataRoleChanged(String policyName) throws AdminProcessingException { save(); } @Override public void connectionTypeChanged() throws AdminProcessingException { save(); } @Override public void dataSourceChanged(String modelName, String sourceName,String translatorName, String dsName) throws AdminProcessingException { save(); } @Override public void onRestart(List<String> modelNames) { ServiceController<?> switchSvc = serviceContainer.getService(TeiidServiceNames.vdbSwitchServiceName(vdbMetadata.getName(), vdbMetadata.getVersion())); if (switchSvc != null) { if (!modelNames.isEmpty()) { for (String model:modelNames) { deleteModelCache(model); } } else { for (String model:vdbMetadata.getModelMetaDatas().keySet()) { deleteModelCache(model); } } switchSvc.setMode(ServiceController.Mode.REMOVE); } } }; return new RuntimeVDB(vdbMetadata, modificationListener) { @Override protected VDBStatusChecker getVDBStatusChecker() { return VDBService.this.vdbStatusCheckInjector.getValue(); } }; } Service<Void> createVoidService() { return new Service<Void>() { @Override public Void getValue() throws IllegalStateException, IllegalArgumentException { return null; } @Override public void start(StartContext sc)throws StartException {} @Override public void stop(StopContext sc) {} }; } private ServiceBuilder<Void> addVDBFinishedService(StartContext context) { ServiceContainer serviceContainer = context.getController().getServiceContainer(); final ServiceController<?> controller = serviceContainer.getService(TeiidServiceNames.vdbFinishedServiceName(vdb.getName(), vdb.getVersion())); if (controller != null) { controller.setMode(ServiceController.Mode.REMOVE); } return serviceContainer.addService(TeiidServiceNames.vdbFinishedServiceName(vdb.getName(), vdb.getVersion()), createVoidService()); } void cleanup(LifecycleContext context) { getVDBRepository().removeVDB(this.vdb.getName(), this.vdb.getVersion()); getVDBRepository().removeListener(this.vdbListener); //getVDBRepository().removeListener(this.restEasyListener); final ServiceController<?> controller = context.getController().getServiceContainer().getService(TeiidServiceNames.vdbFinishedServiceName(vdb.getName(), vdb.getVersion())); if (controller != null) { controller.setMode(ServiceController.Mode.REMOVE); } LogManager.logInfo(LogConstants.CTX_RUNTIME, IntegrationPlugin.Util.gs(IntegrationPlugin.Event.TEIID50026, this.vdb)); } @Override public void stop(StopContext context) { cleanup(context); } @Override public RuntimeVDB getValue() throws IllegalStateException,IllegalArgumentException { return this.runtimeVDB; } private void createConnectorManagers(ConnectorManagerRepository cmr, final TranslatorRepository repo, final VDBMetaData deployment) throws StartException { final IdentityHashMap<Translator, ExecutionFactory<Object, Object>> map = new IdentityHashMap<Translator, ExecutionFactory<Object, Object>>(); try { ConnectorManagerRepository.ExecutionFactoryProvider provider = new ConnectorManagerRepository.ExecutionFactoryProvider() { @Override public ExecutionFactory<Object, Object> getExecutionFactory(String name) throws ConnectorManagerException { return TranslatorUtil.getExecutionFactory(name, repo, getTranslatorRepository(), deployment, map, new HashSet<String>()); } }; cmr.setProvider(provider); cmr.createConnectorManagers(deployment, provider); } catch (ConnectorManagerException e) { if (e.getCause() != null) { throw new StartException(IntegrationPlugin.Event.TEIID50035.name()+" "+e.getMessage(), e.getCause()); //$NON-NLS-1$ } throw new StartException(e.getMessage()); } } @Override @SuppressWarnings({"rawtypes","unchecked"}) protected void loadMetadata(final VDBMetaData vdb, final ModelMetaData model, final ConnectorManagerRepository cmr, final MetadataRepository metadataRepo, final MetadataStore vdbMetadataStore, final AtomicInteger loadCount, final VDBResources vdbResources) { String msg = IntegrationPlugin.Util.gs(IntegrationPlugin.Event.TEIID50029,vdb.getName(), vdb.getVersion(), model.getName(), SimpleDateFormat.getInstance().format(new Date())); model.setMetadataStatus(Model.MetadataStatus.LOADING); model.addRuntimeMessage(Severity.INFO, msg); LogManager.logInfo(LogConstants.CTX_RUNTIME, msg); final Runnable job = new Runnable() { @Override public void run() { boolean cached = false; Exception ex = null; TranslatorException te = null; // if this is not the first time trying to load metadata if (model.getMetadataStatus() != Model.MetadataStatus.LOADING) { model.setMetadataStatus(Model.MetadataStatus.RETRYING); } // designer based models define data types based on their built in data types, which are system vdb data types Map<String, Datatype> datatypes = vdbMetadataStore.getDatatypes(); final File cachedFile = getSerializer().buildModelFile(vdb, model.getName()); MetadataFactory factory = getSerializer().loadSafe(cachedFile, MetadataFactory.class); if (factory != null) { factory.correctDatatypes(datatypes); cached = true; LogManager.logDetail(LogConstants.CTX_RUNTIME, "Model ", model.getName(), "in VDB ", vdb.getName(), " was loaded from cached metadata"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ } else { factory = createMetadataFactory(vdb, vdbMetadataStore, model, vdbResources.getEntriesPlusVisibilities()); ExecutionFactory ef = null; Object cf = null; for (ConnectorManager cm : getConnectorManagers(model, cmr)) { if (ex != null) { LogManager.logDetail(LogConstants.CTX_RUNTIME, ex, "Failed to get metadata, trying next source."); //$NON-NLS-1$ ex = null; te = null; } try { if (cm != null) { ef = cm.getExecutionFactory(); cf = cm.getConnectionFactory(); } } catch (TranslatorException e) { LogManager.logDetail(LogConstants.CTX_RUNTIME, e, "Failed to get a connection factory for metadata load."); //$NON-NLS-1$ te = e; } ClassLoader originalCL = Thread.currentThread().getContextClassLoader(); try { LogManager.logDetail(LogConstants.CTX_RUNTIME, IntegrationPlugin.Util.gs(IntegrationPlugin.Event.TEIID50104,vdb.getName(), vdb.getVersion(), model.getName(), cm != null?cm.getTranslatorName():null, cm != null?cm.getConnectionName():null)); Thread.currentThread().setContextClassLoader(metadataRepo.getClass().getClassLoader()); metadataRepo.loadMetadata(factory, ef, cf); LogManager.logInfo(LogConstants.CTX_RUNTIME, IntegrationPlugin.Util.gs(IntegrationPlugin.Event.TEIID50030,vdb.getName(), vdb.getVersion(), model.getName(), SimpleDateFormat.getInstance().format(new Date()))); break; } catch (Exception e) { factory = createMetadataFactory(vdb, vdbMetadataStore, model, vdbResources.getEntriesPlusVisibilities()); ex = e; } finally { Thread.currentThread().setContextClassLoader(originalCL); } } } synchronized (vdb) { VDBStatusChecker marked = model.removeAttachment(VDBStatusChecker.class); if (ex == null) { if (!cached) { // cache the schema to disk cacheMetadataStore(model, factory); } metadataLoaded(vdb, model, vdbMetadataStore, loadCount, factory, true, cmr, vdbResources); } else { String errorMsg = ex.getMessage()==null?ex.getClass().getName():ex.getMessage(); if (te != null) { errorMsg += ": " + te.getMessage(); //$NON-NLS-1$ } model.addRuntimeError(errorMsg); model.setMetadataStatus(Model.MetadataStatus.FAILED); LogManager.logWarning(LogConstants.CTX_RUNTIME, ex, IntegrationPlugin.Util.gs(IntegrationPlugin.Event.TEIID50036,vdb.getName(), vdb.getVersion(), model.getName(), errorMsg)); if (ex instanceof RuntimeException) { metadataLoaded(vdb, model, vdbMetadataStore, loadCount, factory, false, cmr, vdbResources); } else { if (marked != null) { getExecutor().execute(this); } else { //defer the load to the status checker if/when a source is available/redeployed model.addAttchment(Runnable.class, this); } } } } } }; Executor executor = getExecutor(); //wrap the runnable to trap exceptions that may be caused by an asynch deployment issue executor.execute(new Runnable() { @Override public void run() { try { job.run(); } catch (IllegalStateException e) { if (vdb.getStatus() != Status.FAILED && vdb.getStatus() != Status.REMOVED) { throw e; } LogManager.logDetail(LogConstants.CTX_RUNTIME, e, "Could not load metadata for a removed or failed deployment."); //$NON-NLS-1$ } } }); } private void cacheMetadataStore(final ModelMetaData model, MetadataFactory schema) { boolean cache = true; if (vdb.isXmlDeployment()) { cache = "cached".equalsIgnoreCase(vdb.getPropertyValue("UseConnectorMetadata")); //$NON-NLS-1$ //$NON-NLS-2$ if (cache) { LogManager.logDetail(LogConstants.CTX_RUNTIME, "using VDB metadata caching value", vdb.getPropertyValue("UseConnectorMetadata"), "Note that UseConnectorMetadata is deprecated. Use cache-metadata instead."); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ } } String prop = vdb.getPropertyValue("cache-metadata"); //$NON-NLS-1$ if (prop != null) { cache = Boolean.valueOf(prop); } prop = model.getPropertyValue("cache-metadata"); //$NON-NLS-1$ if (prop != null) { LogManager.logDetail(LogConstants.CTX_RUNTIME, model, "using metadata caching value", prop); //$NON-NLS-1$ cache = Boolean.valueOf(prop); } if (cache) { final File cachedFile = getSerializer().buildModelFile(vdb, model.getName()); try { getSerializer().saveAttachment(cachedFile, schema, false); } catch (Exception e) { LogManager.logWarning(LogConstants.CTX_RUNTIME, e, IntegrationPlugin.Util.gs(IntegrationPlugin.Event.TEIID50044, vdb.getName(), vdb.getVersion(), model.getName())); } } } private void deleteModelCache(String modelName) { final File cachedFile = getSerializer().buildModelFile(vdb, modelName); getSerializer().removeAttachment(cachedFile); } @Override protected VDBRepository getVDBRepository() { return vdbRepositoryInjector.getValue(); } private TranslatorRepository getTranslatorRepository() { return this.translatorRepositoryInjector.getValue(); } private Executor getExecutor() { return this.executorInjector.getValue(); } private ObjectSerializer getSerializer() { return serializerInjector.getValue(); } private void save() throws AdminProcessingException { try { ObjectSerializer os = getSerializer(); VDBMetadataParser.marshell(this.vdb, os.getVdbXmlOutputStream(this.vdb)); } catch (IOException e) { throw new AdminProcessingException(IntegrationPlugin.Event.TEIID50048, e); } catch (XMLStreamException e) { throw new AdminProcessingException(IntegrationPlugin.Event.TEIID50049, e); } } /** * Override for module based loading */ @SuppressWarnings("rawtypes") @Override protected MetadataRepository<?, ?> getMetadataRepository(String repoType) throws VirtualDatabaseException { MetadataRepository<?, ?> repo = super.getMetadataRepository(repoType); if (repo != null) { return repo; } try { repo = TeiidAdd.buildService(MetadataRepository.class, repoType); } catch (OperationFailedException e) { throw new VirtualDatabaseException(IntegrationPlugin.Event.TEIID50057, e, IntegrationPlugin.Util.gs(IntegrationPlugin.Event.TEIID50057, repoType)); } MetadataRepository old = this.repositories.putIfAbsent(repoType, repo); return old!=null?old:repo; } }