/* * See the NOTICE file distributed with this work for additional * information regarding copyright ownership. * * This 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 software 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 software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package org.xwiki.model.script; import java.util.Arrays; import java.util.List; import java.util.Locale; import javax.inject.Inject; import javax.inject.Named; import javax.inject.Singleton; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.xwiki.component.annotation.Component; import org.xwiki.component.manager.ComponentLookupException; import org.xwiki.component.manager.ComponentManager; import org.xwiki.model.EntityType; import org.xwiki.model.reference.AttachmentReference; import org.xwiki.model.reference.ClassPropertyReference; import org.xwiki.model.reference.DocumentReference; import org.xwiki.model.reference.DocumentReferenceResolver; import org.xwiki.model.reference.EntityReference; import org.xwiki.model.reference.EntityReferenceProvider; import org.xwiki.model.reference.EntityReferenceResolver; import org.xwiki.model.reference.EntityReferenceSerializer; import org.xwiki.model.reference.EntityReferenceTree; import org.xwiki.model.reference.EntityReferenceValueProvider; import org.xwiki.model.reference.ObjectPropertyReference; import org.xwiki.model.reference.ObjectReference; import org.xwiki.model.reference.SpaceReference; import org.xwiki.model.reference.WikiReference; import org.xwiki.script.service.ScriptService; /** * Provides Model-specific Scripting APIs. * * @version $Id: 8afe9c0ca44c96ffab4275a932e671889deb463c $ * @since 2.3M1 */ @Component @Named("model") @Singleton public class ModelScriptService implements ScriptService { /** * The default hint used when resolving references. */ private static final String DEFAULT_RESOLVER_HINT = "current"; /** * The default hint used when serializing references. */ private static final String DEFAULT_SERIALIZER_HINT = "compact"; /** * The object used to log messages. */ @Inject private Logger logger; /** * Used to dynamically look up component implementations based on a given hint. */ @Inject private ComponentManager componentManager; @Inject private EntityReferenceSerializer<String> defaultSerializer; /** * Create a Document Reference from a passed wiki, space and page names, which can be empty strings or {@code null} * in which case they are resolved using the {@value #DEFAULT_RESOLVER_HINT} resolver. * * @param wiki the wiki reference name to use (can be empty or null) * @param space the space reference name to use (can be empty or null) * @param page the page reference name to use (can be empty or null) * @return the typed Document Reference object or null if no Resolver with the passed hint could be found * @since 2.3M2 */ public DocumentReference createDocumentReference(String wiki, String space, String page) { return createDocumentReference(wiki, space, page, DEFAULT_RESOLVER_HINT); } /** * Create a Document Reference from a passed wiki, list of spaces and page names, which can be empty strings or * {@code null} in which case they are resolved using the {@value #DEFAULT_RESOLVER_HINT} resolver. * * @param wiki the wiki reference name to use (can be empty or null) * @param spaces the list of spaces name to use (can be empty or null) * @param page the page reference name to use (can be empty or null) * @return the typed Document Reference object or null if no Resolver with the passed hint could be found * @since 7.2M2 */ public DocumentReference createDocumentReference(String wiki, List<String> spaces, String page) { return createDocumentReference(wiki, spaces, page, DEFAULT_RESOLVER_HINT); } /** * Create a new reference with the passed {@link Locale}. * * @param reference the reference (with or without locale) * @param locale the locale of the new reference * @return the typed Document Reference object * @since 5.4RC1 */ public DocumentReference createDocumentReference(DocumentReference reference, Locale locale) { return new DocumentReference(reference, locale); } /** * Creates a new {@link DocumentReference} from a given page name and the reference of the parent space. * * @param pageName the page name * @param parent the parent space reference * @return the typed {@link DocumentReference} object * @since 7.3M2 */ public DocumentReference createDocumentReference(String pageName, SpaceReference parent) { return new DocumentReference(pageName, parent); } /** * Create a Document Reference from a passed wiki, space and page names, which can be empty strings or null in which * case they are resolved against the Resolver having the hint passed as parameter. Valid hints are for example * "default", "current", "currentmixed". * * @param wiki the wiki reference name to use (can be empty or null) * @param space the space reference name to use (can be empty or null) * @param page the page reference name to use (can be empty or null) * @param hint the hint of the Resolver to use in case any parameter is empty or null * @return the typed Document Reference object or null if no Resolver with the passed hint could be found */ public DocumentReference createDocumentReference(String wiki, String space, String page, String hint) { return createDocumentReference(wiki, StringUtils.isEmpty(space) ? null : Arrays.asList(space), page, hint); } /** * Create a Document Reference from a passed wiki, list of spaces and page names, which can be empty strings or null * in which case they are resolved against the Resolver having the hint passed as parameter. Valid hints are for * example "default", "current", "currentmixed". * * @param wiki the wiki reference name to use (can be empty or null) * @param spaces the spaces list to use (can be empty or null) * @param page the page reference name to use (can be empty or null) * @param hint the hint of the Resolver to use in case any parameter is empty or null * @return the typed Document Reference object or null if no Resolver with the passed hint could be found * * @since 7.2M2 */ public DocumentReference createDocumentReference(String wiki, List<String> spaces, String page, String hint) { EntityReference reference = null; if (!StringUtils.isEmpty(wiki)) { reference = new EntityReference(wiki, EntityType.WIKI); } if (spaces != null && !spaces.isEmpty()) { for (String space : spaces) { reference = new EntityReference(space, EntityType.SPACE, reference); } } if (!StringUtils.isEmpty(page)) { reference = new EntityReference(page, EntityType.DOCUMENT, reference); } DocumentReference documentReference; try { DocumentReferenceResolver<EntityReference> resolver = this.componentManager.getInstance(DocumentReferenceResolver.TYPE_REFERENCE, hint); documentReference = resolver.resolve(reference); } catch (ComponentLookupException e) { try { // Ensure backward compatibility with older scripts that use hints like "default/reference" because at // the time they were written we didn't have support for generic types in component role. DocumentReferenceResolver<EntityReference> drr = this.componentManager.getInstance(DocumentReferenceResolver.class, hint); documentReference = drr.resolve(reference); this.logger.warn("Deprecated usage of DocumentReferenceResolver with hint [{}]. " + "Please consider using a DocumentReferenceResolver that takes into account generic types.", hint); } catch (ComponentLookupException ex) { documentReference = null; } } return documentReference; } /** * Creates an {@link AttachmentReference} from a file name and a reference to the document holding that file. * * @param documentReference a reference to the document the file is attached to * @param fileName the name of a file attached to a document * @return a reference to the specified attachment * @since 2.5M2 */ public AttachmentReference createAttachmentReference(DocumentReference documentReference, String fileName) { return new AttachmentReference(fileName, documentReference); } /** * Creates a {@link WikiReference} from a string representing the wiki name. * * @param wikiName the wiki name (eg "xwiki") * @return the reference to the wiki * @since 5.0M1 */ public WikiReference createWikiReference(String wikiName) { return new WikiReference(wikiName); } /** * Creates a {@link SpaceReference} from a string representing the space name. * * @param spaceName the space name (eg "Main") * @param parent the wiki reference in which the space is located * @return the reference to the space * @since 5.0M1 */ public SpaceReference createSpaceReference(String spaceName, WikiReference parent) { return new SpaceReference(spaceName, parent); } /** * Creates a {@link SpaceReference} from a string representing the space name and the reference of the parent space. * * @param spaceName the space name (e.g. "Main") * @param parent the reference of the parent space * @return the reference to the space * @since 7.3RC1 */ public SpaceReference createSpaceReference(String spaceName, SpaceReference parent) { return new SpaceReference(spaceName, parent); } /** * Creates a {@link SpaceReference} from a list of string representing the space name and the name of its parents. * * @param spaces the list of the spaces name (eg ["A", "B", "C"]) * @param parent the wiki reference in which the space is located * @return the reference to the space * @since 7.2M2 */ public SpaceReference createSpaceReference(List<String> spaces, WikiReference parent) { SpaceReference spaceReference = null; EntityReference parentReference = parent; for (String space : spaces) { spaceReference = new SpaceReference(space, parentReference); parentReference = spaceReference; } return spaceReference; } /** * Creates any {@link EntityReference} from a string. * * @param name the entity reference name (eg "page") * @param type the entity type (eg "wiki", "space", "document", etc) * @return the created reference * @since 5.0M1 */ public EntityReference createEntityReference(String name, EntityType type) { return new EntityReference(name, type); } /** * Creates any {@link EntityReference} from a string. * * @param name the entity reference name (eg "page") * @param type the entity type (eg "wiki", "space", "document", etc) * @param parent the entity parent * @return the created reference * @since 5.0M1 */ public EntityReference createEntityReference(String name, EntityType type, EntityReference parent) { return new EntityReference(name, type, parent); } /** * @param stringRepresentation the space reference specified as a String (using the "wiki:space" format and with * special characters escaped where required) * @param parameters extra parameters to pass to the resolver; you can use these parameters to resolve a space * reference relative to another entity reference * @return the typed Space Reference object (resolved using the {@value #DEFAULT_RESOLVER_HINT} resolver) * @since 5.0M1 */ public SpaceReference resolveSpace(String stringRepresentation, Object... parameters) { return resolveSpace(stringRepresentation, DEFAULT_RESOLVER_HINT, parameters); } /** * @param stringRepresentation the space reference specified as a String (using the "wiki:space" format and with * special characters escaped where required) * @param hint the hint of the Resolver to use in case any part of the reference is missing (no wiki or no space * specified) * @param parameters extra parameters to pass to the resolver; you can use these parameters to resolve a space * reference relative to another entity reference * @return the typed Space Reference object or null if no Resolver with the passed hint could be found * @since 5.0M1 */ public SpaceReference resolveSpace(String stringRepresentation, String hint, Object... parameters) { try { EntityReferenceResolver<String> resolver = this.componentManager.getInstance(EntityReferenceResolver.TYPE_STRING, hint); return new SpaceReference(resolver.resolve(stringRepresentation, EntityType.SPACE, parameters)); } catch (ComponentLookupException e) { return null; } } /** * @param stringRepresentation the document reference specified as a String (using the "wiki:space.page" format and * with special characters escaped where required) * @param parameters extra parameters to pass to the resolver; you can use these parameters to resolve a document * reference relative to another entity reference * @return the typed Document Reference object (resolved using the {@value #DEFAULT_RESOLVER_HINT} resolver) * @since 2.3M2 */ public DocumentReference resolveDocument(String stringRepresentation, Object... parameters) { return resolveDocument(stringRepresentation, DEFAULT_RESOLVER_HINT, parameters); } /** * @param stringRepresentation the document reference specified as a String (using the "wiki:space.page" format and * with special characters escaped where required) * @param hint the hint of the Resolver to use in case any part of the reference is missing (no wiki specified, no * space or no page) * @param parameters extra parameters to pass to the resolver; you can use these parameters to resolve a document * reference relative to another entity reference * @return the typed Document Reference object or null if no Resolver with the passed hint could be found */ public DocumentReference resolveDocument(String stringRepresentation, String hint, Object... parameters) { try { EntityReferenceResolver<String> resolver = this.componentManager.getInstance(EntityReferenceResolver.TYPE_STRING, hint); return new DocumentReference(resolver.resolve(stringRepresentation, EntityType.DOCUMENT, parameters)); } catch (ComponentLookupException e) { return null; } } /** * @param stringRepresentation an attachment reference specified as {@link String} (using the "wiki:space.page@file" * format and with special characters escaped where required) * @param parameters extra parameters to pass to the resolver; you can use these parameters to resolve an attachment * reference relative to another entity reference * @return the corresponding typed {@link AttachmentReference} object (resolved using the * {@value #DEFAULT_RESOLVER_HINT} resolver) * @since 2.5M2 */ public AttachmentReference resolveAttachment(String stringRepresentation, Object... parameters) { return resolveAttachment(stringRepresentation, DEFAULT_RESOLVER_HINT, parameters); } /** * @param stringRepresentation an attachment reference specified as {@link String} (using the "wiki:space.page@file" * format and with special characters escaped where required) * @param hint the hint of the resolver to use in case any part of the reference is missing (no wiki specified, no * space or no page) * @param parameters extra parameters to pass to the resolver; you can use these parameters to resolve an attachment * reference relative to another entity reference * @return the corresponding typed {@link AttachmentReference} object * @since 2.5M2 */ public AttachmentReference resolveAttachment(String stringRepresentation, String hint, Object... parameters) { try { EntityReferenceResolver<String> resolver = this.componentManager.getInstance(EntityReferenceResolver.TYPE_STRING, hint); return new AttachmentReference(resolver.resolve(stringRepresentation, EntityType.ATTACHMENT, parameters)); } catch (ComponentLookupException e) { return null; } } /** * @param stringRepresentation an object reference specified as {@link String} (using the "wiki:space.page^object" * format and with special characters escaped where required) * @param parameters extra parameters to pass to the resolver; you can use these parameters to resolve an object * reference relative to another entity reference * @return the corresponding typed {@link ObjectReference} object (resolved using the * {@value #DEFAULT_RESOLVER_HINT} resolver) * @since 3.2M3 */ public ObjectReference resolveObject(String stringRepresentation, Object... parameters) { return resolveObject(stringRepresentation, DEFAULT_RESOLVER_HINT, parameters); } /** * @param stringRepresentation an object reference specified as {@link String} (using the "wiki:space.page^object" * format and with special characters escaped where required) * @param hint the hint of the resolver to use in case any part of the reference is missing (no wiki specified, no * space or no page) * @param parameters extra parameters to pass to the resolver; you can use these parameters to resolve an object * reference relative to another entity reference * @return the corresponding typed {@link ObjectReference} object * @since 3.2M3 */ public ObjectReference resolveObject(String stringRepresentation, String hint, Object... parameters) { try { EntityReferenceResolver<String> resolver = this.componentManager.getInstance(EntityReferenceResolver.TYPE_STRING, hint); return new ObjectReference(resolver.resolve(stringRepresentation, EntityType.OBJECT, parameters)); } catch (ComponentLookupException e) { return null; } } /** * @param stringRepresentation an object property reference specified as {@link String} (using the * "wiki:space.page^object.property" format and with special characters escaped where required) * @param parameters extra parameters to pass to the resolver; you can use these parameters to resolve an object * property reference relative to another entity reference * @return the corresponding typed {@link ObjectPropertyReference} object (resolved using the * {@value #DEFAULT_RESOLVER_HINT} resolver) * @since 3.2M3 */ public ObjectPropertyReference resolveObjectProperty(String stringRepresentation, Object... parameters) { return resolveObjectProperty(stringRepresentation, DEFAULT_RESOLVER_HINT, parameters); } /** * @param stringRepresentation an object property reference specified as {@link String} (using the * "wiki:space.page^object.property" format and with special characters escaped where required) * @param hint the hint of the resolver to use in case any part of the reference is missing (no wiki specified, no * space or no page) * @param parameters extra parameters to pass to the resolver; you can use these parameters to resolve an object * property reference relative to another entity reference * @return the corresponding typed {@link ObjectPropertyReference} object * @since 3.2M3 */ public ObjectPropertyReference resolveObjectProperty(String stringRepresentation, String hint, Object... parameters) { try { EntityReferenceResolver<String> resolver = this.componentManager.getInstance(EntityReferenceResolver.TYPE_STRING, hint); return new ObjectPropertyReference(resolver.resolve(stringRepresentation, EntityType.OBJECT_PROPERTY, parameters)); } catch (ComponentLookupException e) { return null; } } /** * @param stringRepresentation a class property reference specified as {@link String} (using the * "wiki:Space.Class^property" format and with special characters escaped where required) * @param parameters extra parameters to pass to the resolver; you can use these parameters to resolve a class * property reference relative to another entity reference * @return the corresponding typed {@link ClassPropertyReference} object (resolved using the * {@value #DEFAULT_RESOLVER_HINT} resolver) * @since 5.4.2 * @since 6.0M1 */ public ClassPropertyReference resolveClassProperty(String stringRepresentation, Object... parameters) { return resolveClassProperty(stringRepresentation, DEFAULT_RESOLVER_HINT, parameters); } /** * @param stringRepresentation a class property reference specified as {@link String} (using the * "wiki:Space.Class^property" format and with special characters escaped where required) * @param hint the hint of the resolver to use in case any part of the reference is missing (no wiki specified, no * space or no page) * @param parameters extra parameters to pass to the resolver; you can use these parameters to resolve a class * property reference relative to another entity reference * @return the corresponding typed {@link ClassPropertyReference} object * @since 5.4.2 * @since 6.0M1 */ public ClassPropertyReference resolveClassProperty(String stringRepresentation, String hint, Object... parameters) { try { EntityReferenceResolver<String> resolver = this.componentManager.getInstance(EntityReferenceResolver.TYPE_STRING, hint); return new ClassPropertyReference(resolver.resolve(stringRepresentation, EntityType.CLASS_PROPERTY, parameters)); } catch (ComponentLookupException e) { return null; } } /** * @param reference the entity reference to transform into a String representation * @return the string representation of the passed entity reference (using the "compact" serializer) * @param parameters the optional extra parameters to pass to the Serializer; they are passed directly to * {@link EntityReferenceSerializer#serialize(org.xwiki.model.reference.EntityReference, Object...)} * @since 2.3M2 */ public String serialize(EntityReference reference, Object... parameters) { return serialize(reference, DEFAULT_SERIALIZER_HINT, parameters); } /** * @param reference the entity reference to transform into a String representation * @param hint the hint of the Serializer to use (valid hints are for example "default", "compact", "local") * @param parameters the optional extra parameters to pass to the Serializer; they are passed directly to * {@link EntityReferenceSerializer#serialize(org.xwiki.model.reference.EntityReference, Object...)} * @return the string representation of the passed entity reference */ public String serialize(EntityReference reference, String hint, Object... parameters) { String result; try { EntityReferenceSerializer<String> serializer = this.componentManager.getInstance(EntityReferenceSerializer.TYPE_STRING, hint); result = serializer.serialize(reference, parameters); } catch (ComponentLookupException e) { result = null; } return result; } /** * Get the current value for a specific entity type, like the current space or wiki name. This doesn't return a * proper entity reference, but just the string value that should be used for that type of entity. * * @param type the target entity type; from Velocity it's enough to use a string with the uppercase name of the * entity, like {@code 'SPACE'} * @return the current value for the requested entity type * @since 4.3M1 * @deprecated since 7.4.1/8.0M1, use {@link #getEntityReference(EntityType)} */ @Deprecated public String getEntityReferenceValue(EntityType type) { return getEntityReferenceValue(type, DEFAULT_RESOLVER_HINT); } /** * Get the value configured for a specific entity type, like the space name or wiki name. This doesn't return a * proper entity reference, but just the string value that should be used for that type of entity. * * @param type the target entity type; from Velocity it's enough to use a string with the uppercase name of the * entity, like {@code 'SPACE'} * @param hint the hint of the value provider to use (valid hints are for example "default", "current" and * "currentmixed") * @return the configured value for the requested entity type, for example "Main" for the default space or "WebHome" * for the default space homepage * @since 4.3M1 * @deprecated since 7.2M1, use {@link #getEntityReference(EntityType, String)} */ @Deprecated public String getEntityReferenceValue(EntityType type, String hint) { if (type == null) { return null; } try { EntityReferenceValueProvider provider = this.componentManager.getInstance(EntityReferenceValueProvider.class, hint); return provider.getDefaultValue(type); } catch (ComponentLookupException ex) { return null; } } /** * Get the current reference configured for a specific entity type, like the space reference or wiki reference. This * doesn't return a full entity reference, but just the part that should be used for that type of entity. * * @param type the target entity type; from Velocity it's enough to use a string with the uppercase name of the * entity, like {@code 'SPACE'} * @return the current reference for the requested entity type, for example "Main" for the default space or * "WebHome" for the default space homepage * @since 7.2M1 */ public EntityReference getEntityReference(EntityType type) { return getEntityReference(type, DEFAULT_RESOLVER_HINT); } /** * Get the reference configured for a specific entity type, like the space reference or wiki reference. This doesn't * return a full entity reference, but just the part that should be used for that type of entity. * * @param type the target entity type; from Velocity it's enough to use a string with the uppercase name of the * entity, like {@code 'SPACE'} * @param hint the hint of the {@link EntityReferenceProvider} to use (valid hints are for example "default", * "current" and "currentmixed") * @return the configured value for the requested entity type, for example "Main" for the default space or "WebHome" * for the default space homepage * @since 7.2M1 */ public EntityReference getEntityReference(EntityType type, String hint) { if (type == null) { return null; } try { EntityReferenceProvider provider = this.componentManager.getInstance(EntityReferenceProvider.class, hint); return provider.getDefaultReference(type); } catch (ComponentLookupException ex) { return null; } } /** * Convert passed references to a tree of references. * * @param references the references * @return the references as a tree * @since 5.4RC1 */ public EntityReferenceTree toTree(Iterable< ? extends EntityReference> references) { return new EntityReferenceTree(references); } /** * Convert passed references to a tree of references. * * @param references the references * @return the references as a tree * @since 5.4RC1 */ public EntityReferenceTree toTree(EntityReference... references) { return new EntityReferenceTree(references); } /** * Escape the passed entity name according to reference syntax. * * @param name the name of the entity * @param type the type of the entity * @return the escaped version of the passed name * @since 7.2M1 */ public String escape(String name, EntityType type) { return this.defaultSerializer.serialize(new EntityReference(name, type)); } }