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.log4j.Logger;
022import org.apache.wiki.api.core.Attachment;
023import org.apache.wiki.api.core.Context;
024import org.apache.wiki.api.core.Engine;
025import org.apache.wiki.api.core.Page;
026import org.apache.wiki.api.exceptions.NoRequiredPropertyException;
027import org.apache.wiki.api.exceptions.ProviderException;
028import org.apache.wiki.api.providers.PageProvider;
029import org.apache.wiki.api.search.QueryItem;
030import org.apache.wiki.api.search.SearchResult;
031import org.apache.wiki.attachment.AttachmentManager;
032import org.apache.wiki.auth.AuthorizationManager;
033import org.apache.wiki.auth.permissions.PagePermission;
034import org.apache.wiki.pages.PageManager;
035
036import java.io.IOException;
037import java.util.Collection;
038import java.util.Iterator;
039import java.util.List;
040import java.util.Properties;
041import java.util.StringTokenizer;
042import java.util.TreeSet;
043
044/**
045 *  Interface for the search providers that handle searching the Wiki
046 *
047 *  @since 2.2.21.
048 */
049public class BasicSearchProvider implements SearchProvider {
050
051    private static final Logger log = Logger.getLogger( BasicSearchProvider.class );
052    private Engine m_engine;
053
054    /**
055     *  {@inheritDoc}
056     */
057    @Override
058    public void initialize( final Engine engine, final Properties props ) throws NoRequiredPropertyException, IOException {
059        m_engine = engine;
060    }
061
062    /**
063     *  {@inheritDoc}
064     */
065    @Override
066    public void pageRemoved( final Page page ) {}
067
068    /**
069     *  {@inheritDoc}
070     */
071    @Override
072    public void reindexPage( final Page page ) {}
073
074    /**
075     *  Parses a query into something that we can use.
076     *  
077     *  @param query A query string.
078     *  @return A parsed array.
079     */
080    public QueryItem[] parseQuery( final String query) {
081        final StringTokenizer st = new StringTokenizer( query, " \t," );
082        final QueryItem[] items = new QueryItem[st.countTokens()];
083        int word = 0;
084
085        log.debug("Expecting "+items.length+" items");
086
087        //  Parse incoming search string
088        while( st.hasMoreTokens() ) {
089            log.debug( "Item " + word );
090            String token = st.nextToken().toLowerCase();
091
092            items[ word ] = new QueryItem();
093
094            switch( token.charAt( 0 ) ) {
095            case '+':
096                items[ word ].type = QueryItem.REQUIRED;
097                token = token.substring( 1 );
098                log.debug( "Required word: " + token );
099                break;
100
101            case '-':
102                items[ word ].type = QueryItem.FORBIDDEN;
103                token = token.substring( 1 );
104                log.debug( "Forbidden word: " + token );
105                break;
106
107            default:
108                items[ word ].type = QueryItem.REQUESTED;
109                log.debug( "Requested word: " + token );
110                break;
111            }
112
113            items[ word++ ].word = token;
114        }
115
116        return items;
117    }
118
119    private String attachmentNames( final Page page ) {
120        if( m_engine.getManager( AttachmentManager.class ).hasAttachments( page ) ) {
121            final List< Attachment > attachments;
122            try {
123                attachments = m_engine.getManager( AttachmentManager.class ).listAttachments( page );
124            } catch( final ProviderException e ) {
125                log.error( "Unable to get attachments for page", e );
126                return "";
127            }
128
129            final StringBuilder attachmentNames = new StringBuilder();
130            for( final Iterator< Attachment > it = attachments.iterator(); it.hasNext(); ) {
131                final Attachment att = it.next();
132                attachmentNames.append( att.getName() );
133                if( it.hasNext() ) {
134                    attachmentNames.append( " " );
135                }
136            }
137            return attachmentNames.toString();
138        }
139
140        return "";
141    }
142
143    private Collection< SearchResult > findPages( final QueryItem[] query, final Context wikiContext ) {
144        final TreeSet< SearchResult > res = new TreeSet<>( new SearchResultComparator() );
145        final SearchMatcher matcher = new SearchMatcher( m_engine, query );
146        final Collection< Page > allPages;
147        try {
148            allPages = m_engine.getManager( PageManager.class ).getAllPages();
149        } catch( final ProviderException pe ) {
150            log.error( "Unable to retrieve page list", pe );
151            return null;
152        }
153
154        final AuthorizationManager mgr = m_engine.getManager( AuthorizationManager.class );
155
156        for( final Page page : allPages ) {
157            try {
158                if( page != null ) {
159                    final PagePermission pp = new PagePermission( page, PagePermission.VIEW_ACTION );
160                    if( wikiContext == null || mgr.checkPermission( wikiContext.getWikiSession(), pp ) ) {
161                        final String pageName = page.getName();
162                        final String pageContent =
163                                m_engine.getManager( PageManager.class ).getPageText( pageName, PageProvider.LATEST_VERSION ) + attachmentNames( page );
164                        final SearchResult comparison = matcher.matchPageContent( pageName, pageContent );
165                        if( comparison != null ) {
166                            res.add( comparison );
167                        }
168                    }
169                }
170            } catch( final ProviderException pe ) {
171                log.error( "Unable to retrieve page from cache", pe );
172            } catch( final IOException ioe ) {
173                log.error( "Failed to search page", ioe );
174            }
175        }
176
177        return res;
178    }
179
180    /**
181     *  {@inheritDoc}
182     */
183    @Override
184    public Collection< SearchResult > findPages( final String query, final Context wikiContext ) {
185        return findPages( parseQuery( query ), wikiContext );
186    }
187
188    /**
189     *  {@inheritDoc}
190     */
191    @Override
192    public String getProviderInfo() {
193        return "BasicSearchProvider";
194    }
195
196}