Java tutorial
/* * 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 com.xpn.xwiki.plugin.skinx; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.regex.Pattern; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.xwiki.model.EntityType; import org.xwiki.model.reference.ObjectPropertyReference; import org.xwiki.model.reference.RegexEntityReference; import org.xwiki.observation.EventListener; import org.xwiki.observation.ObservationManager; import org.xwiki.observation.event.Event; import org.xwiki.rendering.syntax.Syntax; import com.xpn.xwiki.XWikiContext; import com.xpn.xwiki.XWikiException; import com.xpn.xwiki.doc.XWikiDocument; import com.xpn.xwiki.internal.event.XObjectPropertyAddedEvent; import com.xpn.xwiki.internal.event.XObjectPropertyDeletedEvent; import com.xpn.xwiki.internal.event.XObjectPropertyEvent; import com.xpn.xwiki.internal.event.XObjectPropertyUpdatedEvent; import com.xpn.xwiki.objects.BaseObject; import com.xpn.xwiki.objects.classes.BaseClass; import com.xpn.xwiki.web.Utils; /** * Abstract SX plugin for wiki-document-based extensions (Extensions written as object of a XWiki Extension class). * Provides a generic method to initialize the XWiki class upon plugin initialization if needed. Provide a notification * mechanism for extensions marked as "use-always". * * @version $Id: c3ac032c55e3ccbeffa5b261caa69b0d7d81db2e $ * @since 1.4 * @see JsSkinExtensionPlugin * @see CssSkinExtensionPlugin */ public abstract class AbstractDocumentSkinExtensionPlugin extends AbstractSkinExtensionPlugin implements EventListener { /** * Log helper for logging messages in this class. */ private static final Logger LOGGER = LoggerFactory.getLogger(AbstractDocumentSkinExtensionPlugin.class); /** * The name of the field that indicates whether an extension should always be used, or only when explicitly pulled. */ private static final String USE_FIELDNAME = "use"; /** * A Map with wiki/database name as keys and sets of extensions to use always for this wiki as values. */ private Map<String, Set<String>> alwaysUsedExtensions; /** * Used to match events on "use" property. */ private final List<Event> events = new ArrayList<Event>(3); /** * XWiki plugin constructor. * * @param name The name of the plugin, which can be used for retrieving the plugin API from velocity. Unused. * @param className The canonical classname of the plugin. Unused. * @param context The current request context. * @see com.xpn.xwiki.plugin.XWikiDefaultPlugin#XWikiDefaultPlugin(String,String,com.xpn.xwiki.XWikiContext) */ public AbstractDocumentSkinExtensionPlugin(String name, String className, XWikiContext context) { super(name, className, context); RegexEntityReference usePropertyReference = new RegexEntityReference(Pattern.compile(USE_FIELDNAME), EntityType.OBJECT_PROPERTY, new RegexEntityReference( Pattern.compile(".*:" + getExtensionClassName() + "\\[\\d*\\]"), EntityType.OBJECT)); this.events.add(new XObjectPropertyAddedEvent(usePropertyReference)); this.events.add(new XObjectPropertyDeletedEvent(usePropertyReference)); this.events.add(new XObjectPropertyUpdatedEvent(usePropertyReference)); } @Override public List<Event> getEvents() { return this.events; } /** * The name of the XClass which holds extensions of this type. * * @return A <code>String</code> representation of the XClass name, in the <code>Space.Document</code> format. */ protected abstract String getExtensionClassName(); /** * A user-friendly name for this type of resource, used in the auto-generated class document. * * @return The user-friendly name for this type of resource. */ protected abstract String getExtensionName(); /** * {@inheritDoc} * <p> * Create/update the XClass corresponding to this kind of extension, and register the listeners that update the list * of always used extensions. * </p> * * @see com.xpn.xwiki.plugin.XWikiDefaultPlugin#init(com.xpn.xwiki.XWikiContext) */ @Override public void init(XWikiContext context) { super.init(context); this.alwaysUsedExtensions = new HashMap<String, Set<String>>(); getExtensionClass(context); Utils.getComponent(ObservationManager.class).addListener(this); } /** * {@inheritDoc} * <p> * Create/update the XClass corresponding to this kind of extension in this virtual wiki. * </p> * * @see com.xpn.xwiki.plugin.XWikiDefaultPlugin#virtualInit(com.xpn.xwiki.XWikiContext) */ @Override public void virtualInit(XWikiContext context) { super.virtualInit(context); getExtensionClass(context); } /** * {@inheritDoc} * <p> * For this kind of resources, an XObject property (<tt>use</tt>) with the value <tt>always</tt> indicates always * used extensions. The list of extensions for each wiki is lazily placed in a cache: if the extension set for the * context wiki is null, then they will be looked up in the database and added to it. The cache is invalidated using * the notification mechanism. * </p> * * @see AbstractSkinExtensionPlugin#getAlwaysUsedExtensions(XWikiContext) */ @Override public Set<String> getAlwaysUsedExtensions(XWikiContext context) { // Retrieve the current wiki name from the XWiki context String currentWiki = StringUtils.defaultIfEmpty(context.getDatabase(), context.getMainXWiki()); // If we already have extensions defined for this wiki, we return them if (this.alwaysUsedExtensions.get(currentWiki) != null) { return this.alwaysUsedExtensions.get(currentWiki); } else { // Otherwise, we look them up in the database. Set<String> extensions = new HashSet<String>(); String query = ", BaseObject as obj, StringProperty as use where obj.className='" + getExtensionClassName() + "'" + " and obj.name=doc.fullName and use.id.id=obj.id and use.id.name='use' and use.value='always'"; try { for (String extension : context.getWiki().getStore().searchDocumentsNames(query, context)) { try { XWikiDocument doc = context.getWiki().getDocument(extension, context); // Only add the extension as being "always used" if the page holding it has been saved with // programming rights. if (context.getWiki().getRightService().hasProgrammingRights(doc, context)) { extensions.add(extension); } } catch (XWikiException e1) { LOGGER.error("Error while adding skin extension [{}] as always used. It will be ignored.", extension, e1); } } this.alwaysUsedExtensions.put(currentWiki, extensions); return extensions; } catch (XWikiException e) { LOGGER.error("Error while retrieving always used JS extensions", e); return Collections.emptySet(); } } } @Override public boolean hasPageExtensions(XWikiContext context) { XWikiDocument doc = context.getDoc(); Collection<BaseObject> objects = doc.getObjects(getExtensionClassName()); if (objects != null) { for (BaseObject obj : objects) { if (obj == null) { continue; } if (obj.getStringValue(USE_FIELDNAME).equals("currentPage")) { return true; } } } return false; } /** * {@inheritDoc} * <p> * We must override this method since the plugin manager only calls it for classes that provide their own * implementation, and not an inherited one. * </p> * * @see AbstractSkinExtensionPlugin#endParsing(String, XWikiContext) */ @Override public String endParsing(String content, XWikiContext context) { return super.endParsing(content, context); } /** * Creates or updates the XClass used for this type of extension. Usually called on {@link #init(XWikiContext)} and * {@link #virtualInit(XWikiContext)}. * * @param context The current request context, which gives access to the wiki. * @return The XClass for this extension. */ public BaseClass getExtensionClass(XWikiContext context) { try { XWikiDocument doc = context.getWiki().getDocument(getExtensionClassName(), context); boolean needsUpdate = false; String useOptions = "currentPage=Always on this page|onDemand=On demand|always=Always on this wiki"; BaseClass bclass = doc.getxWikiClass(); if (context.get("initdone") != null) { return bclass; } bclass.setName(getExtensionClassName()); needsUpdate |= bclass.addTextField("name", "Name", 30); needsUpdate |= bclass.addTextAreaField("code", "Code", 50, 20); needsUpdate |= bclass.addStaticListField(USE_FIELDNAME, "Use this extension", useOptions); needsUpdate |= bclass.addBooleanField("parse", "Parse content", "yesno"); needsUpdate |= bclass.addStaticListField("cache", "Caching policy", "long|short|default|forbid"); if (StringUtils.isBlank(doc.getCreator())) { needsUpdate = true; doc.setCreator("superadmin"); } if (StringUtils.isBlank(doc.getAuthor())) { needsUpdate = true; doc.setAuthor(doc.getCreator()); } if (StringUtils.isBlank(doc.getParent())) { needsUpdate = true; doc.setParent("XWiki.XWikiClasses"); } if (StringUtils.isBlank(doc.getTitle())) { needsUpdate = true; doc.setTitle("XWiki " + getExtensionName() + " Extension Class"); } if (StringUtils.isBlank(doc.getContent()) || !Syntax.XWIKI_2_0.equals(doc.getSyntax())) { needsUpdate = true; doc.setContent("{{include document=\"XWiki.ClassSheet\" /}}"); doc.setSyntax(Syntax.XWIKI_2_0); } if (needsUpdate) { context.getWiki().saveDocument(doc, context); } return bclass; } catch (Exception ex) { LOGGER.error("Cannot initialize skin extension class [{}]", getExtensionClassName(), ex); } return null; } /** * {@inheritDoc} * <p> * Make sure to keep the {@link #alwaysUsedExtensions} map consistent when the database changes. * * @see org.xwiki.observation.EventListener#onEvent(org.xwiki.observation.event.Event, java.lang.Object, * java.lang.Object) */ @Override public void onEvent(Event event, Object source, Object data) { XObjectPropertyEvent propertyEvent = (XObjectPropertyEvent) event; XWikiDocument document = (XWikiDocument) source; XWikiContext context = (XWikiContext) data; boolean remove; if (propertyEvent instanceof XObjectPropertyDeletedEvent) { remove = true; } else { remove = !StringUtils.equals((String) document .getXObjectProperty((ObjectPropertyReference) propertyEvent.getReference()).getValue(), "always"); } Set<String> extensions = getAlwaysUsedExtensions(context); if (remove) { extensions.remove(document.getFullName()); } else { extensions.add(document.getFullName()); } } }