You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jspwiki.apache.org by ju...@apache.org on 2020/03/21 17:04:17 UTC

[jspwiki] 15/36: JSPWIKI-303: provide backwards compatibility with public API for page/attachment providers not using it

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 cd501d28f79f0e91aa15995343db5504252f2ef6
Author: juanpablo <ju...@apache.org>
AuthorDate: Fri Mar 20 19:59:38 2020 +0100

    JSPWIKI-303: provide backwards compatibility with public API for page/attachment providers not using it
    
    to use page providers not using the public api, jspwiki.pageProvider must be set to WikiPageAdapterProvider and jspwiki.pageProvider.adapter.impl to the provider itself
    to use attachment providers not using the public api, jspwiki.attachmentProvider must be set to WikiAttachmentAdapterProvider and jspwiki.attachmentProvider.adapter.impl to the provider itself
    see WikiProviderAdaptersTest on the jspwiki-210-adapters module for an example configuration with these parameters
---
 .../providers/WikiAttachmentAdapterProvider.java   | 150 +++++++++++++++
 .../wiki/providers/WikiPageAdapterProvider.java    | 169 +++++++++++++++++
 .../providers/TwoXWikiAttachmentProvider.java      | 198 ++++++++++++++++++++
 .../example/providers/TwoXWikiPageProvider.java    | 206 +++++++++++++++++++++
 4 files changed, 723 insertions(+)

