This is an automated email from the ASF dual-hosted git repository. juanpablo pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/jspwiki.git
commit 66d89ff8881dd2bdd2c127673d00adbddd58971b Author: juanpablo <[email protected]> AuthorDate: Fri Mar 13 20:02:22 2020 +0100 JSPWIKI-303: PageFilter backwards compatibility while having a clean, public API --- jspwiki-210-adapters/pom.xml | 8 ++ .../apache/wiki/api/filters/BasicPageFilter.java | 91 ++++++++++++++++++++++ .../wiki/api/filters/FilterSupportOperations.java | 54 +++++++++++++ .../org/apache/wiki/api/filters/PageFilter.java | 73 ++++++++++------- 4 files changed, 200 insertions(+), 26 deletions(-) diff --git a/jspwiki-210-adapters/pom.xml b/jspwiki-210-adapters/pom.xml index 8b4126b..cde8e1c 100644 --- a/jspwiki-210-adapters/pom.xml +++ b/jspwiki-210-adapters/pom.xml @@ -34,6 +34,14 @@ <groupId>${project.groupId}</groupId> <artifactId>jspwiki-main</artifactId> <version>${project.version}</version> + <scope>provided</scope> + </dependency> + + <dependency> + <groupId>${project.groupId}</groupId> + <artifactId>jspwiki-api</artifactId> + <version>${project.version}</version> + <scope>provided</scope> </dependency> <dependency> diff --git a/jspwiki-210-adapters/src/main/java/org/apache/wiki/api/filters/BasicPageFilter.java b/jspwiki-210-adapters/src/main/java/org/apache/wiki/api/filters/BasicPageFilter.java new file mode 100644 index 0000000..1be7789 --- /dev/null +++ b/jspwiki-210-adapters/src/main/java/org/apache/wiki/api/filters/BasicPageFilter.java @@ -0,0 +1,91 @@ +/* + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. + */ +package org.apache.wiki.api.filters; + +import org.apache.wiki.WikiContext; +import org.apache.wiki.WikiEngine; +import org.apache.wiki.api.core.Context; +import org.apache.wiki.api.core.Engine; +import org.apache.wiki.api.exceptions.FilterException; + +import java.lang.reflect.Method; +import java.util.Properties; + +import static org.apache.wiki.api.filters.FilterSupportOperations.executePageFilterPhase; +import static org.apache.wiki.api.filters.FilterSupportOperations.methodOfNonPublicAPI; + + +/** + * Hooks all filters not using the public api into it. + */ +public class BasicPageFilter extends BasePageFilter { + + public void initialize( final WikiEngine engine, final Properties properties ) throws FilterException { + this.m_engine = engine; + } + + public String preTranslate( final WikiContext wikiContext, final String content ) throws FilterException { + return content; + } + + public String preTranslate( final Context wikiContext, final String content ) throws FilterException { + final Method m = methodOfNonPublicAPI( this, "preTranslate", "org.apache.wiki.WikiContext", "java.lang.String" ); + return executePageFilterPhase( () -> content, m, this, wikiContext, content ); + // return content; + } + + public String postTranslate( final WikiContext wikiContext, final String htmlContent ) throws FilterException { + return htmlContent; + } + + public String postTranslate( final Context wikiContext, final String htmlContent ) throws FilterException { + final Method m = methodOfNonPublicAPI( this, "postTranslate", "org.apache.wiki.WikiContext", "java.lang.String" ); + return executePageFilterPhase( () -> htmlContent, m, this, wikiContext, htmlContent ); + // return htmlContent; + } + + public String preSave( final WikiContext wikiContext, final String content ) throws FilterException { + return content; + } + + public String preSave( final Context wikiContext, final String content ) throws FilterException { + final Method m = methodOfNonPublicAPI( this, "preSave", "org.apache.wiki.WikiContext", "java.lang.String" ); + return executePageFilterPhase( () -> content, m, this, wikiContext, content ); + // return content; + } + + public void postSave( final WikiContext wikiContext, final String content ) throws FilterException { + } + + public void postSave( final Context wikiContext, final String content ) throws FilterException { + final Method m = methodOfNonPublicAPI( this, "postSave", "org.apache.wiki.WikiContext", "java.lang.String" ); + executePageFilterPhase( () -> null, m, this, content ); + // empty method + } + + public void destroy( final WikiEngine engine ) { + } + + public void destroy( final Engine engine ) { + final Method m = methodOfNonPublicAPI( this, "destroy", "org.apache.wiki.WikiEngine" ); + executePageFilterPhase( () -> null, m, this, engine ); + // empty method + } + +} diff --git a/jspwiki-api/src/main/java/org/apache/wiki/api/filters/FilterSupportOperations.java b/jspwiki-api/src/main/java/org/apache/wiki/api/filters/FilterSupportOperations.java new file mode 100644 index 0000000..85bdecd --- /dev/null +++ b/jspwiki-api/src/main/java/org/apache/wiki/api/filters/FilterSupportOperations.java @@ -0,0 +1,54 @@ +package org.apache.wiki.api.filters; + +import org.apache.log4j.Logger; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.function.Supplier; + + +class FilterSupportOperations { + + private static final Logger LOG = Logger.getLogger( FilterSupportOperations.class ); + + /** + * Checks if a given object is using old, non public API for a given Filter. This check is two-fold: + * <ul> + * <li>if the page filter class name starts with JSPWiki base package we assume we're using latest JSPWiki, and the public API</li> + * <li>we try to execute the old, non public API equivalent method passed as parameters. If it exists, well, it is using the + * old, non public API, and we return the method execution. In any other case we return null</li> + * </ul> + * + * @param pf given object to check + * @param method requested method + * @param params class names denoting method parameters + * @return old, non public api method if it exists, {@code null} otherwise + */ + static Method methodOfNonPublicAPI( final PageFilter pf, final String method, final String... params ) { + if( !pf.getClass().getName().startsWith( "org.apache.wiki" ) ) { + try { + final Class< ? >[] classes = new Class< ? >[ params.length ]; + for( int i = 0; i < params.length; i++ ) { + classes[ i ] = Class.forName( params[ i ] ); + } + return pf.getClass().getMethod( method, classes ); + } catch( final ClassNotFoundException | NoSuchMethodException e ) { + return null; + } + } + + return null; + } + + static < R > R executePageFilterPhase( final Supplier< R > s, final Method m, final PageFilter pf, final Object... params ) { + if( m != null ) { + try { + return ( R )m.invoke( pf, params ); + } catch( final IllegalAccessException | InvocationTargetException e ) { + LOG.warn( "Problems using filter adapter: " + e.getMessage(), e ); + } + } + return s.get(); + } + +} diff --git a/jspwiki-api/src/main/java/org/apache/wiki/api/filters/PageFilter.java b/jspwiki-api/src/main/java/org/apache/wiki/api/filters/PageFilter.java index 5ef3da8..19cd401 100644 --- a/jspwiki-api/src/main/java/org/apache/wiki/api/filters/PageFilter.java +++ b/jspwiki-api/src/main/java/org/apache/wiki/api/filters/PageFilter.java @@ -22,21 +22,28 @@ import org.apache.wiki.api.core.Context; import org.apache.wiki.api.core.Engine; import org.apache.wiki.api.exceptions.FilterException; +import java.lang.reflect.Method; import java.util.Properties; +import static org.apache.wiki.api.filters.FilterSupportOperations.executePageFilterPhase; +import static org.apache.wiki.api.filters.FilterSupportOperations.methodOfNonPublicAPI; + /** - * Provides a definition for a page filter. A page filter is a class that can be used to transform the WikiPage content being saved or - * being loaded at any given time. - * <p> - * Note that the WikiContext.getPage() method always returns the context in which text is rendered, i.e. the original request. Thus the - * content may actually be different content than what what the wikiContext.getPage() implies! This happens often if you are for example - * including multiple pages on the same page. - * <p> - * PageFilters must be thread-safe! There is only one instance of each PageFilter per each Engine invocation. If you need to store data - * persistently, use VariableManager, or WikiContext. - * <p> - * As of 2.5.30, initialize() gains access to the Engine. + * <p>Provides a definition for a page filter. A page filter is a class that can be used to transform the WikiPage content being saved or + * being loaded at any given time.</p> + * <p>Note that the Context#getPage() method always returns the context in which text is rendered, i.e. the original request. Thus the + * content may actually be different content than what what the Context#getPage() implies! This happens often if you are for example + * including multiple pages on the same page.</p> + * <p>PageFilters must be thread-safe! There is only one instance of each PageFilter per each Engine invocation. If you need to store data + * persistently, use VariableManager, or WikiContext.</p> + * <p><strong>Design notes</strong></p> + * <p>As of 2.5.30, initialize() gains access to the Engine.</p> + * <p>As of 2.11.0.M7, almost all methods from BasicPageFilter end up here as default methods.</p> + * <p>In order to preserve backwards compatibility with filters not using the public API, these default methods checks if a given filter + * is using the old, non public API and, if that's the case attempt to execute the old, non public api corresponding method. If the filter + * uses the public API, then the default callback is used. None of the default callbacks do anything, so it is a good idea for you to + * implement only methods that you need.</p> */ public interface PageFilter { @@ -47,7 +54,7 @@ public interface PageFilter { * @param properties The properties ripped from filters.xml. * @throws FilterException If the filter could not be initialized. If this is thrown, the filter is not added to the internal queues. */ - void initialize( Engine engine, Properties properties ) throws FilterException; + void initialize( final Engine engine, final Properties properties ) throws FilterException; /** * This method is called whenever a page has been loaded from the provider, but not yet been sent through the markup-translation @@ -55,36 +62,42 @@ public interface PageFilter { * * @param context The current context. * @param content WikiMarkup. - * @return The modified wikimarkup content. + * @return The modified wikimarkup content. Default implementation returns the markup as received. * @throws FilterException If something goes wrong. Throwing this causes the entire page processing to be abandoned. */ default String preTranslate( final Context context, final String content ) throws FilterException { - return content; + final Method m = methodOfNonPublicAPI( this, "preTranslate", "org.apache.wiki.WikiContext", "java.lang.String" ); + return executePageFilterPhase( () -> content, m, this, context, content ); + // return content; } /** * This method is called after a page has been fed through the translation process, so anything you are seeing here is translated * content. If you want to do any of your own WikiMarkup2HTML translation, do it here. * - * @param wikiContext The WikiContext. - * @param htmlContent The translated HTML - * @return The modified HTML + * @param context The WikiContext. + * @param htmlContent The translated HTML. + * @return The modified HTML. Default implementation returns the translated html as received. * @throws FilterException If something goes wrong. Throwing this causes the entire page processing to be abandoned. */ - default String postTranslate( final Context wikiContext, final String htmlContent ) throws FilterException { - return htmlContent; + default String postTranslate( final Context context, final String htmlContent ) throws FilterException { + final Method m = methodOfNonPublicAPI( this, "postTranslate", "org.apache.wiki.WikiContext", "java.lang.String" ); + return executePageFilterPhase( () -> htmlContent, m, this, context, htmlContent ); + // return htmlContent; } /** * This method is called before the page has been saved to the PageProvider. * - * @param wikiContext The WikiContext + * @param context The WikiContext * @param content The wikimarkup that the user just wanted to save. - * @return The modified wikimarkup + * @return The modified wikimarkup. Default implementation returns the markup as received. * @throws FilterException If something goes wrong. Throwing this causes the entire page processing to be abandoned. */ - default String preSave( final Context wikiContext, final String content ) throws FilterException { - return content; + default String preSave( final Context context, final String content ) throws FilterException { + final Method m = methodOfNonPublicAPI( this, "preSave", "org.apache.wiki.WikiContext", "java.lang.String" ); + return executePageFilterPhase( () -> content, m, this, context, content ); + // return content; } /** @@ -93,11 +106,15 @@ public interface PageFilter { * <p> * Since the result is discarded from this method, this is only useful for things like counters, etc. * - * @param wikiContext The WikiContext + * @param context The WikiContext * @param content The content which was just stored. * @throws FilterException If something goes wrong. As the page is already saved, This is just logged. */ - default void postSave( final Context wikiContext, final String content ) throws FilterException {} + default void postSave( final Context context, final String content ) throws FilterException { + final Method m = methodOfNonPublicAPI( this, "postSave", "org.apache.wiki.WikiContext", "java.lang.String" ); + executePageFilterPhase( () -> null, m, this, content ); + // empty method + } /** * Called for every filter, e.g. on wiki engine shutdown. Use this if you have to @@ -106,6 +123,10 @@ public interface PageFilter { * @param engine The Engine which owns this filter. * @since 2.5.36 */ - default void destroy( final Engine engine ) {} + default void destroy( final Engine engine ) { + final Method m = methodOfNonPublicAPI( this, "destroy", "org.apache.wiki.WikiEngine" ); + executePageFilterPhase( () -> null, m, this, engine ); + // empty method + } }
