    001/*
    002    Licensed to the Apache Software Foundation (ASF) under one
    003    or more contributor license agreements.  See the NOTICE file
    004    distributed with this work for additional information
    005    regarding copyright ownership.  The ASF licenses this file
    006    to you under the Apache License, Version 2.0 (the
    007    "License"); you may not use this file except in compliance
    008    with the License.  You may obtain a copy of the License at
    009
    010       http://www.apache.org/licenses/LICENSE-2.0
    011
    012    Unless required by applicable law or agreed to in writing,
    013    software distributed under the License is distributed on an
    014    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
    015    KIND, either express or implied.  See the License for the
    016    specific language governing permissions and limitations
    017    under the License.
    018 */
    019package org.apache.wiki.search;
    020
    021import org.apache.logging.log4j.LogManager;
    022import org.apache.logging.log4j.Logger;
    023import org.apache.wiki.api.core.Attachment;
    024import org.apache.wiki.api.core.Context;
    025import org.apache.wiki.api.core.Engine;
    026import org.apache.wiki.api.core.Page;
    027import org.apache.wiki.api.exceptions.NoRequiredPropertyException;
    028import org.apache.wiki.api.exceptions.ProviderException;
    029import org.apache.wiki.api.providers.PageProvider;
    030import org.apache.wiki.api.search.QueryItem;
    031import org.apache.wiki.api.search.SearchResult;
    032import org.apache.wiki.attachment.AttachmentManager;
    033import org.apache.wiki.auth.AuthorizationManager;
    034import org.apache.wiki.auth.permissions.PagePermission;
    035import org.apache.wiki.pages.PageManager;
    036
    037import java.io.IOException;
    038import java.util.Collection;
    039import java.util.Iterator;
    040import java.util.List;
    041import java.util.Properties;
    042import java.util.StringTokenizer;
    043import java.util.TreeSet;
    044
    045/**
    046 *  Interface for the search providers that handle searching the Wiki
    047 *
    048 *  @since 2.2.21.
    049 */
    050public class BasicSearchProvider implements SearchProvider {
    051
    052    private static final Logger LOG = LogManager.getLogger( BasicSearchProvider.class );
    053    private Engine m_engine;
    054
    055    /**
    056     *  {@inheritDoc}
    057     */
    058    @Override
    059    public void initialize( final Engine engine, final Properties props ) throws NoRequiredPropertyException, IOException {
    060        m_engine = engine;
    061    }
    062
    063    /**
    064     *  {@inheritDoc}
    065     */
    066    @Override
    067    public void pageRemoved( final Page page ) {}
    068
    069    /**
    070     *  {@inheritDoc}
    071     */
    072    @Override
    073    public void reindexPage( final Page page ) {}
    074
    075    /**
    076     *  Parses a query into something that we can use.
    077     *
    078     *  @param query A query string.
    079     *  @return A parsed array.
    080     */
    081    public QueryItem[] parseQuery( final String query) {
    082        final StringTokenizer st = new StringTokenizer( query, " \t," );
    083        final QueryItem[] items = new QueryItem[st.countTokens()];
    084        int word = 0;
    085
    086        LOG.debug("Expecting "+items.length+" items");
    087
    088        //  Parse incoming search string
    089        while( st.hasMoreTokens() ) {
    090            LOG.debug( "Item " + word );
    091            String token = st.nextToken().toLowerCase();
    092
    093            items[ word ] = new QueryItem();
    094
    095            switch( token.charAt( 0 ) ) {
    096            case '+':
    097                items[ word ].type = QueryItem.REQUIRED;
    098                token = token.substring( 1 );
    099                LOG.debug( "Required word: " + token );
    100                break;
    101
    102            case '-':
    103                items[ word ].type = QueryItem.FORBIDDEN;
    104                token = token.substring( 1 );
    105                LOG.debug( "Forbidden word: " + token );
    106                break;
    107
    108            default:
    109                items[ word ].type = QueryItem.REQUESTED;
    110                LOG.debug( "Requested word: " + token );
    111                break;
    112            }
    113
    114            items[ word++ ].word = token;
    115        }
    116
    117        return items;
    118    }
    119
    120    private String attachmentNames( final Page page ) {
    121        if( m_engine.getManager( AttachmentManager.class ).hasAttachments( page ) ) {
    122            final List< Attachment > attachments;
    123            try {
    124                attachments = m_engine.getManager( AttachmentManager.class ).listAttachments( page );
    125            } catch( final ProviderException e ) {
    126                LOG.error( "Unable to get attachments for page", e );
    127                return "";
    128            }
    129
    130            final StringBuilder attachmentNames = new StringBuilder();
    131            for( final Iterator< Attachment > it = attachments.iterator(); it.hasNext(); ) {
    132                final Attachment att = it.next();
    133                attachmentNames.append( att.getName() );
    134                if( it.hasNext() ) {
    135                    attachmentNames.append( " " );
    136                }
    137            }
    138            return attachmentNames.toString();
    139        }
    140
    141        return "";
    142    }
    143
    144    private Collection< SearchResult > findPages( final QueryItem[] query, final Context wikiContext ) {
    145        final TreeSet< SearchResult > res = new TreeSet<>( new SearchResultComparator() );
    146        final SearchMatcher matcher = new SearchMatcher( m_engine, query );
    147        final Collection< Page > allPages;
    148        try {
    149            allPages = m_engine.getManager( PageManager.class ).getAllPages();
    150        } catch( final ProviderException pe ) {
    151            LOG.error( "Unable to retrieve page list", pe );
    152            return null;
    153        }
    154
    155        final AuthorizationManager mgr = m_engine.getManager( AuthorizationManager.class );
    156
    157        for( final Page page : allPages ) {
    158            try {
    159                if( page != null ) {
    160                    final PagePermission pp = new PagePermission( page, PagePermission.VIEW_ACTION );
    161                    if( wikiContext == null || mgr.checkPermission( wikiContext.getWikiSession(), pp ) ) {
    162                        final String pageName = page.getName();
    163                        final String pageContent =
    164                                m_engine.getManager( PageManager.class ).getPageText( pageName, PageProvider.LATEST_VERSION ) + attachmentNames( page );
    165                        final SearchResult comparison = matcher.matchPageContent( pageName, pageContent );
    166                        if( comparison != null ) {
    167                            res.add( comparison );
    168                        }
    169                    }
    170                }
    171            } catch( final ProviderException pe ) {
    172                LOG.error( "Unable to retrieve page from cache", pe );
    173            } catch( final IOException ioe ) {
    174                LOG.error( "Failed to search page", ioe );
    175            }
    176        }
    177
    178        return res;
    179    }
    180
    181    /**
    182     *  {@inheritDoc}
    183     */
    184    @Override
    185    public Collection< SearchResult > findPages( final String query, final Context wikiContext ) {
    186        return findPages( parseQuery( query ), wikiContext );
    187    }
    188
    189    /**
    190     *  {@inheritDoc}
    191     */
    192    @Override
    193    public String getProviderInfo() {
    194        return "BasicSearchProvider";
    195    }
    196
    197}