/*
* See the NOTICE file distributed with this work for additional
* information regarding copyright ownership.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.xwiki.search.solr.script;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Provider;
import javax.inject.Singleton;
import org.apache.solr.common.SolrDocument;
import org.slf4j.Logger;
import org.xwiki.component.annotation.Component;
import org.xwiki.model.EntityType;
import org.xwiki.model.reference.DocumentReference;
import org.xwiki.model.reference.DocumentReferenceResolver;
import org.xwiki.model.reference.EntityReference;
import org.xwiki.model.reference.EntityReferenceResolver;
import org.xwiki.script.service.ScriptService;
import org.xwiki.search.solr.internal.api.FieldUtils;
import org.xwiki.search.solr.internal.api.SolrIndexer;
import org.xwiki.security.authorization.AuthorizationManager;
import org.xwiki.security.authorization.Right;
import com.xpn.xwiki.XWikiContext;
/**
* Script service exposing interaction with the {@link SolrIndexer}. Queries on the index are performed using XWiki's <a
* href="http://extensions.xwiki.org/xwiki/bin/view/Extension/Query+Module">Query Module API</a> with query type "solr".
*
* @version $Id: 6b53e96a9e24a0523b63e4d85154337562acdcf7 $
* @since 4.3M2
*/
@Component
@Named("solr")
@Singleton
public class SolrIndexScriptService implements ScriptService
{
/**
* Field name of the last API exception inserted in context.
*/
public static final String CONTEXT_LASTEXCEPTION = "lastexception";
/**
* Logging framework.
*/
@Inject
private Logger logger;
/**
* Wrapped {@link SolrIndex} component.
*/
@Inject
private SolrIndexer solrIndexer;
/**
* Used to check rights.
*/
@Inject
private AuthorizationManager authorization;
/**
* Used to access the current {@link XWikiContext}.
*/
@Inject
private Provider<XWikiContext> xcontextProvider;
/**
* Used to extract a {@link DocumentReference} from a {@link SolrDocument}.
*/
@Inject
private DocumentReferenceResolver<SolrDocument> solrDocumentReferenceResolver;
/**
* Used to extract an {@link EntityReference} from a {@link SolrDocument}.
*/
@Inject
private EntityReferenceResolver<SolrDocument> solrEntityReferenceResolver;
/**
* Index an entity and all it's contained entities recursively.
* <p>
* Null reference means the whole farm.
*
* @param reference the reference to index.
*/
public void index(EntityReference reference)
{
clearException();
try {
checkAccessToWikiIndex(reference);
this.solrIndexer.index(reference, true);
} catch (Exception e) {
error(e);
}
}
/**
* Index multiple entities and all their contained entities recursively. This is a batch operation.
* <p>
* Null reference means the whole farm.
*
* @param references the references to index.
*/
public void index(List<EntityReference> references)
{
clearException();
try {
checkAccessToWikiIndex(references);
for (EntityReference reference : references) {
this.solrIndexer.index(reference, true);
}
} catch (Exception e) {
error(e);
}
}
/**
* Delete an indexed entity and all its contained entities recursively.
* <p>
* Null reference means the whole farm.
*
* @param reference the reference to delete from the index.
*/
public void delete(EntityReference reference)
{
clearException();
try {
checkAccessToWikiIndex(reference);
this.solrIndexer.delete(reference, true);
} catch (Exception e) {
error(e);
}
}
/**
* Delete multiple entities and all their contained entities recursively. This is a batch operation.
* <p>
* Null reference means the whole farm.
*
* @param references the references to delete from the index.
*/
public void delete(List<EntityReference> references)
{
clearException();
try {
checkAccessToWikiIndex(references);
for (EntityReference reference : references) {
this.solrIndexer.delete(reference, true);
}
} catch (Exception e) {
error(e);
}
}
/**
* @return the size of the index/delete queue
* @since 5.1RC1
*/
public int getQueueSize()
{
return this.solrIndexer.getQueueSize();
}
/**
* Extract a {@link DocumentReference} from the given {@link SolrDocument} (e.g. search result).
*
* @param document the {@link SolrDocument} to extract the {@link DocumentReference} from
* @param parameters the parameters to pass to the reference resolver (e.g. in case some reference components are
* missing)
* @return the reference to the document associated with the given {@link SolrDocument}
* @since 7.2RC1
*/
public DocumentReference resolveDocument(SolrDocument document, Object... parameters)
{
return this.solrDocumentReferenceResolver.resolve(document, parameters);
}
/**
* Extract an {@link EntityReference} from the given {@link SolrDocument} (e.g. search result). The entity type is
* inferred from the "type" field which must be specified and must have a valid value (that corresponds to an
* existing {@link EntityType}).
*
* @param document a {@link SolrDocument} to extract the {@link EntityReference} from (the "type" field must be
* specified)
* @param parameters the parameters to pass to the reference resolver (e.g. in case some reference components are
* missing)
* @return the reference to the entity associated with the given {@link SolrDocument}
* @since 7.2RC1
*/
public EntityReference resolve(SolrDocument document, Object... parameters)
{
EntityType type;
try {
type = EntityType.valueOf((String) document.get(FieldUtils.TYPE));
} catch (IllegalArgumentException e) {
return null;
} catch (NullPointerException e) {
return null;
}
return resolve(document, type, parameters);
}
/**
* Extract an {@link EntityReference} of the specified type from the given {@link SolrDocument} (e.g. search
* result).
*
* @param document a {@link SolrDocument} to extract the {@link EntityReference} from
* @param type the entity type
* @param parameters the parameters to pass to the reference resolver (e.g. in case some reference components are
* missing)
* @return the reference to the entity associated with the given {@link SolrDocument}
* @since 7.2RC1
*/
public EntityReference resolve(SolrDocument document, EntityType type, Object... parameters)
{
return this.solrEntityReferenceResolver.resolve(document, type, parameters);
}
/**
* Log exception and store the exception in the context.
*
* @param errorMessage the error message to log.
* @param e the caught exception.
* @see #CONTEXT_LASTEXCEPTION
*/
private void error(String errorMessage, Exception e)
{
String errorMessageToLog = errorMessage;
if (errorMessageToLog == null) {
errorMessageToLog = e.getMessage();
}
this.logger.error(errorMessageToLog, e);
this.xcontextProvider.get().put(CONTEXT_LASTEXCEPTION, e);
}
/**
* Log exception and store it in the context. The logged message is the exception's message. This allows the
* underlying component to define it's messages and removes duplication.
*
* @param e the caught exception
*/
private void error(Exception e)
{
error(null, e);
}
/**
* Clear the last exception from the context.
*/
private void clearException()
{
this.xcontextProvider.get().remove(CONTEXT_LASTEXCEPTION);
}
/**
* Check the current user's access to alter the index of the wiki owning the given referenced entity.
*
* @param reference the reference whose owning wiki to check.
* @throws IllegalAccessException if the user is not allowed or if problems occur.
*/
private void checkAccessToWikiIndex(EntityReference reference) throws IllegalAccessException
{
EntityReference wikiReference = reference.extractReference(EntityType.WIKI);
XWikiContext xcontext = this.xcontextProvider.get();
DocumentReference userReference = xcontext.getUserReference();
DocumentReference programmingUserReference = xcontext.getDoc().getContentAuthorReference();
if (!this.authorization.hasAccess(Right.ADMIN, userReference, wikiReference)
|| !this.authorization.hasAccess(Right.PROGRAM, programmingUserReference, wikiReference)) {
throw new IllegalAccessException(String.format(
"The user '%s' is not allowed to alter the index for the entity '%s'", userReference, reference));
}
}
/**
* Check the current user's access to alter the index of the wikis owning the given referenced entities.
*
* @param references the references whose owning wikis to check.
* @throws IllegalAccessException if the user is not allowed for at least one of the passed references or if
* problems occur.
*/
private void checkAccessToWikiIndex(List<EntityReference> references) throws IllegalAccessException
{
Set<EntityReference> representatives = new HashSet<EntityReference>();
for (EntityReference reference : references) {
EntityReference wikiReference = reference.extractReference(EntityType.WIKI);
if (!representatives.contains(wikiReference)) {
checkAccessToWikiIndex(wikiReference);
representatives.add(wikiReference);
}
}
}
}