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}