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}