diff --git a/jspwiki-210-adapters/src/main/java/org/apache/wiki/providers/WikiAttachmentAdapterProvider.java b/jspwiki-210-adapters/src/main/java/org/apache/wiki/providers/WikiAttachmentAdapterProvider.java
new file mode 100644
index 0000000..e7b39a5
--- /dev/null
+++ b/jspwiki-210-adapters/src/main/java/org/apache/wiki/providers/WikiAttachmentAdapterProvider.java
@@ -0,0 +1,150 @@
+/*
+    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.providers;
+
+import org.apache.log4j.Logger;
+import org.apache.wiki.WikiPage;
+import org.apache.wiki.api.core.Attachment;
+import org.apache.wiki.api.core.Engine;
+import org.apache.wiki.api.core.Page;
+import org.apache.wiki.api.exceptions.NoRequiredPropertyException;
+import org.apache.wiki.api.exceptions.ProviderException;
+import org.apache.wiki.api.providers.AttachmentProvider;
+import org.apache.wiki.api.search.QueryItem;
+import org.apache.wiki.util.ClassUtil;
+import org.apache.wiki.util.TextUtil;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Date;
+import java.util.List;
+import java.util.Properties;
+import java.util.stream.Collectors;
+
+
+/**
+ * This provider ensures backward compatibility with attachment providers not using the public API. As providers should use the public API
+ * directly, the use of this class is considered deprecated.
+ *
+ * @deprecated adapted provider should use {@link AttachmentProvider} instead.
+ * @see AttachmentProvider
+ */
+@Deprecated
+public class WikiAttachmentAdapterProvider implements AttachmentProvider {
+
+    private static final Logger LOG = Logger.getLogger( WikiAttachmentAdapterProvider.class );
+    private static final String PROP_ADAPTER_IMPL = "jspwiki.attachmentProvider.adapter.impl";
+
+    WikiAttachmentProvider provider;
+
+    /** {@inheritDoc} */
+    @Override
+    public void initialize( final Engine engine, final Properties properties ) throws NoRequiredPropertyException, IOException {
+        LOG.warn( "Using an attachment provider through org.apache.wiki.providers.WikiAttachmentAdapterProvider" );
+        LOG.warn( "Please contact the attachment provider's author so there can be a new release of the provider " +
+                  "implementing the new org.apache.wiki.api.providers.AttachmentProvider public API" );
+        final String classname = TextUtil.getRequiredProperty( properties, PROP_ADAPTER_IMPL );
+        try {
+            LOG.debug( "Page provider class: '" + classname + "'" );
+            final Class<?> providerclass = ClassUtil.findClass("org.apache.wiki.providers", classname);
+            provider = ( WikiAttachmentProvider ) providerclass.newInstance();
+        } catch( final IllegalAccessException | InstantiationException | ClassNotFoundException e ) {
+            LOG.error( "Could not instantiate " + classname, e );
+            throw new IOException( e.getMessage(), e );
+        }
+
+        LOG.debug( "Initializing attachment provider class " + provider );
+        provider.initialize( engine, properties );
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String getProviderInfo() {
+        return provider.getProviderInfo();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void putAttachmentData( final Attachment att, final InputStream data ) throws ProviderException, IOException {
+        provider.putAttachmentData( ( org.apache.wiki.attachment.Attachment )att, data );
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public InputStream getAttachmentData( final Attachment att ) throws ProviderException, IOException {
+        return provider.getAttachmentData( ( org.apache.wiki.attachment.Attachment )att );
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public List< Attachment > listAttachments( final Page page ) throws ProviderException {
+        return provider.listAttachments( ( WikiPage )page ).stream().map( att -> ( Attachment )att ).collect( Collectors.toList() );
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public Collection< Attachment > findAttachments( final QueryItem[] query ) {
+        final org.apache.wiki.search.QueryItem[] queryItems = Arrays.stream( query )
+                                                                    .map( SearchAdapter::oldQueryItemfrom )
+                                                                    .toArray( org.apache.wiki.search.QueryItem[]::new );
+        return provider.findAttachments( queryItems ).stream().map( att -> ( Attachment )att ).collect( Collectors.toList() );
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public List< Attachment > listAllChanged( final Date timestamp ) throws ProviderException {
+        return provider.listAllChanged( timestamp ).stream().map( att -> ( Attachment )att ).collect( Collectors.toList() );
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public Attachment getAttachmentInfo( final Page page, final String name, final int version ) throws ProviderException {
+        return provider.getAttachmentInfo( ( WikiPage )page, name, version );
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public List< Attachment > getVersionHistory( final Attachment att ) {
+        return provider.getVersionHistory( ( org.apache.wiki.attachment.Attachment )att )
+                       .stream()
+                       .map( attr -> ( Attachment )attr )
+                       .collect( Collectors.toList() );
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void deleteVersion( final Attachment att ) throws ProviderException {
+        provider.deleteVersion( ( org.apache.wiki.attachment.Attachment )att );
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void deleteAttachment( final Attachment att ) throws ProviderException {
+        provider.deleteAttachment( ( org.apache.wiki.attachment.Attachment )att );
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void moveAttachmentsForPage( final String oldParent, final String newParent ) throws ProviderException {
+        provider.moveAttachmentsForPage( oldParent, newParent );
+    }
+
+}
diff --git a/jspwiki-210-adapters/src/main/java/org/apache/wiki/providers/WikiPageAdapterProvider.java b/jspwiki-210-adapters/src/main/java/org/apache/wiki/providers/WikiPageAdapterProvider.java
new file mode 100644
index 0000000..7558906
--- /dev/null
+++ b/jspwiki-210-adapters/src/main/java/org/apache/wiki/providers/WikiPageAdapterProvider.java
@@ -0,0 +1,169 @@
+/*
+    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.providers;
+
+import org.apache.log4j.Logger;
+import org.apache.wiki.WikiPage;
+import org.apache.wiki.api.core.Engine;
+import org.apache.wiki.api.core.Page;
+import org.apache.wiki.api.exceptions.NoRequiredPropertyException;
+import org.apache.wiki.api.exceptions.ProviderException;
+import org.apache.wiki.api.providers.PageProvider;
+import org.apache.wiki.api.search.QueryItem;
+import org.apache.wiki.api.search.SearchResult;
+import org.apache.wiki.search.SearchResultComparator;
+import org.apache.wiki.util.ClassUtil;
+import org.apache.wiki.util.TextUtil;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Date;
+import java.util.List;
+import java.util.Properties;
+import java.util.TreeSet;
+import java.util.stream.Collectors;
+
+
+/**
+ * This provider ensures backward compatibility with page providers not using the public API. As providers should use the public API
+ * directly, the use of this class is considered deprecated.
+ *
+ * @deprecated adapted provider should use {@link PageProvider} instead.
+ * @see PageProvider
+ */
+@Deprecated
+public class WikiPageAdapterProvider implements PageProvider {
+
+    private static final Logger LOG = Logger.getLogger( WikiPageAdapterProvider.class );
+    private static final String PROP_ADAPTER_IMPL = "jspwiki.pageProvider.adapter.impl";
+
+    WikiPageProvider provider;
+
+    /** {@inheritDoc} */
+    @Override
+    public void initialize( final Engine engine, final Properties properties ) throws NoRequiredPropertyException, IOException {
+        LOG.warn( "Using a page provider through org.apache.wiki.providers.WikiPageAdapterProviderAdapterProvider" );
+        LOG.warn( "Please contact the page provider's author so there can be a new release of the provider " +
+                  "implementing the new org.apache.wiki.api.providers.PageProvider public API" );
+        final String classname = TextUtil.getRequiredProperty( properties, PROP_ADAPTER_IMPL );
+        try {
+            LOG.debug( "Page provider class: '" + classname + "'" );
+            final Class<?> providerclass = ClassUtil.findClass("org.apache.wiki.providers", classname);
+            provider = ( WikiPageProvider ) providerclass.newInstance();
+        } catch( final IllegalAccessException | InstantiationException | ClassNotFoundException e ) {
+            LOG.error( "Could not instantiate " + classname, e );
+            throw new IOException( e.getMessage(), e );
+        }
+
+        LOG.debug( "Initializing page provider class " + provider );
+        provider.initialize( engine, properties );
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String getProviderInfo() {
+        return provider.getProviderInfo();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void putPageText( final Page page, final String text ) throws ProviderException {
+        provider.putPageText( ( WikiPage )page, text );
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public boolean pageExists( final String page ) {
+        return provider.pageExists( page );
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public boolean pageExists( final String page, final int version ) {
+        return provider.pageExists( page, version );
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public Collection< SearchResult > findPages( final QueryItem[] query ) {
+        final org.apache.wiki.search.QueryItem[] queryItems = Arrays.stream( query )
+                                                                    .map( SearchAdapter::oldQueryItemfrom )
+                                                                    .toArray( org.apache.wiki.search.QueryItem[]::new );
+        final Collection< org.apache.wiki.search.SearchResult > results = provider.findPages( queryItems );
+        return results.stream()
+                      .map( SearchAdapter::newSearchResultFrom )
+                      .collect( Collectors.toCollection( () -> new TreeSet<>( new SearchResultComparator() ) ) );
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public Page getPageInfo( final String page, final int version ) throws ProviderException {
+        return provider.getPageInfo( page, version );
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public Collection< Page > getAllPages() throws ProviderException {
+        return provider.getAllPages().stream().map( wikiPage -> ( Page )wikiPage ).collect( Collectors.toList() );
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public Collection< Page > getAllChangedSince( final Date date ) {
+        return provider.getAllChangedSince( date ).stream().map( wikiPage -> ( Page )wikiPage ).collect( Collectors.toList() );
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public int getPageCount() throws ProviderException {
+        return provider.getPageCount();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public List< Page > getVersionHistory( final String page ) throws ProviderException {
+        return provider.getVersionHistory( page ).stream().map( wikiPage -> ( Page )wikiPage ).collect( Collectors.toList() );
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String getPageText( final String page, final int version ) throws ProviderException {
+        return provider.getPageText( page, version );
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void deleteVersion( final String pageName, final int version ) throws ProviderException {
+        provider.deleteVersion( pageName, version );
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void deletePage( final String pageName ) throws ProviderException {
+        provider.deletePage( pageName );
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void movePage( final String from, final String to ) throws ProviderException {
+        provider.movePage( from, to );
+    }
+
+}
diff --git a/jspwiki-210-test-adaptees/src/test/java/com/example/providers/TwoXWikiAttachmentProvider.java b/jspwiki-210-test-adaptees/src/test/java/com/example/providers/TwoXWikiAttachmentProvider.java
new file mode 100644
index 0000000..7b6ba45
--- /dev/null
+++ b/jspwiki-210-test-adaptees/src/test/java/com/example/providers/TwoXWikiAttachmentProvider.java
@@ -0,0 +1,198 @@
+/*
+    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 com.example.providers;
+
+import org.apache.wiki.WikiEngine;
+import org.apache.wiki.WikiPage;
+import org.apache.wiki.WikiProvider;
+import org.apache.wiki.api.exceptions.NoRequiredPropertyException;
+import org.apache.wiki.api.exceptions.ProviderException;
+import org.apache.wiki.attachment.Attachment;
+import org.apache.wiki.pages.PageTimeComparator;
+import org.apache.wiki.providers.WikiAttachmentProvider;
+import org.apache.wiki.search.QueryItem;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.stream.Collectors;
+
+
+public class TwoXWikiAttachmentProvider implements WikiAttachmentProvider {
+
+    WikiEngine engine;
+    Map< String, List < Attachment > > attachments = new ConcurrentHashMap<>();
+    Map< String, List < InputStream > > contents = new ConcurrentHashMap<>();
+
+    /** {@inheritDoc} */
+    @Override
+    public String getProviderInfo() {
+        return this.getClass().getName();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void initialize( final WikiEngine engine, final Properties properties ) throws NoRequiredPropertyException, IOException {
+        this.engine = engine;
+        try {
+            putAttachmentData( new Attachment( engine, "page1", "att11.txt" ), new ByteArrayInputStream( "blurb".getBytes( StandardCharsets.UTF_8 ) ) );
+            putAttachmentData( new Attachment( engine, "page1", "att12.txt" ), new ByteArrayInputStream( "blerb".getBytes( StandardCharsets.UTF_8) ) );
+            putAttachmentData( new Attachment( engine, "page2", "att21.txt" ), new ByteArrayInputStream( "blarb".getBytes( StandardCharsets.UTF_8) ) );
+        } catch( final ProviderException e ) {
+            throw new IOException( e.getMessage(), e );
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void putAttachmentData( final Attachment att, final InputStream data ) throws ProviderException, IOException {
+        att.setVersion( att.getVersion() + 1 );
+        att.setLastModified( new Date() );
+        if( attachmentExists( att ) ) {
+            attachments.get( att.getName() ).add( att );
+            contents.get( att.getName() ).add( data );
+        } else {
+            attachments.put( att.getName(), new ArrayList<>( Arrays.asList( att ) ) );
+            contents.put( att.getName(), new ArrayList<>( Arrays.asList( data ) ) );
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public InputStream getAttachmentData( final Attachment att ) throws ProviderException, IOException {
+        final int v = att.getVersion() == WikiProvider.LATEST_VERSION ? contents.get( att.getName() ).size() - 1 : att.getVersion();
+        return contents.get( att.getName() ).get( v );
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public List< Attachment > listAttachments( final WikiPage page ) throws ProviderException {
+        return attachments.entrySet()
+                          .stream()
+                          .filter( e -> e.getKey().startsWith( page.getName() + "/" ) )
+                          .map( Map.Entry::getValue )
+                          .flatMap( List::stream )
+                          .collect( Collectors.toList() );
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public Collection< Attachment > findAttachments( final QueryItem[] query ) {
+        return new ArrayList<>();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public List< Attachment > listAllChanged( final Date timestamp ) throws ProviderException {
+        final List< Attachment > attachs = attachments.values()
+                                                      .stream()
+                                                      .map( attrs -> attrs.get( attrs.size() -1 ) )
+                                                      .filter( att -> att.getLastModified() != null && att.getLastModified().after( timestamp ) )
+                                                      .collect( Collectors.toList() );
+        attachs.sort( new PageTimeComparator() );
+        return attachs;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public Attachment getAttachmentInfo( final WikiPage page, final String name, final int version ) throws ProviderException {
+        if( attachments.get( page.getName() + "/" + name ) != null ) {
+            final int v = version == WikiProvider.LATEST_VERSION ? contents.get( page.getName() + "/" + name ).size() - 1 : version;
+            return attachments.get( page.getName() + "/" + name ).get( v );
+        }
+        return null;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public List< Attachment > getVersionHistory( final Attachment att ) {
+        if( att != null && attachments.get( att.getName() ) != null ) {
+            return attachments.get( att.getName() );
+        }
+        return Collections.emptyList();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void deleteVersion( final Attachment att ) throws ProviderException {
+        if( attachmentVersionExists( att ) ) {
+            attachments.get( att.getName() ).remove( att.getVersion() - 1 );
+            contents.get( att.getName() ).remove( att.getVersion() - 1 );
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void deleteAttachment( final Attachment att ) throws ProviderException {
+        if( att != null ) {
+            attachments.remove( att.getName() );
+            contents.remove( att.getName() );
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void moveAttachmentsForPage( final String oldParent, final String newParent ) throws ProviderException {
+        final Map< String, List < Attachment > > oldAttachments = attachments.entrySet()
+                                                                             .stream()
+                                                                             .filter( e -> e.getKey().startsWith( oldParent + "/" ) )
+                                                                             .collect( Collectors.toMap( Map.Entry::getKey, Map.Entry::getValue ) );
+        final Map< String, List < InputStream > > oldContents = contents.entrySet()
+                                                                        .stream()
+                                                                        .filter( e -> e.getKey().startsWith( oldParent + "/" ) )
+                                                                        .collect( Collectors.toMap( Map.Entry::getKey, Map.Entry::getValue ) );
+
+        // If it exists, we're overwriting an old page (this has already been confirmed at a higher level), so delete any existing attachments.
+        oldAttachments.forEach( ( key, value ) -> {
+            final String newKey = newParent + "/" + key.substring( key.indexOf( '/' ) + 1 );
+            attachments.putIfAbsent( newKey, value );
+            if( attachments.get( key ) != null ) {
+                attachments.remove( key );
+            }
+        } );
+        oldContents.forEach( ( key, value ) -> {
+            final String newKey = newParent + "/" + key.substring( key.indexOf( '/' ) + 1 );
+            contents.putIfAbsent( newKey, value );
+            if( contents.get( key ) != null ) {
+                contents.remove( key );
+            }
+        } );
+    }
+
+    boolean attachmentExists( final Attachment att ) {
+        return att != null && attachments.get( att.getName() ) != null && contents.get( att.getName() ) != null;
+    }
+
+    boolean attachmentVersionExists( final Attachment att ) {
+        return attachmentExists( att ) &&
+               attachments.get( att.getName() ).size() >= att.getVersion() &&
+               contents.get( att.getName() ).size() >= att.getVersion();
+    }
+
+}
diff --git a/jspwiki-210-test-adaptees/src/test/java/com/example/providers/TwoXWikiPageProvider.java b/jspwiki-210-test-adaptees/src/test/java/com/example/providers/TwoXWikiPageProvider.java
new file mode 100644
index 0000000..0bde27c
--- /dev/null
+++ b/jspwiki-210-test-adaptees/src/test/java/com/example/providers/TwoXWikiPageProvider.java
@@ -0,0 +1,206 @@
+/*
+    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 com.example.providers;
+
+import org.apache.wiki.WikiEngine;
+import org.apache.wiki.WikiPage;
+import org.apache.wiki.WikiProvider;
+import org.apache.wiki.api.exceptions.NoRequiredPropertyException;
+import org.apache.wiki.api.exceptions.ProviderException;
+import org.apache.wiki.providers.WikiPageProvider;
+import org.apache.wiki.search.QueryItem;
+import org.apache.wiki.search.SearchMatcher;
+import org.apache.wiki.search.SearchResult;
+import org.apache.wiki.search.SearchResultComparator;
+
+import java.io.IOException;
+import java.util.AbstractMap;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import java.util.TreeSet;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.Supplier;
+import java.util.stream.Collectors;
+
+
+public class TwoXWikiPageProvider implements WikiPageProvider {
+
+    WikiEngine engine;
+    Map< String, List < WikiPage > > pages = new ConcurrentHashMap<>();
+    Map< String, List < String > > contents = new ConcurrentHashMap<>();
+
+    /** {@inheritDoc} */
+    @Override
+    public String getProviderInfo() {
+        return this.getClass().getName();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void initialize( final WikiEngine engine, final Properties properties ) throws NoRequiredPropertyException, IOException {
+        this.engine = engine;
+        try {
+            putPageText( new WikiPage( engine, "page1" ), "blablablabla" );
+            putPageText( new WikiPage( engine, "page2" ), "bleblebleble" );
+            putPageText( new WikiPage( engine, "page3" ), "blibliblibli" );
+        } catch( final ProviderException e ) {
+            throw new IOException( e.getMessage(), e );
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void putPageText( final WikiPage page, final String text ) throws ProviderException {
+        page.setVersion( page.getVersion() + 1 );
+        page.setLastModified( new Date() );
+        if( pageExists( page.getName() ) ) {
+            pages.get( page.getName() ).add( page );
+            contents.get( page.getName() ).add( text );
+        } else {
+            pages.put( page.getName(), new ArrayList<>( Arrays.asList( page ) ) );
+            contents.put( page.getName(), new ArrayList<>( Arrays.asList( text ) ) );
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public boolean pageExists( final String page ) {
+        return pages.get( page ) != null && contents.get( page ) != null;
+    }
+
+    < S > S executeIfPageExists( final String page, final int version, final Supplier< S > s ) throws ProviderException {
+        if( pageExists( page, version ) ) {
+            try {
+                return s.get();
+            } catch( final Exception e ) {
+                throw new ProviderException( e.getMessage() );
+            }
+        }
+        return null;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public boolean pageExists( final String page, final int version ) {
+        return pages.get( page ) != null    && pages.get( page ).size() >= version &&
+               contents.get( page ) != null && contents.get( page ).size() >= version;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public Collection< SearchResult > findPages( final QueryItem[] query ) {
+        final TreeSet< SearchResult > res = new TreeSet<>( new SearchResultComparator() );
+        final SearchMatcher matcher = new SearchMatcher( engine, query );
+        final Map< String, WikiPage > wikipages = pages.entrySet()
+                                                       .stream()
+                                                       .map( e -> new AbstractMap.SimpleEntry<>( e.getKey(), e.getValue().get( e.getValue().size() - 1 ) ) )
+                                                       .collect( Collectors.toMap( AbstractMap.SimpleEntry::getKey, AbstractMap.SimpleEntry::getValue ) );
+        for( final String wikipage : wikipages.keySet() ) {
+            final String pagetext = contents.get( wikipage ).get( contents.get( wikipage ).size() - 1 );
+            try {
+                final SearchResult comparison = matcher.matchPageContent( wikipage, pagetext );
+                if( comparison != null ) {
+                    res.add( comparison );
+                }
+            } catch( final IOException e ) {
+                // ok to ignore, shouldn't happen
+            }
+        }
+        return res;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public WikiPage getPageInfo( final String page, final int version ) throws ProviderException {
+        return executeIfPageExists( page, version, () -> pages.get( page ).get( version ) );
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public Collection< WikiPage > getAllPages() throws ProviderException {
+        return pages.values().stream().map( versions -> versions.get( versions.size() - 1 ) ).collect( Collectors.toList() );
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public Collection< WikiPage > getAllChangedSince( final Date date ) {
+        try {
+            return getAllPages();
+        } catch( final ProviderException e ) {
+            return Collections.emptyList();
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public int getPageCount() throws ProviderException {
+        return pages.size();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public List< WikiPage > getVersionHistory( final String page ) throws ProviderException {
+        return pageExists( page ) ? pages.get( page ) : Collections.emptyList();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String getPageText( final String page, final int version ) throws ProviderException {
+        return executeIfPageExists( page, version, () -> {
+            final int v = version == WikiProvider.LATEST_VERSION ? contents.get( page ).size() - 1 : version;
+            return contents.get( page ).get( v );
+        } );
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void deleteVersion( final String pageName, final int version ) throws ProviderException {
+        executeIfPageExists( pageName, version, () -> {
+            pages.get( pageName ).remove( version );
+            contents.get( pageName ).remove( version );
+            return null;
+        } );
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void deletePage( final String pageName ) throws ProviderException {
+        pages.remove( pageName );
+        contents.remove( pageName );
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void movePage( final String from, final String to ) throws ProviderException {
+        if( pageExists( to ) ) {
+            throw new ProviderException( to + " page already exists, can't move there" );
+        }
+        pages.put( to, pages.get( from ) );
+        contents.put( to, contents.get( from ) );
+        pages.remove( from );
+        contents.remove( from );
+    }
+
+}