/*
* 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.wysiwyg.server.internal.wiki;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Provider;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.xwiki.bridge.DocumentAccessBridge;
import org.xwiki.csrf.CSRFToken;
import org.xwiki.gwt.wysiwyg.client.wiki.Attachment;
import org.xwiki.gwt.wysiwyg.client.wiki.EntityConfig;
import org.xwiki.gwt.wysiwyg.client.wiki.ResourceReference;
import org.xwiki.gwt.wysiwyg.client.wiki.WikiPage;
import org.xwiki.gwt.wysiwyg.client.wiki.WikiPageReference;
import org.xwiki.gwt.wysiwyg.client.wiki.WikiService;
import org.xwiki.model.reference.AttachmentReference;
import org.xwiki.model.reference.DocumentReference;
import org.xwiki.model.reference.SpaceReference;
import org.xwiki.model.reference.SpaceReferenceResolver;
import org.xwiki.model.reference.WikiReference;
import org.xwiki.query.Query;
import org.xwiki.query.QueryException;
import org.xwiki.query.QueryFilter;
import org.xwiki.query.QueryManager;
import org.xwiki.wiki.descriptor.WikiDescriptorManager;
import org.xwiki.wysiwyg.server.wiki.EntityReferenceConverter;
import org.xwiki.wysiwyg.server.wiki.LinkService;
/**
* Put here only the methods that can be implemented without depending on the old XWiki core.
*
* @version $Id: 0b8172e9a850f27cd23b90df50ae3164d8729f5c $
* @since 3.2
*/
public abstract class AbstractWikiService implements WikiService
{
/**
* Logger.
*/
@Inject
protected Logger logger;
/**
* The object used to convert between client and server entity reference.
*/
@Inject
protected EntityReferenceConverter entityReferenceConverter;
/**
* The component used to create queries.
*/
@Inject
private QueryManager queryManager;
/**
* Provides the query filter used to filter hidden documents. We need to get the filter through a provider because
* it uses a per-lookup instantiation strategy.
*/
@Inject
@Named("hidden")
private Provider<QueryFilter> hiddenDocumentsQueryFilterProvider;
/**
* The service used to create links.
*/
@Inject
private LinkService linkService;
/**
* The component used to access documents. This is temporary till XWiki model is moved into components.
*/
@Inject
private DocumentAccessBridge documentAccessBridge;
/**
* The component that protects us against cross site request forgery by using a secret token validation mechanism.
* The secret token is added to the query string of the upload URL and then checked in the upload action when files
* are uploaded.
*/
@Inject
private CSRFToken csrf;
@Inject
private SpaceReferenceResolver<String> spaceResolver;
@Inject
private WikiDescriptorManager wikiDescriptorManager;
@Override
public Boolean isMultiWiki()
{
return true;
}
@Override
public List<String> getSpaceNames(String wikiName)
{
try {
return queryManager.getNamedQuery("getSpaces").setWiki(wikiName)
.addFilter(hiddenDocumentsQueryFilterProvider.get()).execute();
} catch (QueryException e) {
logger.error("Failed to get the list of spaces.", e);
return Collections.emptyList();
}
}
@Override
public List<String> getPageNames(String wikiName, String spaceName)
{
String statement =
"select distinct doc.space, doc.name from XWikiDocument as doc where doc.space = :space "
+ "order by doc.space, doc.name";
Query query = createHQLQuery(statement);
query.setWiki(wikiName).bindValue("space", spaceName);
List<String> pagesNames = new ArrayList<String>();
for (DocumentReference documentReference : searchDocumentReferences(query)) {
pagesNames.add(documentReference.getName());
}
return pagesNames;
}
@Override
public List<WikiPage> getRecentlyModifiedPages(String wikiName, int offset, int limit)
{
String statement =
"select distinct doc.space, doc.name, doc.date from XWikiDocument as doc where doc.author = :author "
+ "order by doc.date desc, doc.space, doc.name";
Query query = createHQLQuery(statement);
query.setWiki(wikiName).setOffset(offset).setLimit(limit);
query.bindValue("author", getCurrentUserRelativeTo(wikiName));
return getWikiPages(searchDocumentReferences(query));
}
/**
* @param wikiName the name of a wiki
* @return the name of the current user, relative to the specified wiki
*/
protected abstract String getCurrentUserRelativeTo(String wikiName);
@Override
public List<WikiPage> getMatchingPages(String wikiName, String keyword, int offset, int limit)
{
StringBuilder statement = new StringBuilder();
statement.append("select distinct doc.space, doc.name from XWikiDocument as doc where ");
statement.append("(lower(doc.title) like '%'||:keyword||'%' or lower(doc.fullName) like '%'||:keyword||'%')");
statement.append(" order by doc.space, doc.name");
Query query = createHQLQuery(statement.toString());
query.setWiki(wikiName).setOffset(offset).setLimit(limit);
query.bindValue("keyword", keyword.toLowerCase());
return getWikiPages(searchDocumentReferences(query));
}
/**
* Creates a new HQL query. Converts {@link QueryException} to a {@link RuntimeException}.
*
* @param statement the query statement
* @return the created query
*/
private Query createHQLQuery(String statement)
{
try {
return queryManager.createQuery(statement, Query.HQL).addFilter(hiddenDocumentsQueryFilterProvider.get());
} catch (QueryException e) {
throw new RuntimeException(e);
}
}
/**
* Helper function to create a list of {@link WikiPage}s from a list of document references.
*
* @param documentReferences a list of document references
* @return the list of {@link WikiPage}s corresponding to the given document references
*/
protected abstract List<WikiPage> getWikiPages(List<DocumentReference> documentReferences);
/**
* Executes the given query and converts the result to a list of document references. The first two columns in each
* result row must be the document space and name respectively.
*
* @param query the query to be executed
* @return the list of document references matching the result of executing the given query
*/
private List<DocumentReference> searchDocumentReferences(Query query)
{
try {
WikiReference wikiReference = new WikiReference(query.getWiki());
List<DocumentReference> documentReferences = new ArrayList<DocumentReference>();
List<Object[]> results = query.execute();
for (Object[] result : results) {
SpaceReference spaceReference = this.spaceResolver.resolve((String) result[0], wikiReference);
documentReferences.add(new DocumentReference((String) result[1], spaceReference));
}
return documentReferences;
} catch (QueryException e) {
throw new RuntimeException(e);
}
}
@Override
public EntityConfig getEntityConfig(org.xwiki.gwt.wysiwyg.client.wiki.EntityReference origin,
ResourceReference destination)
{
return linkService.getEntityConfig(origin, destination);
}
@Override
public ResourceReference parseLinkReference(String linkReference,
org.xwiki.gwt.wysiwyg.client.wiki.EntityReference baseReference)
{
return linkService.parseLinkReference(linkReference, baseReference);
}
@Override
public Attachment getAttachment(org.xwiki.gwt.wysiwyg.client.wiki.AttachmentReference clientAttachmentReference)
{
AttachmentReference attachmentReference = entityReferenceConverter.convert(clientAttachmentReference);
try {
if (StringUtils.isBlank(documentAccessBridge.getAttachmentVersion(attachmentReference))) {
logger.warn("Failed to get attachment: [{}] not found.", attachmentReference.getName());
return null;
}
} catch (Exception e) {
logger.error("Failed to get attachment: there was a problem with getting the document on the server.", e);
return null;
}
Attachment attach = new Attachment();
attach.setReference(clientAttachmentReference.getEntityReference());
attach.setUrl(documentAccessBridge.getAttachmentURL(attachmentReference, false));
return attach;
}
@Override
public List<Attachment> getImageAttachments(WikiPageReference reference)
{
List<Attachment> imageAttachments = new ArrayList<Attachment>();
List<Attachment> allAttachments = getAttachments(reference);
for (Attachment attachment : allAttachments) {
if (attachment.getMimeType().startsWith("image/")) {
imageAttachments.add(attachment);
}
}
return imageAttachments;
}
@Override
public String getUploadURL(WikiPageReference reference)
{
String queryString = "form_token=" + csrf.getToken();
return documentAccessBridge.getDocumentURL(entityReferenceConverter.convert(reference), "upload", queryString,
null);
}
@Override
public List<String> getVirtualWikiNames()
{
try {
List<String> wikis = new ArrayList<String>(this.wikiDescriptorManager.getAllIds());
Collections.sort(wikis);
return wikis;
} catch (Exception e) {
this.logger.error("Failed to retrieve the list of wikis.", e);
return Collections.emptyList();
}
}
}