diff --git a/repository/src/main/java/org/orderofthebee/addons/support/tools/repo/jsconsole/AlfrescoScriptAPITernGet.java b/repository/src/main/java/org/orderofthebee/addons/support/tools/repo/jsconsole/AlfrescoScriptAPITernGet.java new file mode 100644 index 0000000..743cf7f --- /dev/null +++ b/repository/src/main/java/org/orderofthebee/addons/support/tools/repo/jsconsole/AlfrescoScriptAPITernGet.java @@ -0,0 +1,1237 @@ +/** + * Copyright (C) 2016 - 2022 Order of the Bee + * + * This file is part of OOTBee Support Tools + * + * OOTBee Support Tools 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 3 of the License, + * or (at your option) any later version. + * + * OOTBee Support Tools 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 OOTBee Support Tools. If not, see + * . + * + * Linked to Alfresco + * Copyright (C) 2005 - 2022 Alfresco Software Limited. + * + * This file is part of code forked from the JavaScript Console project + * which was licensed under the Apache License, Version 2.0 at the time. + * In accordance with that license, the modifications / derivative work + * is now being licensed under the LGPL as part of the OOTBee Support Tools + * addon. + */ + + /** + * forked from https://github.com/AFaust/js-console + */ + +package org.orderofthebee.addons.support.tools.repo.jsconsole; + +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.Date; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Properties; +import java.util.Set; +import java.util.concurrent.atomic.AtomicInteger; + +import org.alfresco.processor.ProcessorExtension; +import org.alfresco.repo.jscript.BaseScopableProcessorExtension; +import org.alfresco.repo.jscript.NativeMap; +import org.alfresco.repo.jscript.Scopeable; +import org.alfresco.repo.jscript.ScriptLogger; +import org.alfresco.repo.jscript.ScriptNode; +import org.alfresco.repo.jscript.ScriptableHashMap; +import org.alfresco.repo.jscript.ScriptableQNameMap; +import org.alfresco.repo.processor.BaseProcessor; +import org.alfresco.repo.processor.BaseProcessorExtension; +import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.repo.workflow.WorkflowModel; +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.dictionary.AspectDefinition; +import org.alfresco.service.cmr.dictionary.ClassDefinition; +import org.alfresco.service.cmr.dictionary.DictionaryService; +import org.alfresco.service.cmr.dictionary.PropertyDefinition; +import org.alfresco.service.cmr.dictionary.TypeDefinition; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.ScriptService; +import org.alfresco.service.cmr.security.PersonService; +import org.alfresco.service.namespace.NamespaceService; +import org.alfresco.service.namespace.QName; +import org.alfresco.util.Pair; +import org.alfresco.util.PropertyCheck; +import org.mozilla.javascript.Scriptable; +import org.mozilla.javascript.ScriptableObject; +import org.springframework.beans.factory.DisposableBean; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.context.ApplicationContextAware; +import org.springframework.core.LocalVariableTableParameterNameDiscoverer; +import org.springframework.core.ParameterNameDiscoverer; +import org.springframework.extensions.surf.util.I18NUtil; +import org.springframework.extensions.webscripts.Cache; +import org.springframework.extensions.webscripts.DeclarativeWebScript; +import org.springframework.extensions.webscripts.ScriptableLinkedHashMap; +import org.springframework.extensions.webscripts.ScriptableMap; +import org.springframework.extensions.webscripts.Status; +import org.springframework.extensions.webscripts.WebScript; +import org.springframework.extensions.webscripts.WebScriptRequest; + +/** + * @author Axel Faust + */ +public class AlfrescoScriptAPITernGet extends DeclarativeWebScript implements InitializingBean +{ + + private static final Collection> PRIMITIVE_NUMBER_CLASSES = Collections.unmodifiableList(Arrays.> asList(byte.class, + short.class, int.class, long.class, float.class, double.class)); + + private static final Collection> CUTOFF_CLASSES = Collections.unmodifiableList(Arrays.> asList(Object.class, + Scriptable.class, org.springframework.extensions.webscripts.processor.BaseProcessorExtension.class, + BaseProcessorExtension.class, BaseScopableProcessorExtension.class, ScriptableObject.class, List.class, Map.class, Set.class)); + + private static final Collection> CUTOFF_INTERFACES = Collections.unmodifiableList(Arrays.> asList(Scriptable.class, + ProcessorExtension.class, org.springframework.extensions.surf.core.processor.ProcessorExtension.class, Scopeable.class, + ApplicationContextAware.class, InitializingBean.class, DisposableBean.class)); + + private static final Collection INIT_METHOD_NAMES = Collections.unmodifiableSet(new HashSet(Arrays. asList( + "init", "register"))); + + private static final ParameterNameDiscoverer PARAMETER_NAME_DISCOVERER = new LocalVariableTableParameterNameDiscoverer(); + + protected NamespaceService namespaceService; + + protected DictionaryService dictionaryService; + + protected ScriptService scriptService; + + protected PersonService personService; + + protected ServiceRegistry serviceRegistry; + + protected BaseProcessor scriptProcessor; + + protected Properties properties; + + /** + * {@inheritDoc} + */ + @Override + public void afterPropertiesSet() + { + PropertyCheck.mandatory(this, "namespaceService", this.namespaceService); + PropertyCheck.mandatory(this, "dictionaryService", this.dictionaryService); + PropertyCheck.mandatory(this, "scriptService", this.scriptService); + PropertyCheck.mandatory(this, "personService", this.personService); + PropertyCheck.mandatory(this, "serviceRegistry", this.serviceRegistry); + PropertyCheck.mandatory(this, "scriptProcessor", this.scriptProcessor); + + PropertyCheck.mandatory(this, "properties", this.properties); + } + + /** + * @param namespaceService + * the namespaceService to set + */ + public void setNamespaceService(final NamespaceService namespaceService) + { + this.namespaceService = namespaceService; + } + + /** + * @param dictionaryService + * the dictionaryService to set + */ + public void setDictionaryService(final DictionaryService dictionaryService) + { + this.dictionaryService = dictionaryService; + } + + /** + * @param scriptService + * the scriptService to set + */ + public void setScriptService(final ScriptService scriptService) + { + this.scriptService = scriptService; + } + + /** + * @param personService + * the personService to set + */ + public void setPersonService(final PersonService personService) + { + this.personService = personService; + } + + /** + * @param serviceRegistry + * the serviceRegistry to set + */ + public void setServiceRegistry(final ServiceRegistry serviceRegistry) + { + this.serviceRegistry = serviceRegistry; + } + + /** + * @param scriptProcessor + * the scriptProcessor to set + */ + public void setScriptProcessor(final BaseProcessor scriptProcessor) + { + this.scriptProcessor = scriptProcessor; + } + + /** + * @param properties + * the properties to set + */ + public void setProperties(final Properties properties) + { + this.properties = properties; + } + + /** + * {@inheritDoc} + */ + @Override + protected Map executeImpl(final WebScriptRequest req, final Status status, final Cache cache) + { + final Map model = new HashMap(); + + this.prepareCoreScriptAPIJavaTypeDefinitions(model); + this.prepareCoreScriptAPIGlobalDefinitions(model); + this.preparePropertyDefinitions(model); + // TODO Process action definitions + parameters + + this.prepareWebScriptAPIJavaTypeDefinitions(req, model); + this.prepareWebScriptAPIGlobalDefinitions(req, model); + + return model; + } + + /** + * Prepares the type definitions for the core script API of Alfresco (common across all use cases) + * + * @param model + * the current web script model into which to insert the definitions + */ + protected void prepareCoreScriptAPIJavaTypeDefinitions(final Map model) + { + final Map scriptModel = this.buildScriptAPIModel(); + model.put("scriptAPIJavaTypeDefinitions", this.prepareJavaTypeDefinitions(scriptModel)); + } + + /** + * Prepares the type definitions for the script API specific to Alfresco web scripts + * + * @param req + * the current web script request + * @param model + * the current web script model into which to insert the definitions + */ + protected void prepareWebScriptAPIJavaTypeDefinitions(final WebScriptRequest req, final Map model) + { + final ScriptDetails script = this.getExecuteScript(req.getContentType()); + final Map scriptModel = this.createScriptParameters(req, null, script, Collections. emptyMap()); + + this.removeCoreScriptAPIGlobalsFromWebScriptAPI(scriptModel); + + model.put("webScriptAPIJavaTypeDefinitions", this.prepareJavaTypeDefinitions(scriptModel)); + } + + /** + * Removes core script API globals from a script model specific to web script execution + * + * @param scriptModel + * the script model of a web script + */ + protected void removeCoreScriptAPIGlobalsFromWebScriptAPI(final Map scriptModel) + { + // avoid unnecessary overlap between web script and standard script API model + // remove well known types handled in core script API + final Collection keysToRemove = new HashSet(); + for (final Entry entry : scriptModel.entrySet()) + { + final Object value = entry.getValue(); + if (value instanceof ScriptNode || value instanceof NodeRef || value instanceof ScriptLogger) + { + keysToRemove.add(entry.getKey()); + } + } + } + + /** + * Prepares the type definitions for Java classes found in a specific model. + * + * @param model + * the model containing objects exposed to scripts + * @return the list of models for each type found directly or transitively (via public properties / methods) in the model elements + */ + protected List> prepareJavaTypeDefinitions(final Map model) + { + final List> typeDefinitions = new ArrayList>(); + + final Collection> classesToDescribe = new HashSet>(); + final Collection> classesDescribed = new HashSet>(); + + for (final Entry modelEntry : model.entrySet()) + { + if (modelEntry.getValue() instanceof NodeRef) + { + modelEntry.setValue(new ScriptNode((NodeRef) modelEntry.getValue(), this.serviceRegistry)); + } + } + + classesToDescribe.addAll(Arrays.asList(String.class, Boolean.class, Number.class, Date.class)); + for (final Entry globalEntry : model.entrySet()) + { + final String globalPrefix = "global." + globalEntry.getKey(); + final String skip = this.properties.getProperty(globalPrefix + ".skip"); + if (skip == null || skip.isEmpty() || !Boolean.parseBoolean(skip)) + { + final Class realValueType = globalEntry.getValue().getClass(); + Class effectiveValueType = realValueType; + effectiveValueType = this.determineEffectiveType(realValueType, effectiveValueType, globalPrefix); + this.determineType(effectiveValueType, classesToDescribe); + } + } + + while (classesToDescribe.size() > classesDescribed.size()) + { + final Collection> remainingClasses = new HashSet>(classesToDescribe); + remainingClasses.removeAll(classesDescribed); + + for (final Class cls : remainingClasses) + { + if (!CUTOFF_CLASSES.contains(cls)) + { + final String typeClsName = cls.getName(); + final String typePrefix = "type." + typeClsName; + final String skip = this.properties.getProperty(typePrefix + ".skip"); + + if (skip == null || skip.isEmpty() || !Boolean.parseBoolean(skip)) + { + final Map typeDefinition = new HashMap(); + final Collection> relatedClasses = this.fillClassTypeDefinition(cls, typeDefinition); + classesToDescribe.addAll(relatedClasses); + typeDefinitions.add(typeDefinition); + } + } + classesDescribed.add(cls); + } + } + + return typeDefinitions; + } + + /** + * Prepares the definitions for global / root scope objects found in the core script API model (common across all use cases) + * + * @param model + * the model into which to insert the global definitions + */ + protected void prepareCoreScriptAPIGlobalDefinitions(final Map model) + { + final Map scriptModel = this.buildScriptAPIModel(); + model.put("scriptAPIGlobalDefinitions", this.prepareGlobalDefinitions(scriptModel)); + } + + /** + * Prepares the definitions for global / root scope objects found in the script API model specific to Alfresco web scripts + * + * @param req + * the current web script request + * @param model + * the model into which to insert the global definitions + */ + protected void prepareWebScriptAPIGlobalDefinitions(final WebScriptRequest req, final Map model) + { + final ScriptDetails script = this.getExecuteScript(req.getContentType()); + final Map scriptModel = this.createScriptParameters(req, null, script, Collections. emptyMap()); + + this.removeCoreScriptAPIGlobalsFromWebScriptAPI(scriptModel); + + model.put("webScriptAPIGlobalDefinitions", this.prepareGlobalDefinitions(scriptModel)); + } + + /** + * Prepares the global definitions for Java objects found in a specific model. + * + * @param model + * the model containing objects exposed to scripts + * @return the list of models for each global value + */ + protected List> prepareGlobalDefinitions(final Map model) + { + final List> globalDefinitions = new ArrayList>(); + + for (final Entry modelEntry : model.entrySet()) + { + if (modelEntry.getValue() instanceof NodeRef) + { + modelEntry.setValue(new ScriptNode((NodeRef) modelEntry.getValue(), this.serviceRegistry)); + } + } + + final Collection> dummyClasses = new HashSet>(); + + for (final Entry globalEntry : model.entrySet()) + { + final String globalPrefix = "global." + globalEntry.getKey(); + + final String skip = this.properties.getProperty(globalPrefix + ".skip"); + if (skip == null || skip.isEmpty() || !Boolean.parseBoolean(skip)) + { + final Map globalDefinition = new HashMap(); + globalDefinition.put("name", globalEntry.getKey()); + + final Object value = globalEntry.getValue(); + final Class realValueType = value.getClass(); + Class effectiveValueType = realValueType; + effectiveValueType = this.determineEffectiveType(realValueType, effectiveValueType, globalPrefix); + final Class valueType = this.determineType(effectiveValueType, dummyClasses); + + String type = valueType.getSimpleName(); + + final String globalTernName = this.properties.getProperty(globalPrefix + ".ternName"); + if (globalTernName != null && !globalTernName.isEmpty()) + { + type = globalTernName; + } + else + { + final String clsName = valueType.getName(); + final String typePrefix = "type." + clsName; + final String typeTernName = this.properties.getProperty(typePrefix + ".ternName"); + if (typeTernName != null && !typeTernName.isEmpty()) + { + type = typeTernName; + } + } + globalDefinition.put("type", type); + + // support I18n for any documentation + final String i18nKey = "javascript-console.tern." + globalPrefix + ".ternDoc"; + String ternDoc = I18NUtil.getMessage(i18nKey); + if (ternDoc == null || ternDoc.isEmpty() || ternDoc.equals(i18nKey)) + { + ternDoc = this.properties.getProperty(globalPrefix + ".ternDoc"); + } + if (ternDoc != null && !ternDoc.isEmpty()) + { + globalDefinition.put("doc", ternDoc); + } + + globalDefinitions.add(globalDefinition); + } + } + + return globalDefinitions; + } + + protected Collection> fillClassTypeDefinition(final Class cls, final Map typeDefinition) + { + final Collection> relatedClasses = new HashSet>(); + + final String clsName = cls.getName(); + final String commonPrefix = "type." + clsName; + + String name = cls.getSimpleName(); + final String ternName = this.properties.getProperty(commonPrefix + ".ternName"); + if (ternName != null && !ternName.isEmpty()) + { + name = ternName; + } + typeDefinition.put("name", name); + + // support I18n for any documentation + final String i18nKey = "javascript-console.tern." + commonPrefix + ".ternDoc"; + String ternDoc = I18NUtil.getMessage(i18nKey); + if (ternDoc == null || ternDoc.isEmpty() || ternDoc.equals(i18nKey)) + { + ternDoc = this.properties.getProperty(commonPrefix + ".ternDoc"); + } + if (ternDoc != null && !ternDoc.isEmpty()) + { + typeDefinition.put("doc", ternDoc); + } + + String ternUrl = this.properties.getProperty(commonPrefix + ".ternUrl"); + if ((ternUrl == null || ternUrl.isEmpty()) && clsName.startsWith("org.alfresco.")) + { + ternUrl = "http://dev.alfresco.com/resource/docs/java/" + clsName.replace('.', '/') + ".html"; + } + + if (ternUrl != null && !ternUrl.isEmpty()) + { + typeDefinition.put("url", ternUrl); + } + + final Class superclass = cls.getSuperclass(); + if (superclass != null && !CUTOFF_CLASSES.contains(superclass)) + { + relatedClasses.add(superclass); + + final String superClsName = superclass.getName(); + final String superPrefix = "type." + superClsName; + String superName = superclass.getSimpleName(); + final String superTernName = this.properties.getProperty(superPrefix + ".ternName"); + if (superTernName != null && !superTernName.isEmpty()) + { + superName = superTernName; + } + typeDefinition.put("prototype", superName); + } + + final String nameOnly = this.properties.getProperty(commonPrefix + ".nameOnly"); + if (nameOnly == null || nameOnly.isEmpty() || !Boolean.parseBoolean(nameOnly)) + { + this.fillClassTypeMemberDefinitions(cls, typeDefinition, relatedClasses, commonPrefix, superclass); + } + + return relatedClasses; + } + + protected void fillClassTypeMemberDefinitions(final Class cls, final Map typeDefinition, + final Collection> relatedClasses, final String commonPrefix, final Class superclass) + { + final List> memberDefinitions = new ArrayList>(); + final Map usedMemberNames = new HashMap(); + final Collection handledProperties = new HashSet(); + + final List methods = this.collectDocumentableMethods(cls); + + for (final Method method : methods) + { + final String methodName = method.getName(); + final String memberName; + Map memberDefinition = null; + + if (methodName.matches("^get[A-Z].*$") && method.getParameterTypes().length == 0) + { + memberName = methodName.substring(3, 4).toLowerCase(Locale.ENGLISH) + methodName.substring(4); + if (!handledProperties.contains(memberName)) + { + memberDefinition = this.buildClassPropertyMemberDefinition(cls, method, commonPrefix, memberName, relatedClasses); + if (memberDefinition != null) + { + handledProperties.add(memberName); + } + } + } + else if (methodName.matches("^set[A-Z].*$") && method.getParameterTypes().length == 1) + { + memberName = methodName; + final Class parameterType = method.getParameterTypes()[0]; + + boolean isPropertyRelated; + final String getterName = "get" + methodName.substring(3); + try + { + final Method getter = cls.getMethod(getterName); + isPropertyRelated = getter.getReturnType().equals(parameterType); + } + catch (final NoSuchMethodException nsmex) + { + isPropertyRelated = false; + } + + if (!isPropertyRelated) + { + memberDefinition = this.buildClassMethodMemberDefinition(cls, method, commonPrefix, relatedClasses); + } + } + else + { + memberName = methodName; + memberDefinition = this.buildClassMethodMemberDefinition(cls, method, commonPrefix, relatedClasses); + } + + if (memberDefinition != null) + { + memberDefinitions.add(memberDefinition); + + AtomicInteger count = usedMemberNames.get(memberName); + if (count == null) + { + count = new AtomicInteger(1); + usedMemberNames.put(memberName, count); + } + else + { + memberDefinition.put("originalName", memberName); + memberDefinition.put("name", memberName + count.incrementAndGet()); + } + } + } + typeDefinition.put("members", memberDefinitions); + } + + protected List collectDocumentableMethods(final Class cls) + { + // collect classes in hierarchy from base to special + final List> classHierarchy = new LinkedList>(); + Class curCls = cls; + while (curCls != null) + { + classHierarchy.add(0, curCls); + + final Collection> interfaces = new HashSet>(Arrays.asList(curCls.getInterfaces())); + + // interfaces are the collection of superclasses for an interface + if (curCls.isInterface()) + { + // TODO Handle transitive interface extensions if this becomes a relevant issue + classHierarchy.addAll(0, interfaces); + } + + // include cutoff interfaces earlier so we capture method definitions before implementations / redefinition + interfaces.retainAll(CUTOFF_INTERFACES); + if (!interfaces.isEmpty()) + { + classHierarchy.addAll(0, interfaces); + } + + if (Object.class.equals(curCls)) + { + curCls = null; + } + else + { + if (!interfaces.isEmpty()) + { + curCls = Object.class; + } + else + { + curCls = curCls.getSuperclass(); + } + } + } + + // interfaces don't explicitly contain Object in their hierarchy and some define Object-original methods too + if (!classHierarchy.contains(Object.class)) + { + classHierarchy.add(Object.class); + } + + // collect declared public methods (other than cls.getMethods we don't want overriden methods, only initial implementations) + final Map>>, Method> methodsByNameAndParameterTypes = new HashMap>>, Method>(); + while (!classHierarchy.isEmpty()) + { + curCls = classHierarchy.remove(0); + final Method[] declaredMethods = curCls.getDeclaredMethods(); + for (final Method declaredMethod : declaredMethods) + { + final String methodName = declaredMethod.getName(); + + if (Modifier.isPublic(declaredMethod.getModifiers()) && !Modifier.isStatic(declaredMethod.getModifiers())) + { + final Pair>> key = new Pair>>(methodName, Arrays.asList(declaredMethod + .getParameterTypes())); + if (!methodsByNameAndParameterTypes.containsKey(key)) + { + methodsByNameAndParameterTypes.put(key, declaredMethod); + } + } + } + } + + // filter and collect methods + final List documentableMethods = new ArrayList(); + + for (final Method method : methodsByNameAndParameterTypes.values()) + { + if (cls.equals(method.getDeclaringClass())) + { + // ignore potential initialization setters / methods + final String methodName = method.getName(); + if ((BaseProcessorExtension.class.isAssignableFrom(method.getDeclaringClass()) || WebScript.class.isAssignableFrom(method + .getDeclaringClass())) + && (INIT_METHOD_NAMES.contains(methodName) || methodName.matches("^[sg]et[A-Z].+Service$") || (methodName + .matches("^set[A-Z].+$") && method.getParameterTypes().length == 1))) + { + // skip + continue; + } + + documentableMethods.add(method); + } + } + + Collections.sort(documentableMethods, new Comparator() + { + + /** + * + * {@inheritDoc} + */ + @Override + public int compare(final Method a, final Method b) + { + final String aName = a.getName(); + final String bName = b.getName(); + + int result = aName.compareTo(bName); + if (result == 0) + { + final int aArgLength = a.getParameterTypes().length; + final int bArgLength = b.getParameterTypes().length; + + if (aArgLength != bArgLength) + { + result = aArgLength - bArgLength; + } + } + + return result; + } + }); + + return documentableMethods; + } + + protected Map buildClassMethodMemberDefinition(final Class cls, final Method method, final String commonPrefix, + final Collection> relatedClasses) + { + final Map memberDefinition; + + final Class realReturnType = method.getReturnType(); + Class effectiveReturnType = realReturnType; + + final Class[] realParameterTypes = method.getParameterTypes(); + + final String methodName = method.getName(); + final String simpleMethodPrefix = commonPrefix + "." + methodName; + final String methodPrefix = this.buildMethodPrefix(commonPrefix, realReturnType, realParameterTypes, methodName); + + String skip = this.properties.getProperty(methodPrefix + ".skip"); + if (skip == null || skip.isEmpty()) + { + skip = this.properties.getProperty(simpleMethodPrefix + ".skip"); + } + + if (skip == null || skip.isEmpty() || !Boolean.parseBoolean(skip)) + { + memberDefinition = new HashMap(); + effectiveReturnType = this.determineEffectiveType(realReturnType, effectiveReturnType, methodPrefix); + final Class returnType = this.determineType(effectiveReturnType, relatedClasses); + + final Class[] effectiveParameterTypes = new Class[realParameterTypes.length]; + final Class[] parameterTypes = new Class[realParameterTypes.length]; + System.arraycopy(realParameterTypes, 0, effectiveParameterTypes, 0, realParameterTypes.length); + + for (int idx = 0; idx < parameterTypes.length; idx++) + { + effectiveParameterTypes[idx] = this.determineEffectiveType(realParameterTypes[idx], effectiveParameterTypes[idx], + methodPrefix + ".arg" + idx); + parameterTypes[idx] = this.determineType(effectiveParameterTypes[idx], relatedClasses); + } + + memberDefinition.put("name", methodName); + + final String methodType = this.buildMethodTypeDescription(method, realReturnType, effectiveReturnType, returnType, + effectiveParameterTypes, parameterTypes, methodPrefix); + memberDefinition.put("type", methodType); + + // support I18n for any documentation + final String i18nKey = "javascript-console.tern." + methodPrefix + ".ternDoc"; + String ternDoc = I18NUtil.getMessage(i18nKey); + if (ternDoc == null || ternDoc.isEmpty() || ternDoc.equals(i18nKey)) + { + ternDoc = this.properties.getProperty(methodPrefix + ".ternDoc"); + } + if (ternDoc != null && !ternDoc.isEmpty()) + { + memberDefinition.put("doc", ternDoc); + } + } + else + { + memberDefinition = null; + } + + return memberDefinition; + } + + protected Map buildClassPropertyMemberDefinition(final Class cls, final Method getter, final String commonPrefix, + final String propertyName, final Collection> relatedClasses) + { + final Map memberDefinition; + + final Class realReturnType = getter.getReturnType(); + Class effectiveReturnType = realReturnType; + + final String propertyPrefix = commonPrefix + "." + propertyName; + + final String skip = this.properties.getProperty(propertyPrefix + ".skip"); + + if (skip == null || skip.isEmpty() || !Boolean.parseBoolean(skip)) + { + memberDefinition = new HashMap(); + effectiveReturnType = this.determineEffectiveType(realReturnType, effectiveReturnType, propertyPrefix); + final Class returnType = this.determineType(effectiveReturnType, relatedClasses); + + final String returnTypeClsName = returnType.getName(); + final String returnTypePrefix = "type." + returnTypeClsName; + + String returnTypeName = returnType.getSimpleName(); + final String propertyTypeTernName = this.properties.getProperty(propertyPrefix + ".typeTernName"); + if (propertyTypeTernName != null && !propertyTypeTernName.isEmpty()) + { + returnTypeName = propertyTypeTernName; + } + else + { + String returnTypeTernName = this.properties.getProperty(returnTypePrefix + ".returnTypeTernName"); + if (returnTypeTernName == null || returnTypeName.isEmpty()) + { + returnTypeTernName = this.properties.getProperty(returnTypePrefix + ".ternName"); + } + + if (returnTypeTernName != null && !returnTypeTernName.isEmpty()) + { + returnTypeName = returnTypeTernName; + } + } + + if (effectiveReturnType.isArray() && !(returnTypeName.startsWith("[") && returnTypeName.endsWith("["))) + { + returnTypeName = "[" + returnTypeName + "]"; + } + + memberDefinition.put("name", propertyName); + memberDefinition.put("type", returnTypeName); + + // support I18n for any documentation + final String i18nKey = "javascript-console.tern." + propertyPrefix + ".ternDoc"; + String ternDoc = I18NUtil.getMessage(i18nKey); + if (ternDoc == null || ternDoc.isEmpty() || ternDoc.equals(i18nKey)) + { + ternDoc = this.properties.getProperty(propertyPrefix + ".ternDoc"); + } + if (ternDoc != null && !ternDoc.isEmpty()) + { + memberDefinition.put("doc", ternDoc); + } + + boolean readOnly; + final String setterName = "set" + getter.getName().substring(3); + try + { + cls.getMethod(setterName, realReturnType); + readOnly = false; + } + catch (final NoSuchMethodException nsmex) + { + readOnly = true; + } + memberDefinition.put("readOnly", Boolean.valueOf(readOnly)); + } + else + { + memberDefinition = null; + } + + return memberDefinition; + } + + protected Class determineEffectiveType(final Class realType, final Class baseEffectiveReturnType, final String prefix) + { + Class effectiveType = baseEffectiveReturnType; + + // implicit conversion in RhinoScriptProcessor$RhinoWrapFactory + if (Map.class.isAssignableFrom(realType) && !(ScriptableHashMap.class.isAssignableFrom(realType))) + { + effectiveType = NativeMap.class; + } + + if (!(void.class.equals(realType) || Void.class.equals(realType))) + { + // due to bad return type in Java code we may need to specify overrides on a per-use-case level + final String typeClassName = this.properties.getProperty(prefix + ".typeClassName"); + if (typeClassName != null && !typeClassName.isEmpty()) + { + try + { + effectiveType = Class.forName(typeClassName); + } + catch (final ClassNotFoundException cnfex) + { + // TODO log + } + } + } + return effectiveType; + } + + protected Class determineType(final Class inType, final Collection> relatedClasses) + { + Class type = inType; + if (PRIMITIVE_NUMBER_CLASSES.contains(type) || Number.class.isAssignableFrom(type)) + { + type = Number.class; + } + else if (boolean.class.equals(type)) + { + type = Boolean.class; + } + else if (char.class.equals(type)) + { + type = Character.class; + } + else if (Date.class.isAssignableFrom(type)) + { + type = Date.class; + } + else if (CharSequence.class.equals(type)) + { + type = String.class; + } + else if (type.isArray()) + { + type = type.getComponentType(); + if (type.isArray()) + { + type = type.getComponentType(); + if (PRIMITIVE_NUMBER_CLASSES.contains(type) || Number.class.isAssignableFrom(type)) + { + type = Number.class; + } + else if (boolean.class.equals(type)) + { + type = Boolean.class; + } + else if (char.class.equals(type)) + { + type = Character.class; + } + else if (Date.class.isAssignableFrom(type)) + { + type = Date.class; + } + else if (CharSequence.class.equals(type)) + { + type = String.class; + } + else + { + relatedClasses.add(type); + } + } + else + { + if (PRIMITIVE_NUMBER_CLASSES.contains(type) || Number.class.isAssignableFrom(type)) + { + type = Number.class; + } + else if (boolean.class.equals(type)) + { + type = Boolean.class; + } + else if (char.class.equals(type)) + { + type = Character.class; + } + else if (Date.class.isAssignableFrom(type)) + { + type = Date.class; + } + else if (CharSequence.class.equals(type)) + { + type = String.class; + } + else + { + relatedClasses.add(type); + } + } + } + else if (Map.class.isAssignableFrom(type) && !Scriptable.class.isAssignableFrom(type)) + { + type = Map.class; + relatedClasses.add(type); + } + else if (List.class.isAssignableFrom(type)) + { + type = List.class; + relatedClasses.add(type); + } + else if (Set.class.isAssignableFrom(type)) + { + type = Set.class; + relatedClasses.add(type); + } + else if (!(Scriptable.class.equals(type) || Object.class.equals(type) || void.class.equals(type) || Void.class.equals(type) + || NativeMap.class.isAssignableFrom(type) + || org.springframework.extensions.webscripts.NativeMap.class.isAssignableFrom(type) + || ScriptableMap.class.isAssignableFrom(type) || ScriptableHashMap.class.isAssignableFrom(type) + || ScriptableQNameMap.class.isAssignableFrom(type) || ScriptableLinkedHashMap.class.isAssignableFrom(type))) + { + relatedClasses.add(type); + } + return type; + } + + protected String buildMethodPrefix(final String commonPrefix, final Class realReturnType, final Class[] realParameterTypes, + final String methodName) + { + final StringBuilder signatureKeyBuilder = new StringBuilder(); + if (!(void.class.equals(realReturnType) || Void.class.equals(realReturnType))) + { + signatureKeyBuilder.append(".out."); + signatureKeyBuilder.append(realReturnType.getSimpleName()); + } + if (realParameterTypes.length > 0) + { + signatureKeyBuilder.append(".in"); + for (final Class realParameterType : realParameterTypes) + { + signatureKeyBuilder.append("."); + signatureKeyBuilder.append(realParameterType.getSimpleName()); + } + } + final String methodPrefix = commonPrefix + "." + methodName + signatureKeyBuilder; + return methodPrefix; + } + + protected String buildMethodTypeDescription(final Method method, final Class realReturnType, final Class effectiveReturnType, + final Class returnType, final Class[] effectiveParameterTypes, final Class[] parameterTypes, final String methodPrefix) + { + final StringBuilder typeBuilder = new StringBuilder(); + typeBuilder.append("fn("); + if (parameterTypes.length > 0) + { + final String[] parameterNames = PARAMETER_NAME_DISCOVERER.getParameterNames(method); + + for (int idx = 0; idx < parameterTypes.length; idx++) + { + String parameterName = this.properties.getProperty(methodPrefix + ".arg" + idx + ".name"); + if ((parameterName == null || parameterName.isEmpty()) && parameterNames != null) + { + parameterName = parameterNames[idx]; + } + if (parameterName == null || parameterName.isEmpty()) + { + parameterName = "arg" + idx; + } + + String typeName = parameterTypes[idx].getSimpleName(); + if (Object.class.equals(parameterTypes[idx])) + { + typeName = "+" + typeName; + } + // Rhino transparently handles native to Java type conversions for core types + else if (Number.class.equals(parameterTypes[idx])) + { + typeName = "number"; + } + else if (CharSequence.class.isAssignableFrom(parameterTypes[idx])) + { + typeName = "string"; + } + else if (Boolean.class.equals(parameterTypes[idx])) + { + typeName = "bool"; + } + + final String parameterTypeTernName = this.properties.getProperty(methodPrefix + ".arg" + idx + ".typeTernName"); + if (parameterTypeTernName != null && !parameterTypeTernName.isEmpty()) + { + typeName = parameterTypeTernName; + } + else + { + final String typeClsName = parameterTypes[idx].getName(); + final String typePrefix = "type." + typeClsName; + String typeTernName = this.properties.getProperty(typePrefix + ".paramTypeTernName"); + if (typeTernName == null || typeTernName.isEmpty()) + { + typeTernName = this.properties.getProperty(typePrefix + ".typeTernName"); + } + if (typeTernName != null && !typeTernName.isEmpty()) + { + typeName = typeTernName; + } + } + + if (effectiveParameterTypes[idx].isArray() && !(typeName.startsWith("[") && typeName.endsWith("["))) + { + typeName = "[" + typeName + "]"; + } + + if (idx != 0) + { + typeBuilder.append(", "); + } + + typeBuilder.append(parameterName); + typeBuilder.append(": "); + typeBuilder.append(typeName); + } + } + typeBuilder.append(")"); + if (!(void.class.equals(realReturnType) || Void.class.equals(realReturnType))) + { + typeBuilder.append(" -> "); + + String returnTypeName = returnType.getSimpleName(); + if (Object.class.equals(returnType)) + { + returnTypeName = "+" + returnTypeName; + } + + final String propertyTypeTernName = this.properties.getProperty(methodPrefix + ".returnTypeTernName"); + if (propertyTypeTernName != null && !propertyTypeTernName.isEmpty()) + { + returnTypeName = propertyTypeTernName; + } + else + { + final String returnTypeClsName = returnType.getName(); + final String returnTypePrefix = "type." + returnTypeClsName; + String returnTypeTernName = this.properties.getProperty(returnTypePrefix + ".returnTypeTernName"); + if (returnTypeTernName == null || returnTypeName.isEmpty()) + { + returnTypeTernName = this.properties.getProperty(returnTypePrefix + ".ternName"); + } + if (returnTypeTernName != null && !returnTypeTernName.isEmpty()) + { + returnTypeName = returnTypeTernName; + } + } + + if (effectiveReturnType.isArray() && !(returnTypeName.startsWith("[") && returnTypeName.endsWith("["))) + { + returnTypeName = "[" + returnTypeName + "]"; + } + + typeBuilder.append(returnTypeName); + } + final String methodType = typeBuilder.toString(); + return methodType; + } + + protected Map buildScriptAPIModel() + { + final String userName = AuthenticationUtil.getFullyAuthenticatedUser(); + final NodeRef person = this.personService.getPerson(userName); + // the specific values of companyHome, userHome, script, document and space are irrelevant for type analysis + final Map defaultModel = this.scriptService.buildDefaultModel(person, person, person, person, person, person); + + final Collection processorExtensions = this.scriptProcessor.getProcessorExtensions(); + for (final ProcessorExtension extension : processorExtensions) + { + if (!defaultModel.containsKey(extension.getExtensionName())) + { + defaultModel.put(extension.getExtensionName(), extension); + } + } + + return defaultModel; + } + + /** + * Prepares the definitions of node and task properties based on the {@link DictionaryService dictionary} active in the current context + * (authentication / tenant) + * + * @param model + * the model into which to insert the definitions + */ + protected void preparePropertyDefinitions(final Map model) + { + final Collection taskTypes = this.dictionaryService.getSubTypes(WorkflowModel.TYPE_TASK, true); + final Collection taskAspects = this.collectTaskAspects(taskTypes); + + model.put("taskProperties", this.buildPropertyDefinitions(taskTypes, taskAspects)); + + // though task types could technically be used for nodes (task derives from cm:content) they rarely are + final Collection nodeTypes = new HashSet(this.dictionaryService.getAllTypes()); + nodeTypes.removeAll(nodeTypes); + final Collection nodeAspects = this.dictionaryService.getAllAspects(); + + model.put("nodeProperties", this.buildPropertyDefinitions(nodeTypes, nodeAspects)); + } + + protected List buildPropertyDefinitions(final Collection types, final Collection aspects) + { + final List propertyDefinitions = new ArrayList(); + + final Collection allClasses = new HashSet(); + allClasses.addAll(types); + allClasses.addAll(aspects); + + for (final QName cls : allClasses) + { + propertyDefinitions.addAll(this.buildPropertyDefinitions(cls)); + } + + return propertyDefinitions; + } + + protected List buildPropertyDefinitions(final QName cls) + { + final ClassDefinition classDefinition = this.dictionaryService.getClass(cls); + + final Map properties = classDefinition.getProperties(); + final Map parentProperties = classDefinition.getParentName() != null ? classDefinition + .getParentClassDefinition().getProperties() : Collections. emptyMap(); + + final List propertyDefinitions = new ArrayList(); + + for (final Entry propertyEntry : properties.entrySet()) + { + if (!parentProperties.containsKey(propertyEntry.getKey())) + { + propertyDefinitions.add(propertyEntry.getValue()); + } + } + + return propertyDefinitions; + } + + protected Collection collectTaskAspects(final Collection taskTypes) + { + final Collection taskAspects = new HashSet(); + for (final QName taskType : taskTypes) + { + final TypeDefinition type = this.dictionaryService.getType(taskType); + taskAspects.addAll(type.getDefaultAspectNames()); + } + + boolean taskAspectsChanged = true; + while (taskAspectsChanged) + { + taskAspectsChanged = false; + for (final QName taskAspect : taskAspects) + { + final AspectDefinition aspect = this.dictionaryService.getAspect(taskAspect); + taskAspectsChanged = taskAspects.addAll(aspect.getDefaultAspectNames()) || taskAspectsChanged; + if (aspect.getParentName() != null) + { + taskAspectsChanged = taskAspects.add(aspect.getParentName()) || taskAspectsChanged; + } + } + } + + return taskAspects; + } +} \ No newline at end of file diff --git a/repository/src/main/resources/alfresco/module/ootbee-support-tools-repo/jsconsole-tern.properties b/repository/src/main/resources/alfresco/module/ootbee-support-tools-repo/jsconsole-tern.properties new file mode 100644 index 0000000..b6e765d --- /dev/null +++ b/repository/src/main/resources/alfresco/module/ootbee-support-tools-repo/jsconsole-tern.properties @@ -0,0 +1,145 @@ +global.requestbody.typeClassName=org.alfresco.util.Content +global.webscript.typeClassName=org.springframework.extensions.webscripts.Description +global.config.typeClassName=org.springframework.extensions.webscripts.ScriptConfigModel +global.url.typeClassName=org.springframework.extensions.webscripts.URLModel + +# Scriptable JMX API is just too nasty +global.jmx.skip=true + +type.java.lang.String.ternName=JavaString +type.java.lang.Character.ternName=JavaCharacter +type.java.lang.Number.ternName=JavaNumber +type.java.lang.Boolean.ternName=JavaBoolean +type.java.util.Date.ternName=JavaDate +type.java.util.Map.ternName=JavaMap +type.java.util.List.ternName=JavaList +type.java.util.Set.ternName=JavaSet +type.java.util.Collection.ternName=JavaCollection + +# anything that isn't specified to a type better than Object or Scriptable is considered unknown +type.java.lang.Object.paramTypeTernName=? +type.org.mozilla.javascript.Scriptable.paramTypeTernName=? +type.org.mozilla.javascript.ScriptableObject.paramTypeTernName=? +type.java.lang.Object.returnTypeTernName=? +type.org.mozilla.javascript.Scriptable.returnTypeTernName=? +type.org.mozilla.javascript.ScriptableObject.returnTypeTernName=? + +# all the custom native-like maps should be considered native objects by default +type.org.alfresco.repo.jscript.NativeMap.typeTernName=object +type.org.springframework.extensions.webscripts.NativeMap.typeTernName=object +type.org.alfresco.repo.jscript.ScriptableHashMap.returnTypeTernName=object +type.org.alfresco.repo.jscript.ScriptableQNameMap.returnTypeTernName=object +type.org.alfresco.repo.jscript.ScriptAction$ScriptableParameterMap.returnTypeTernName=object +type.org.springframework.extensions.webscripts.ScriptableLinkedHashMap.returnTypeTernName=object +type.org.springframework.extensions.webscripts.ScriptableWrappedMap.returnTypeTernName=object + +type.java.lang.Object.class.skip=true +type.java.lang.Object.notify.skip=true +type.java.lang.Object.notifyAll.skip=true +type.java.lang.Object.wait.skip=true +type.java.lang.Object.toString.skip=true +type.java.lang.Enum.declaringClass.skip=true +type.java.util.Date.toInstant.skip=true + +type.org.springframework.extensions.webscripts.WebScript.execute.skip=true +type.org.springframework.extensions.webscripts.ScriptableUtils.parseXMLNodeModel.skip=true +type.org.springframework.extensions.webscripts.AbstractBaseDescription.parse.skip=true +type.org.springframework.extensions.webscripts.AbstractBaseDescription.validateRootElement.skip=true + +# in case by any chance a property / method leaks class-related types, we don't want it to pull other core Java stuff in +type.java.lang.Class.nameOnly=true +type.java.lang.ClassLoader.nameOnly=true +type.java.lang.reflect.Constructor.nameOnly=true +type.java.lang.reflect.Method.nameOnly=true +type.java.lang.reflect.Field.nameOnly=true +type.java.lang.reflect.TypeVariable.nameOnly=true +type.java.lang.annotation.Annotation.nameOnly=true + +# avoid leaking all Java services and classes only transitively associated due to bad API design +type.org.alfresco.service.ServiceRegistry.nameOnly=true +type.org.activiti.engine.delegate.VariableScope.nameOnly=true + +type.org.alfresco.repo.jscript.ScriptNode.properties.typeTernName=NodeProperties +type.org.alfresco.repo.jscript.Person.getImmutableProperties.in.String.out.ScriptableHashMap.typeTernName=NodeProperties +type.org.alfresco.repo.workflow.jscript.JscriptWorkflowTask.properties.typeTernName=TaskProperties +type.org.alfresco.repo.jscript.Search.queryResultSet.in.Object.out.Scriptable.typeTernName=SearchResultSetMeta + +type.java.lang.String.getChars.skip=true +type.java.lang.String.getBytes.skip=true +type.java.lang.String.contentEquals.skip=true +type.java.lang.String.bytes.skip=true +type.java.lang.String.subSequence.skip=true + +type.java.io.Reader.read.out.int.in.CharBuffer.skip=true + +type.org.alfresco.repo.jscript.ScriptNode.newInstance.skip=true + +type.org.alfresco.enterprise.repo.management.script.ScriptMBean.setMBeanServer.skip=true +type.org.alfresco.enterprise.repo.management.script.ScriptMBean.setJmxValueConversionChain.skip=true + +# some methods / properties use a generic Scriptable type for representing native arrays +type.org.alfresco.repo.jscript.ScriptNode.children.typeClassName=[Lorg.alfresco.repo.jscript.ScriptNode; +type.org.alfresco.repo.jscript.ScriptNode.childFileFolders.out.Scriptable.typeClassName=[Lorg.alfresco.repo.jscript.ScriptNode; +type.org.alfresco.repo.jscript.ScriptNode.childFileFolders.in.boolean.boolean.out.Scriptable.typeClassName=[Lorg.alfresco.repo.jscript.ScriptNode; +type.org.alfresco.repo.jscript.ScriptNode.childFileFolders.in.boolean.boolean.Object.out.Scriptable.typeClassName=[Lorg.alfresco.repo.jscript.ScriptNode; +type.org.alfresco.repo.jscript.ScriptNode.childFileFolders.in.boolean.boolean.Object.int.out.Scriptable.typeClassName=[Lorg.alfresco.repo.jscript.ScriptNode; +type.org.alfresco.repo.jscript.ScriptNode.childFileFolders.in.boolean.boolean.Object.int.int.int.String.Boolean.String.out.Scriptable.typeClassName=[Lorg.alfresco.repo.jscript.ScriptNode; +type.org.alfresco.repo.jscript.ScriptNode.childrenByXPath.in.String.out.Scriptable.typeClassName=[Lorg.alfresco.repo.jscript.ScriptNode; +type.org.alfresco.repo.jscript.ScriptNode.getChildAssocsByType.in.String.out.Scriptable.typeClassName=[Lorg.alfresco.repo.jscript.ScriptNode; +type.org.alfresco.repo.jscript.ScriptNode.getPropertyNames.in.boolean.out.Scriptable.typeClassName=[Ljava.lang.String; +type.org.alfresco.repo.jscript.ScriptNode.typePropertyNames.typeClassName=[Ljava.lang.String; +type.org.alfresco.repo.jscript.ScriptNode.getTypePropertyNames.in.boolean.out.Scriptable.typeClassName=[Ljava.lang.String; +type.org.alfresco.repo.jscript.Classification.getAllCategoryNodes.in.String.out.Scriptable.typeClassName=[Lorg.alfresco.repo.jscript.CategoryNode; +type.org.alfresco.repo.jscript.Classification.getCategoryUsage.in.String.int.out.Scriptable.typeClassName=[Lorg.alfresco.repo.jscript.Classification$Tag; +type.org.alfresco.repo.jscript.Classification.getRootCategories.in.String.out.Scriptable.typeClassName=[Lorg.alfresco.repo.jscript.CategoryNode; +type.org.alfresco.repo.jscript.Search.luceneSearch.in.String.out.Scriptable.typeClassName=[Lorg.alfresco.repo.jscript.ScriptNode; +type.org.alfresco.repo.jscript.Search.luceneSearch.in.String.String.out.Scriptable.typeClassName=[Lorg.alfresco.repo.jscript.ScriptNode; +type.org.alfresco.repo.jscript.Search.luceneSearch.in.String.String.boolean.out.Scriptable.typeClassName=[Lorg.alfresco.repo.jscript.ScriptNode; +type.org.alfresco.repo.jscript.Search.luceneSearch.in.String.String.boolean.int.out.Scriptable.typeClassName=[Lorg.alfresco.repo.jscript.ScriptNode; +type.org.alfresco.repo.jscript.Search.luceneSearch.in.String.String.String.boolean.out.Scriptable.typeClassName=[Lorg.alfresco.repo.jscript.ScriptNode; +type.org.alfresco.repo.jscript.Search.luceneSearch.in.String.String.String.boolean.int.out.Scriptable.typeClassName=[Lorg.alfresco.repo.jscript.ScriptNode; +type.org.alfresco.repo.jscript.Search.query.in.Object.out.Scriptable.typeClassName=[Lorg.alfresco.repo.jscript.ScriptNode; +type.org.alfresco.repo.jscript.Search.savedSearch.in.ScriptNode.out.Scriptable.typeClassName=[Lorg.alfresco.repo.jscript.ScriptNode; +type.org.alfresco.repo.jscript.Search.savedSearch.in.String.out.Scriptable.typeClassName=[Lorg.alfresco.repo.jscript.ScriptNode; +type.org.alfresco.repo.jscript.Search.selectNodes.in.String.out.Scriptable.typeClassName=[Lorg.alfresco.repo.jscript.ScriptNode; +type.org.alfresco.repo.jscript.Search.selectNodes.in.String.String.out.Scriptable.typeClassName=[Lorg.alfresco.repo.jscript.ScriptNode; +type.org.alfresco.repo.jscript.Search.xpathSearch.in.String.out.Scriptable.typeClassName=[Lorg.alfresco.repo.jscript.ScriptNode; +type.org.alfresco.repo.jscript.Search.xpathSearch.in.String.String.out.Scriptable.typeClassName=[Lorg.alfresco.repo.jscript.ScriptNode; +type.org.alfresco.repo.jscript.People.getContainerGroups.in.ScriptNode.out.Scriptable.typeClassName=[Lorg.alfresco.repo.jscript.ScriptNode; +type.org.alfresco.repo.jscript.People.getMembers.in.ScriptNode.out.Scriptable.typeClassName=[Lorg.alfresco.repo.jscript.ScriptNode; +type.org.alfresco.repo.jscript.People.getMembers.in.ScriptNode.boolean.out.Scriptable.typeClassName=[Lorg.alfresco.repo.jscript.ScriptNode; +# don't know why, but getPeople returns array of NodeRef instead of ScriptNode +type.org.alfresco.repo.jscript.People.getPeople.in.String.out.Scriptable.typeClassName=[Lorg.alfresco.service.cmr.repository.NodeRef; +type.org.alfresco.repo.jscript.People.getPeople.in.String.int.out.Scriptable.typeClassName=[Lorg.alfresco.service.cmr.repository.NodeRef; +type.org.alfresco.repo.jscript.People.getPeople.in.String.int.String.boolean.out.Scriptable.typeClassName=[Lorg.alfresco.service.cmr.repository.NodeRef; +type.org.alfresco.repo.jscript.People.getPeoplePaging.in.String.ScriptPagingDetails.String.Boolean.out.Scriptable.typeClassName=[Lorg.alfresco.service.cmr.repository.NodeRef; +type.org.alfresco.repo.workflow.jscript.WorkflowManager.assignedTasks.typeClassName=[Lorg.alfresco.repo.workflow.jscript.JscriptWorkflowTask; +type.org.alfresco.repo.workflow.jscript.WorkflowManager.completedTasks.typeClassName=[Lorg.alfresco.repo.workflow.jscript.JscriptWorkflowTask; +type.org.alfresco.repo.workflow.jscript.WorkflowManager.latestDefinitions.typeClassName=[Lorg.alfresco.repo.workflow.jscript.JscriptWorkflowDefinition; +type.org.alfresco.repo.workflow.jscript.WorkflowManager.allDefinitions.typeClassName=[Lorg.alfresco.repo.workflow.jscript.JscriptWorkflowDefinition; +type.org.alfresco.repo.workflow.jscript.WorkflowManager.getPooledTasks.out.Scriptable.in.String.typeClassName=[Lorg.alfresco.repo.workflow.jscript.JscriptWorkflowTask; +type.org.alfresco.repo.workflow.jscript.JscriptWorkflowDefinition.activeInstances.typeClassName=[Lorg.alfresco.repo.workflow.jscript.JscriptWorkflowInstance; +type.org.alfresco.repo.workflow.jscript.JscriptWorkflowInstance.paths.typeClassName=[Lorg.alfresco.repo.workflow.jscript.JscriptWorkflowPath; +type.org.alfresco.repo.workflow.jscript.JscriptWorkflowPath.tasks.typeClassName=[Lorg.alfresco.repo.workflow.jscript.JscriptWorkflowTask; + +# some methods expect native-like objects +# childFileFolders supports JavaString, native String and native array of native String - we opt for the latter for documentation +type.org.alfresco.repo.jscript.ScriptNode.childFileFolders.in.boolean.boolean.Object.out.Scriptable.arg2.typeTernName=[string] +type.org.alfresco.repo.jscript.ScriptNode.childFileFolders.in.boolean.boolean.Object.int.out.Scriptable.arg2.typeTernName=[string] +type.org.alfresco.repo.jscript.ScriptNode.childFileFolders.in.boolean.boolean.Object.int.int.int.String.Boolean.String.out.Scriptable.arg2.typeTernName=[string] + +type.org.springframework.extensions.webscripts.ConfigModel.script.typeClassName=java.lang.String + +type.org.springframework.extensions.webscripts.ScriptMessage.get.in.String.Scriptable.out.String.arg1.typeClassName=[Ljava.lang.String; +type.org.springframework.extensions.webscripts.ScriptMessage.get.in.String.Scriptable.out.String.arg1.typeTernName=[string] + +# default doc without I18n +type.java.lang.String.ternDoc=Java representation of a string value not wrapped into a native Rhino / JS string +type.java.lang.Character.ternDoc=Java representation of a single-character value not wrapped into a native Rhino / JS string +type.java.lang.Number.ternDoc=Java representation of a numeric value not wrapped into a native Rhino / JS number +type.java.lang.Boolean.ternDoc=Java representation of a boolean value not wrapped into a native Rhino / JS boolean +type.java.util.Date.ternDoc=Java representation of a Date value not wrapped into a native Rhino / JS Date +type.java.util.Map.ternDoc=A generic Java map structure without any scripting support +type.java.util.List.ternDoc=A generic Java list structure without any scripting support +type.java.util.Set.ternDoc=A generic Java set structure without any scripting support +type.java.util.Collection.ternDoc=A generic Java collection structure without any scripting support \ No newline at end of file diff --git a/repository/src/main/resources/alfresco/module/ootbee-support-tools-repo/module-context.xml b/repository/src/main/resources/alfresco/module/ootbee-support-tools-repo/module-context.xml index ca8b402..d790451 100644 --- a/repository/src/main/resources/alfresco/module/ootbee-support-tools-repo/module-context.xml +++ b/repository/src/main/resources/alfresco/module/ootbee-support-tools-repo/module-context.xml @@ -111,4 +111,27 @@ + + + + + + + + + + + + + classpath*:alfresco/module/*/jsconsole-tern.properties + classpath*:alfresco/extension/jsconsole-tern.properties + + + + + + + + + \ No newline at end of file diff --git a/repository/src/main/resources/alfresco/templates/webscripts/org/orderofthebee/support-tools/jsconsole/tern/alfresco-script-api.get.desc.xml b/repository/src/main/resources/alfresco/templates/webscripts/org/orderofthebee/support-tools/jsconsole/tern/alfresco-script-api.get.desc.xml new file mode 100644 index 0000000..3c85c5d --- /dev/null +++ b/repository/src/main/resources/alfresco/templates/webscripts/org/orderofthebee/support-tools/jsconsole/tern/alfresco-script-api.get.desc.xml @@ -0,0 +1,9 @@ + + + JavaScript Console - Tern Type Definitions for Alfresco Script API + /ootbee/jsconsole/tern-definitions/alfresco-script-api + + user + required + OOTBee Support Tools + \ No newline at end of file diff --git a/repository/src/main/resources/alfresco/templates/webscripts/org/orderofthebee/support-tools/jsconsole/tern/alfresco-script-api.get.js b/repository/src/main/resources/alfresco/templates/webscripts/org/orderofthebee/support-tools/jsconsole/tern/alfresco-script-api.get.js new file mode 100644 index 0000000..b41326a --- /dev/null +++ b/repository/src/main/resources/alfresco/templates/webscripts/org/orderofthebee/support-tools/jsconsole/tern/alfresco-script-api.get.js @@ -0,0 +1,22 @@ +/** + * Copyright (C) 2016 - 2022 Order of the Bee + * + * This file is part of OOTBee Support Tools + * + * OOTBee Support Tools 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 3 of the License, + * or (at your option) any later version. + * + * OOTBee Support Tools 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 OOTBee Support Tools. If not, see + * . + * + */ + +// NO-OP - only here so Java web script is able to resolve a ScriptDetails \ No newline at end of file diff --git a/repository/src/main/resources/alfresco/templates/webscripts/org/orderofthebee/support-tools/jsconsole/tern/alfresco-script-api.get.json.ftl b/repository/src/main/resources/alfresco/templates/webscripts/org/orderofthebee/support-tools/jsconsole/tern/alfresco-script-api.get.json.ftl new file mode 100644 index 0000000..bfdb87e --- /dev/null +++ b/repository/src/main/resources/alfresco/templates/webscripts/org/orderofthebee/support-tools/jsconsole/tern/alfresco-script-api.get.json.ftl @@ -0,0 +1,136 @@ +<#escape x as jsonUtils.encodeJSONString(x)>{ + "typeDefinitions" : [ + { + "!name" : "alfresco-script-api", + "!define" : { + <@renderJavaTypes scriptAPIJavaTypeDefinitions/><#if scriptAPIJavaTypeDefinitions?? && scriptAPIJavaTypeDefinitions?size > 0>, + <@renderPropertyTypes />, + <#-- we fake this type for Search.queryResultSet (would just be plain object else) --> + "SearchResultSetMeta" : { + "nodes" : "[ScriptNode]", + "meta" : "JavaMap" + <#-- we could add all the metadata collected in latest Alfresco version as sub-structure but then it might not match Alfresco version in use --> + } + }<#if scriptAPIGlobalDefinitions?? && scriptAPIGlobalDefinitions?size > 0>, + <@renderGlobals scriptAPIGlobalDefinitions /> + }, + { + "!name" : "alfresco-web-script-api", + "!define" : { + <@renderJavaTypes webScriptAPIJavaTypeDefinitions/> + }<#if webScriptAPIGlobalDefinitions?? && webScriptAPIGlobalDefinitions?size > 0>, + <@renderGlobals webScriptAPIGlobalDefinitions /> + } + ] +} + +<#macro renderGlobals globals><#compress><#escape x as jsonUtils.encodeJSONString(x)> +<#list globals as globalDefinition> + "${globalDefinition.name}" : { + <#if globalDefinition.doc??> + "!doc" : "${globalDefinition.doc}"<#if globalDefinition.type != 'object'>, + + <#if globalDefinition.type != 'object'> + "!type" : "${globalDefinition.type}" + + }<#if globalDefinition_has_next>, + + + +<#macro renderJavaTypes javaTypeDefinitions><#compress><#escape x as jsonUtils.encodeJSONString(x)> +<#list javaTypeDefinitions as javaTypeDefinition> + "${javaTypeDefinition.name}" : { + <#if javaTypeDefinition.doc??> + "!doc" : "${javaTypeDefinition.doc}"<#if javaTypeDefinition.url?? || javaTypeDefinition.prototype?? || (javaTypeDefinition.members?? && javaTypeDefinition.members?size > 0)>, + + <#if javaTypeDefinition.url??> + "!url" : "${javaTypeDefinition.url}"<#if javaTypeDefinition.prototype?? || (javaTypeDefinition.members?? && javaTypeDefinition.members?size > 0)>, + + <#if javaTypeDefinition.prototype??> + "!proto" : "${javaTypeDefinition.prototype}"<#if javaTypeDefinition.members?? && javaTypeDefinition.members?size > 0>, + + <#if javaTypeDefinition.members??> + <#list javaTypeDefinition.members as memberDefinition> + "${memberDefinition.name}" : { + <#if memberDefinition.originalName??> + "!original" : "${memberDefinition.originalName}", + + <#if memberDefinition.doc??> + "!doc" : "<#if memberDefinition.readOnly??>${memberDefinition.readOnly?string('(read-only)', '(read-write)')} ${memberDefinition.doc}", + <#elseif memberDefinition.readOnly??> + "!doc" : "${memberDefinition.readOnly?string('(read-only)', '(read-write)')}", + + <#if memberDefinition.url??> + "!url" : "${memberDefinition.url}", + + "!type" : "${memberDefinition.type}" + }<#if memberDefinition_has_next>, + + + }<#if javaTypeDefinition_has_next>, + + + +<#macro renderPropertyTypes><#compress><#escape x as jsonUtils.encodeJSONString(x)> +"TaskProperties" : { + "!doc" : "The virtual type for task property maps. No global object by this type exists - it is only ever returned from Alfresco Script APIs" + <#list taskProperties as taskProperty>, + <@renderProperty taskProperty /> + +}, +"NodeProperties" : { + "!doc" : "The virtual type for node property maps. No global object by this type exists - it is only ever returned from Alfresco Script APIs" + <#list nodeProperties as nodeProperty>, + <@renderProperty nodeProperty /> + +} + + +<#macro renderProperty property><#compress><#escape x as jsonUtils.encodeJSONString(x)> +<#assign propertyName = shortQName(property.name) /> +"${propertyName}" : { + <@renderPropertyType property />, + "!doc" : "${(property.description!property.title)!propertyName}" +}<#if propertyName?starts_with('cm:')>, +"${propertyName?substring(3)}" : { + <@renderPropertyType property />, + "!doc" : "${(property.description!property.title)!propertyName}" +} + + + +<#macro renderPropertyType property><#compress><#escape x as jsonUtils.encodeJSONString(x)> +"!type": <#switch shortQName(property.dataType.name)> + <#case "d:text"> + <#case "d:mltext"> + <#-- despite claims in ValueConverter comments, Rhino does not automatically wrap Java String to native string --> + "<#if property.multiValued>[JavaString<#if property.multiValued>]" + <#break> + <#case "d:boolean"> + <#-- despite claims in ValueConverter comments, Rhino does not automatically wrap Java Boolean to native boolean --> + "<#if property.multiValued>[JavaBoolean<#if property.multiValued>]" + <#break> + <#case "d:noderef"> + "<#if property.multiValued>[ScriptNode<#if property.multiValued>]" + <#break> + <#case "d:category"> + "<#if property.multiValued>[CategoryNode<#if property.multiValued>]" + <#break> + <#case "d:int"> + <#case "d:long"> + <#case "d:float"> + <#case "d:double"> + <#-- despite claims in ValueConverter comments, Rhino does not automatically wrap Java Number to native number --> + "<#if property.multiValued>[JavaNumber<#if property.multiValued>]" + <#break> + <#case "d:content"> + "<#if property.multiValued>[ScriptContentData<#if property.multiValued>]" + <#break> + <#case "d:date"> + <#case "d:datetime"> + "<#if property.multiValued>[+Date<#if property.multiValued>]" + <#break> + <#default> + "<#if property.multiValued>[?<#if property.multiValued>]" + + \ No newline at end of file diff --git a/share/src/main/resources/META-INF/resources/components/ootbee-support-tools/codemirror/addon/tern/defs/alfresco-json-dynamic.js b/share/src/main/resources/META-INF/resources/components/ootbee-support-tools/codemirror/addon/tern/defs/alfresco-json-dynamic.js new file mode 100644 index 0000000..8d3fa1a --- /dev/null +++ b/share/src/main/resources/META-INF/resources/components/ootbee-support-tools/codemirror/addon/tern/defs/alfresco-json-dynamic.js @@ -0,0 +1,19 @@ +(function() { + Alfresco.util.Ajax.jsonGet({ + url : Alfresco.constants.PROXY_URI + "ootbee/jsconsole/tern-definitions/alfresco-script-api", + successCallback : { + fn : function(response) { + if (response.json != undefined && response.json.typeDefinitions != undefined) { + CodeMirror.tern.addDef(response.json.typeDefinitions[0]); + CodeMirror.tern.addDef(response.json.typeDefinitions[1]); + } + } + }, + failureCallback : { + fn : function(response) { + alert("Can not load tern definitions"); + } + } + }); + +})(); diff --git a/share/src/main/resources/alfresco/site-webscripts/org/orderofthebee/support-tools/console/support-tools/ootbee-jsconsole.get.head.ftl b/share/src/main/resources/alfresco/site-webscripts/org/orderofthebee/support-tools/console/support-tools/ootbee-jsconsole.get.head.ftl index 35806d4..efb65cc 100644 --- a/share/src/main/resources/alfresco/site-webscripts/org/orderofthebee/support-tools/console/support-tools/ootbee-jsconsole.get.head.ftl +++ b/share/src/main/resources/alfresco/site-webscripts/org/orderofthebee/support-tools/console/support-tools/ootbee-jsconsole.get.head.ftl @@ -129,6 +129,8 @@ addon. <@script type="text/javascript" src="${page.url.context}/res/components/ootbee-support-tools/codemirror/addon/tern/defs/alfresco-webscripts-tern.js"> <@script type="text/javascript" src="${page.url.context}/res/components/ootbee-support-tools/codemirror/addon/tern/defs/alfresco-batchprocessing-tern.js"> + +<@script type="text/javascript" src="${page.url.context}/res/components/ootbee-support-tools/codemirror/addon/tern/defs/alfresco-json-dynamic.js"> <@script type="text/javascript" src="${page.url.context}/res/components/ootbee-support-tools/codemirror-ui/js/codemirror-ui.js">