/* * 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.udf; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.List; import java.util.Properties; import java.util.Set; import org.eclipse.core.internal.resources.Marker; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IFolder; import org.eclipse.core.resources.IMarker; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IResource; import org.eclipse.core.resources.IResourceChangeEvent; import org.eclipse.core.resources.IResourceChangeListener; import org.eclipse.core.runtime.CoreException; import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.ecore.resource.Resource; import org.teiid.core.designer.ModelerCoreException; import org.teiid.core.designer.util.FileUtils; import org.teiid.core.designer.util.StringConstants; import org.teiid.designer.core.ModelerCore; import org.teiid.designer.core.util.ModelObjectCollector; import org.teiid.designer.core.util.ModelVisitor; import org.teiid.designer.core.util.ModelVisitorProcessor; import org.teiid.designer.core.workspace.ModelObjectAnnotationHelper; import org.teiid.designer.core.workspace.ModelResource; import org.teiid.designer.core.workspace.ModelUtil; import org.teiid.designer.core.workspace.ModelWorkspaceException; import org.teiid.designer.core.workspace.ModelWorkspaceManager; import org.teiid.designer.core.workspace.ResourceChangeUtilities; import org.teiid.designer.core.workspace.WorkspaceResourceFinderUtil; import org.teiid.designer.metamodels.core.ModelType; import org.teiid.designer.metamodels.function.FunctionPlugin; import org.teiid.designer.metamodels.function.ScalarFunction; import org.teiid.designer.metamodels.relational.DirectionKind; import org.teiid.designer.metamodels.relational.Procedure; import org.teiid.designer.metamodels.relational.ProcedureParameter; import org.teiid.designer.metamodels.relational.RelationalPackage; import org.teiid.designer.metamodels.relational.RelationalPlugin; import org.teiid.designer.metamodels.relational.util.PushdownFunctionData; import org.teiid.designer.query.IQueryService; import org.teiid.designer.runtime.spi.ITeiidServer; import org.teiid.designer.runtime.spi.ITeiidServerVersionListener; import org.teiid.designer.runtime.version.spi.ITeiidServerVersion; /** * @since 8.0 */ public final class UdfManager implements IResourceChangeListener { public static final String RELATIONAL_EXT_PROP_PREFIX = "relational:"; //$NON-NLS-1$ public static final String FUNCTION_CATEGORY_PROP = "relational:function-category"; //$NON-NLS-1$ public static final String JAVA_CLASS_PROP = "relational:java-class"; //$NON-NLS-1$ public static final String JAVA_METHOD_PROP = "relational:java-method"; //$NON-NLS-1$ private static UdfManager INSTANCE; private ModelObjectAnnotationHelper ANNOTATION_HELPER = new ModelObjectAnnotationHelper(); private IFunctionLibrary systemFunctionLibrary; private IFunctionLibrary cachedFunctionLibrary; /** * A set of function models. * * @since 6.0.0 */ private Set<ModelResource> functionModels = new HashSet<ModelResource>(); private volatile boolean initialized; private boolean changed = false; private ITeiidServerVersionListener teiidServerVersionListener = new ITeiidServerVersionListener() { @Override public void serverChanged(ITeiidServer server) { // Nothing to do } @Override public void versionChanged(ITeiidServerVersion version) { systemFunctionLibrary = null; cachedFunctionLibrary = null; } }; /** * Get the singleton instance of the manager * * @return single instance of {@link UdfManager} */ public static UdfManager getInstance() { if (INSTANCE == null) { INSTANCE = new UdfManager(); try { INSTANCE.initialize(); } catch (Exception ex) { throw new RuntimeException(ex); } } return INSTANCE; } /** * Don't allow public construction. * * @since 6.0.0 */ private UdfManager() { } /** * Ensures the UDF model is located in the workspace and the FunctionLibraryManager has been initialized using the default UDF * model. <strong>This must be called before any other method can be used.</strong> * * @throws Exception if there was a problem with the UDF model or library manager * @since 6.0.0 */ public synchronized void initialize() throws Exception { if (initialized) { return; } // register to receive resource events so that we can notify listeners when a function model changes ModelerCore.getWorkspace().addResourceChangeListener(this); // register as a listener for when the default teiid instance version is modified ModelerCore.addTeiidServerVersionListener(teiidServerVersionListener); // Register ModelResources that 1) are type function model or 2) are type relational, containing a procedure with function=true Collection<IFile> allResources = WorkspaceResourceFinderUtil.getProjectFileResources(); try { for( IResource next : allResources ) { if(! ModelUtil.isModelFile(next, true)) { continue; } ModelResource mr = ModelUtil.getModelResource((IFile)next, true); int theModelType = mr.getModelType().getValue(); if( theModelType == ModelType.FUNCTION) { registerFunctionModel(mr, false); } else if(theModelType == ModelType.PHYSICAL || theModelType==ModelType.VIRTUAL) { Collection<Procedure> functionProcs = getFunctions(mr); if(!functionProcs.isEmpty()) { registerFunctionModel(mr,false); } } } } catch (Exception err) { UdfPlugin.UTIL.log(err); } initialized = true; } /** * @param udfModel the UDF model being checked * @return <code>true</code> if the workspace function model does not have validation errors * @throws CoreException if there is a problem obtaining the EMF resource of the workspace function model * @since 6.0.0 */ private boolean isFunctionObjectErrorFree( EObject functionEObject, IMarker[] markers, ModelResource udfModelResource) { if(markers != null && markers.length > 0) { for (int ndx = markers.length; --ndx >= 0;) { IMarker iMarker = markers[ndx]; EObject targetEObject = null; try { targetEObject = ModelWorkspaceManager.getModelWorkspaceManager().getMarkerManager().getMarkedEObject(udfModelResource.getCorrespondingResource(), iMarker); } catch (ModelWorkspaceException ex) { UdfPlugin.UTIL.log(ex); } if( targetEObject == functionEObject ) { Object attribute = null; if( iMarker != null ) { try { attribute = iMarker.getAttribute(IMarker.SEVERITY); } catch (CoreException e) { // ResourceException is caught here because some calls to getAttribute() may be on an IMarker who's resource // does not exist in the workspace any more. (Defect 15552) if (e instanceof ModelerCoreException ) { UdfPlugin.UTIL.log(e); } } } if (attribute == null) { return true; } // Asserting attr is an Integer... final int severity = ((Integer)attribute).intValue(); if (severity == IMarker.SEVERITY_ERROR) { return false; } } } } return true; } private IMarker[] getMarkers(ModelResource udfModelResource ) { IResource resrc = null; if (udfModelResource != null && udfModelResource.exists() ) { resrc = udfModelResource.getResource(); } if( resrc != null ) { IMarker[] markers = null; try { markers = resrc.findMarkers(IMarker.PROBLEM, false, IResource.DEPTH_INFINITE); } catch (CoreException ex) { UdfPlugin.UTIL.log(ex); return new Marker[0]; } return markers; } return new Marker[0]; } private synchronized boolean makeModification(ModelResource modelResource, boolean deleted ) throws Exception { // let query engine know of the change so that functions can be available for modeling boolean result = false; if (deleted) { //System.out.println(" UDF model " + modelResource.getItemName() + " was REMOVED from the FunctionLibrary"); result = this.functionModels.remove(modelResource); } else { //System.out.println(" UDF model " + modelResource.getItemName() + " was ADDED to the FunctionLibrary"); result = this.functionModels.add(modelResource); } return result; } /** * Informs this manager that a function model has changed and that the cached function library needs to be reloaded. * * @param modelResource the model resource that was changed * @param delete whether it was a delete or not * @throws Exception if error occurs */ public void registerFunctionModel( ModelResource modelResource, boolean delete ) throws Exception { makeModification(modelResource, delete); changed = true; } /** * Informs this manager that a source model has changed and that the cached function library needs to be reloaded. * * @param modelResource the model resource that was changed * @param delete whether it was a delete or not */ public void notifySourceModelChanged( ModelResource modelResource, boolean delete ) { changed = true; } @Override public void resourceChanged( IResourceChangeEvent event ) { if (!initialized) { return; } if (ResourceChangeUtilities.isPreClose(event)) { IProject project = (IProject)event.getResource(); if (ModelerCore.hasModelNature(project)) { modelProjectClosed(project); } } else if (ResourceChangeUtilities.isPreDelete(event)) { IProject project = (IProject)event.getResource(); if (ModelerCore.hasModelNature(project)) { modelProjectDeleted(project); } } } private void modelProjectClosed(IProject project) { unregisterProject(project); } private void modelProjectDeleted(IProject project) { //System.out.println(" Model Project " + project.getName() + " is being DELETED"); if (! project.isOpen()) { /* * project should already have been unregistered and we cannot * analysis project members due to the project being closed */ return; } unregisterProject(project); } /** * @param project */ private void unregisterProject(IProject project) { try { for( IResource res : project.members()) { if( res instanceof IFolder ) { folderDeleted((IFolder)res); } else if(res instanceof IFile) { fileDeleted((IFile)res); } } } catch (CoreException ex) { UdfPlugin.UTIL.log(ex); } catch (Exception ex) { UdfPlugin.UTIL.log(ex); } } private void folderDeleted(IFolder folder) throws CoreException, Exception { for( IResource res : folder.members()) { if( res instanceof IFolder ) { folderDeleted((IFolder)res); } else if(res instanceof IFile) { fileDeleted((IFile)res); } } } private void fileDeleted(IFile file) throws ModelWorkspaceException, Exception { // Do the check for UDF file and un-register the model ModelResource modelResource = ModelerCore.getModelEditor().findModelResource(file); if( modelResource != null ) { UdfManager.INSTANCE.registerFunctionModel(modelResource, true); } } /** * Ensures the UDF model framework is shutdown correctly. <strong>This must be called at the time the plugin is * stopped.</strong> * * @since 6.0.0 */ public void shutdown() { ModelerCore.getWorkspace().removeResourceChangeListener(this); ModelerCore.removeTeiidServerVersionListener(teiidServerVersionListener); } /** * Get the default system function library * * @return implementation of {@link IFunctionLibrary} */ public IFunctionLibrary<IFunctionForm, IFunctionDescriptor> getSystemFunctionLibrary() { IQueryService queryService = ModelerCore.getTeiidQueryService(); if( this.systemFunctionLibrary == null ) { this.systemFunctionLibrary = queryService.createFunctionLibrary(); } return this.systemFunctionLibrary; } /** * Get the FunctionLibrary * @return the FunctionLibrary */ public synchronized IFunctionLibrary<IFunctionForm, IFunctionDescriptor> getFunctionLibrary() { //System.out.println("UdfManger.getFunctionLibrary()"); if( !changed && this.cachedFunctionLibrary != null ) { return this.cachedFunctionLibrary; } List<FunctionMethodDescriptor> functionMethodDescriptors = new ArrayList<FunctionMethodDescriptor>(); for( ModelResource functionModelResource : functionModels ) { ScalarFunction[] functions = getScalarFunctions(functionModelResource); if( functions.length == 0 ) continue; IMarker[] markers = getMarkers(functionModelResource); String schema = FileUtils.getFilenameWithoutExtension(functionModelResource.getItemName()); for( ScalarFunction function : functions ) { // Function's must have a return parameter and a Scalar function may not yet have one after // it's initially created (intermediate state) // Also the Function AND it's return parameter (if non-null) need to be error free if( !isFunctionObjectErrorFree(function.getReturnParameter(), markers, functionModelResource) || function.getReturnParameter() == null || !isFunctionObjectErrorFree(function.getReturnParameter(), markers, functionModelResource)) { continue; } String description = null; try { description = ModelerCore.getModelEditor().getDescription(function); } catch (ModelerCoreException ex) { UdfPlugin.UTIL.log(ex); } boolean functionPamameterHasError = false; Collection<FunctionParameterDescriptor> fParams = new ArrayList<FunctionParameterDescriptor>(); for( Object inputParam : function.getInputParameters() ) { if( inputParam instanceof org.teiid.designer.metamodels.function.FunctionParameter) { org.teiid.designer.metamodels.function.FunctionParameter param = (org.teiid.designer.metamodels.function.FunctionParameter)inputParam; fParams.add(new FunctionParameterDescriptor(param.getName(), param.getType())); // If any function parameter has an error don't add this if( !functionPamameterHasError && !isFunctionObjectErrorFree(param, markers, functionModelResource)){ functionPamameterHasError = true; } } } if( functionPamameterHasError ) { continue; } String returnParamName = ModelerCore.getModelEditor().getName(function.getReturnParameter()); FunctionParameterDescriptor outputParam = new FunctionParameterDescriptor(returnParamName, function.getReturnParameter().getType()); FunctionMethodDescriptor fMethodDescriptor = new FunctionMethodDescriptor(function, function.getName(), description, function.getCategory(), function.getInvocationClass(), function.getInvocationMethod(), fParams.toArray(new FunctionParameterDescriptor[0]), outputParam, schema); fMethodDescriptor.setPushDown(function.getPushDown().getLiteral()); fMethodDescriptor.setDeterministic(function.isDeterministic()); boolean varArgs = false; String propValue = FunctionPlugin.getExtensionProperty(function, "function:varargs"); //$NON-NLS-1$ if( propValue != null && propValue.length() > 0 ) { varArgs = Boolean.parseBoolean(propValue); } fMethodDescriptor.setVariableArgs(varArgs); functionMethodDescriptors.add(fMethodDescriptor); } } // Now walk "Relational models" to search for new Procedures that have FUNCTION = true set // -- Source procedures will be the pushdown functions // -- View procedures will be the user-defined functions for( ModelResource sourceModel : getRelationalModels() ) { IMarker[] markers = getMarkers(sourceModel); String schema = FileUtils.getFilenameWithoutExtension(sourceModel.getItemName()); for( Procedure procedure : getFunctions(sourceModel) ) { // Determine if working with a Source or View Model boolean isPhysical = ModelUtil.isPhysical(procedure); // Also the Function's input parameters AND it's return parameter (if non-null) need to be error free ProcedureWrapper wrappedProcedure = new ProcedureWrapper(procedure); if( !isFunctionObjectErrorFree(wrappedProcedure.getReturnParameter(), markers, sourceModel) || wrappedProcedure.getReturnParameter() == null || !isFunctionObjectErrorFree(wrappedProcedure.getReturnParameter(), markers, sourceModel)) { continue; } String description = null; try { description = ModelerCore.getModelEditor().getDescription(procedure); } catch (ModelerCoreException ex) { UdfPlugin.UTIL.log(ex); } boolean functionPamameterHasError = false; Collection<FunctionParameterDescriptor> fParams = new ArrayList<FunctionParameterDescriptor>(); for( ProcedureParameter inputParam : wrappedProcedure.getInputParameters() ) { String dTypeName = ModelerCore.getModelEditor().getName(inputParam.getType()); fParams.add(new FunctionParameterDescriptor(inputParam.getName(), dTypeName)); // If any function parameter has an error don't add this if( !functionPamameterHasError && !isFunctionObjectErrorFree(inputParam, markers, sourceModel)){ functionPamameterHasError = true; } } if( functionPamameterHasError ) { continue; } String dTypeName = ModelerCore.getModelEditor().getName(wrappedProcedure.getReturnParameter().getType()); String returnParamName = ModelerCore.getModelEditor().getName(wrappedProcedure.getReturnParameter()); FunctionParameterDescriptor outputParam = new FunctionParameterDescriptor(returnParamName, dTypeName); String category = wrappedProcedure.getCategory(); String javaClass = wrappedProcedure.getJavaClass(); String javaMethod = wrappedProcedure.getJavaMethod(); boolean javaClassAndMethodEmpty = (javaClass==null && javaMethod==null) ? true : false; // For source pushdown function, set the category name as the Model Name boolean isPushdown = false; if(isPhysical && javaClassAndMethodEmpty) { isPushdown = true; category = sourceModel.getItemName(); if( category.endsWith(StringConstants.DOT_XMI)) { category = category.replaceAll(StringConstants.DOT_XMI, StringConstants.EMPTY_STRING); } } FunctionMethodDescriptor fMethodDescriptor = new FunctionMethodDescriptor(procedure, wrappedProcedure.getName(), description, category, javaClass, javaMethod, fParams.toArray(new FunctionParameterDescriptor[0]), outputParam, schema); if( isPushdown) { fMethodDescriptor.setPushDown(Boolean.toString(true)); } fMethodDescriptor.setDeterministic(wrappedProcedure.isDeterministic()); boolean varArgs = false; String propValue = RelationalPlugin.getExtensionProperty(procedure, "relational:varargs"); //$NON-NLS-1$ if( propValue != null && propValue.length() > 0 ) { varArgs = Boolean.parseBoolean(propValue); } fMethodDescriptor.setVariableArgs(varArgs); functionMethodDescriptors.add(fMethodDescriptor); } } IQueryService queryService = ModelerCore.getTeiidQueryService(); this.cachedFunctionLibrary = queryService.createFunctionLibrary(functionMethodDescriptors); this.changed = false; return this.cachedFunctionLibrary; } /* * Get all procedures from the supplied model, where FUNCTION=true */ private Collection<Procedure> getFunctions(ModelResource relationalModel) { Collection<Procedure> functions = new ArrayList<Procedure>(); try { final ModelObjectCollector moc = new ModelObjectCollector(relationalModel.getEmfResource()); for( Object eObj : moc.getEObjects()){ if( eObj instanceof Procedure ) { if( ((Procedure)eObj).isFunction()) { functions.add((Procedure)eObj); } } } } catch (CoreException ex) { UdfPlugin.UTIL.log(ex); } return functions; } private Collection<ModelResource> getRelationalModels() { Collection<ModelResource> relationalMdls = new ArrayList<ModelResource>(); try { ModelResource[] allModels = ModelWorkspaceManager.getModelWorkspaceManager().getModelWorkspace().getModelResources(); for( ModelResource model : allModels ) { String uri = model.getPrimaryMetamodelUri(); if( RelationalPackage.eNS_URI.equalsIgnoreCase(uri) ) { relationalMdls.add(model); } } } catch (CoreException ex) { UdfPlugin.UTIL.log(ex); } return relationalMdls; } private ScalarFunction[] getScalarFunctions(ModelResource mr) { ScalarFunctionFinder visitor = new ScalarFunctionFinder(); final int mode = ModelVisitorProcessor.MODE_VISIBLE_CONTAINMENTS; // show only those objects visible to user final ModelVisitorProcessor processor = new ModelVisitorProcessor(visitor,mode); try { processor.walk(mr, ModelVisitorProcessor.DEPTH_INFINITE); } catch (ModelerCoreException ex) { UdfPlugin.UTIL.log(ex); } return visitor.getFunctions(); } class ScalarFunctionFinder implements ModelVisitor { Collection<ScalarFunction> functions; @SuppressWarnings("unused") @Override public boolean visit(EObject object) throws ModelerCoreException { // Tables are contained by Catalogs, Schemas and Resources if (object instanceof ScalarFunction) { if( functions == null ) { functions = new ArrayList<ScalarFunction>(); } functions.add((ScalarFunction)object); return true; } return false; } @SuppressWarnings("unused") @Override public boolean visit(Resource resource) throws ModelerCoreException { return true; } public ScalarFunction[] getFunctions() { if( functions == null ) { functions = new ArrayList<ScalarFunction>(); } return functions.toArray(new ScalarFunction[0]); } } class ProcedureWrapper { Procedure procedure; ProcedureParameter returnParam; Collection<ProcedureParameter> inputParams; boolean deterministic = false; String category; String javaClass; String javaMethod; public ProcedureWrapper(Procedure procedure) { super(); this.procedure = procedure; init(); } private void init() { inputParams = new ArrayList<ProcedureParameter>(); for( Object obj : procedure.getParameters() ) { ProcedureParameter param = (ProcedureParameter)obj; if( param.getDirection() == DirectionKind.IN_LITERAL ) { inputParams.add(param); } else if( param.getDirection() == DirectionKind.RETURN_LITERAL) { returnParam = param; } } try { Properties relationalProps = ANNOTATION_HELPER.getProperties(procedure, UdfManager.RELATIONAL_EXT_PROP_PREFIX); Properties extProps = ANNOTATION_HELPER.getExtendedProperties(procedure); if( extProps != null && extProps.size() > 0 ) { Object determValue = extProps.get(PushdownFunctionData.DETERMINISTIC_PROPERTY_KEY); if( determValue != null ) { deterministic = Boolean.valueOf((String)determValue); } } if(relationalProps!=null && relationalProps.size()>0) { Object categoryValue = relationalProps.get(UdfManager.FUNCTION_CATEGORY_PROP); if(categoryValue!=null && !((String)categoryValue).isEmpty()) { category = (String)categoryValue; } Object javaClassValue = relationalProps.get(UdfManager.JAVA_CLASS_PROP); if(javaClassValue!=null && !((String)javaClassValue).isEmpty()) { javaClass = (String)javaClassValue; } Object javaMethodValue = relationalProps.get(UdfManager.JAVA_METHOD_PROP); if(javaMethodValue!=null && !((String)javaMethodValue).isEmpty()) { javaMethod = (String)javaMethodValue; } } } catch (ModelerCoreException ex) { UdfPlugin.UTIL.log(ex); } } public ProcedureParameter getReturnParameter() { return this.returnParam; } public Collection<ProcedureParameter> getInputParameters() { return this.inputParams; } public String getName() { return ModelerCore.getModelEditor().getName(procedure); } public boolean isDeterministic() { return this.deterministic; } /* * Get the category. Will only be set for view function - will be null for source */ public String getCategory() { return category; } /* * Get the java class. Will only be set for view function - will be null for source */ public String getJavaClass() { return javaClass; } /* * Get the java method. Will only be set for view function - will be null for source */ public String getJavaMethod() { return javaMethod; } } }