/* * 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.uiextension.internal; import java.util.ArrayList; import java.util.List; import javax.inject.Inject; import javax.inject.Named; import javax.inject.Provider; import javax.inject.Singleton; import org.slf4j.Logger; import org.xwiki.component.annotation.Component; import org.xwiki.component.manager.ComponentManager; import org.xwiki.component.wiki.WikiComponent; import org.xwiki.component.wiki.WikiComponentBuilder; import org.xwiki.component.wiki.WikiComponentException; import org.xwiki.component.wiki.WikiComponentScope; import org.xwiki.model.reference.DocumentReference; import org.xwiki.model.reference.EntityReferenceSerializer; import org.xwiki.security.authorization.AuthorExecutor; import org.xwiki.security.authorization.AuthorizationManager; import org.xwiki.security.authorization.Right; import com.xpn.xwiki.XWikiContext; import com.xpn.xwiki.XWikiException; import com.xpn.xwiki.doc.XWikiDocument; import com.xpn.xwiki.objects.BaseObject; /** * Provides {@link org.xwiki.uiextension.UIExtension} components from definitions stored in XObjects. * * @version $Id: b7d2d9a3a6af8dfa3cec57be71874ccf42f932fd $ * @since 4.2M3 */ @Component @Singleton @Named("uiextension") public class WikiUIExtensionComponentBuilder implements WikiComponentBuilder, WikiUIExtensionConstants { /** * The logger to log. */ @Inject private Logger logger; /** * Used to transform the reference to the UI Extension XClass to a string usable in a query. {@see * #searchDocumentReferences()} */ @Inject @Named("compactwiki") private EntityReferenceSerializer<String> compactWikiSerializer; /** * Used to generate a role hint for UI extensions based on their object reference. */ @Inject private EntityReferenceSerializer<String> serializer; /** * Used to get the parser to transform the extension content to a XDOM. */ @Inject @Named("wiki") private ComponentManager cm; @Inject private Provider<XWikiContext> xcontextProvider; @Inject private AuthorizationManager authorization; @Inject private AuthorExecutor authorExecutor; /** * Checks if the last author of the document holding the extension(s) has the rights required to register extensions * for the given scope. If the document author doesn't have the required rights a {@link WikiComponentException} is * thrown. * * @param extensionsDoc the document holding the extension(s) * @param scope the scope to check the rights for * @throws WikiComponentException if the document author doesn't have the required rights to register extensions */ private void checkRights(XWikiDocument extensionsDoc, WikiComponentScope scope) throws WikiComponentException { if (scope == WikiComponentScope.GLOBAL && !this.authorization.hasAccess(Right.PROGRAM, extensionsDoc.getAuthorReference(), null)) { throw new WikiComponentException("Registering global UI extensions requires programming rights"); } else if (scope == WikiComponentScope.WIKI && !this.authorization.hasAccess(Right.ADMIN, extensionsDoc.getAuthorReference(), extensionsDoc.getDocumentReference().getWikiReference())) { throw new WikiComponentException( "Registering UI extensions at wiki level requires wiki administration rights"); } } /** * Retrieve the list of {@link BaseObject} defining UI extensions. * * @param extensionsDoc the document to retrieve the definitions from * @return the list of {@link BaseObject} defining UI extensions in the given document * @throws WikiComponentException if no extension definition can be found in the document */ private List<BaseObject> getExtensionDefinitions(XWikiDocument extensionsDoc) throws WikiComponentException { // Check whether this document contains a listener definition. List<BaseObject> extensionDefinitions = extensionsDoc.getXObjects(UI_EXTENSION_CLASS); if (extensionDefinitions.size() == 0) { throw new WikiComponentException(String.format("No UI extension object could be found in document [%s]", extensionsDoc.getDocumentReference())); } return extensionDefinitions; } @Override public List<WikiComponent> buildComponents(DocumentReference reference) throws WikiComponentException { List<WikiComponent> extensions = new ArrayList<WikiComponent>(); XWikiDocument doc = null; try { XWikiContext xcontext = xcontextProvider.get(); doc = xcontext.getWiki().getDocument(reference, xcontext); } catch (XWikiException e) { throw new WikiComponentException( String.format("Failed to create UI Extension(s) document [%s]", reference), e); } for (BaseObject extensionDefinition : this.getExtensionDefinitions(doc)) { if (extensionDefinition == null) { continue; } // Extract extension definition. String id = extensionDefinition.getStringValue(ID_PROPERTY); String extensionPointId = extensionDefinition.getStringValue(EXTENSION_POINT_ID_PROPERTY); String content = extensionDefinition.getStringValue(CONTENT_PROPERTY); String rawParameters = extensionDefinition.getStringValue(PARAMETERS_PROPERTY); WikiComponentScope scope = WikiComponentScope.fromString(extensionDefinition.getStringValue(SCOPE_PROPERTY)); // Before going further we need to check the document author is authorized to register the extension this.checkRights(doc, scope); String roleHint = this.serializer.serialize(extensionDefinition.getReference()); WikiUIExtension extension = new WikiUIExtension(roleHint, id, extensionPointId, extensionDefinition.getReference(), doc.getAuthorReference(), this.authorExecutor); // It would be nice to have PER_LOOKUP components for UIX parameters but without constructor injection it's // safer to use a POJO and pass the Component Manager to it. WikiUIExtensionParameters parameters = new WikiUIExtensionParameters(id, rawParameters, cm); extension.setParameters(parameters); // It would be nice to have PER_LOOKUP components for UIX renderers but without constructor injection it's // safer to use a POJO and pass the Component Manager to it. WikiUIExtensionRenderer renderer = new WikiUIExtensionRenderer(roleHint, content, doc.getDocumentReference(), cm); extension.setRenderer(renderer); extension.setScope(scope); extensions.add(extension); } return extensions; } /** * @return list of document references to documents containing a UI extension object. */ @Override public List<DocumentReference> getDocumentReferences() { List<DocumentReference> results = new ArrayList<DocumentReference>(); // Note that the query is made to work with Oracle which treats empty strings as null. String query = ", BaseObject as obj, StringProperty as epId where obj.className=? " + "and obj.name=doc.fullName and epId.id.id=obj.id and epId.id.name=? " + "and (epId.value <> '' or (epId.value is not null and '' is null))"; List<String> parameters = new ArrayList<String>(); parameters.add(this.compactWikiSerializer.serialize(UI_EXTENSION_CLASS)); parameters.add(EXTENSION_POINT_ID_PROPERTY); try { XWikiContext xcontext = xcontextProvider.get(); results.addAll(xcontext.getWiki().getStore().searchDocumentReferences(query, parameters, xcontext)); } catch (XWikiException e) { this.logger.warn("Search for UI extensions failed: [{}]", e.getMessage()); } return results; } }