/*
* 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.url.internal.standard.entity;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.ImmutablePair;
import org.apache.commons.lang3.tuple.Pair;
import org.xwiki.model.EntityType;
import org.xwiki.model.reference.EntityReference;
import org.xwiki.model.reference.EntityReferenceResolver;
import org.xwiki.model.reference.WikiReference;
import org.xwiki.resource.CreateResourceReferenceException;
import org.xwiki.resource.ResourceType;
import org.xwiki.resource.UnsupportedResourceReferenceException;
import org.xwiki.resource.entity.EntityResourceAction;
import org.xwiki.resource.entity.EntityResourceReference;
import org.xwiki.resource.internal.entity.EntityResourceActionLister;
import org.xwiki.url.ExtendedURL;
import org.xwiki.url.internal.AbstractResourceReferenceResolver;
import org.xwiki.url.internal.standard.StandardURLConfiguration;
/**
* Common code for Entity Resource Reference Resolvers.
*
* @version $Id: c3edf35297b581d27c053502dcd06ca0a79ace44 $
* @since 6.3M1
*/
public abstract class AbstractEntityResourceReferenceResolver extends AbstractResourceReferenceResolver
{
private static final String VIEW_ACTION = "view";
private static final String DOWNLOAD_ACTION = "download";
private static final String DELATTACHMENT_ACTION = "delattachment";
private static final String VIEWATTACHREV_ACTION = "viewattachrev";
private static final String DOWNLOADREV_ACTION = "downloadrev";
/**
* @TODO: Remove when https://jira.xwiki.org/browse/XWIKI-12449 is implemented
*/
private static final String SKIN_ACTION = "skin";
/**
* List of Actions which use URLs of the format {@code /(actionname)/space1/space2/page/filename}.
*/
private static final List<String> FILE_ACTION_LIST =
Arrays.asList(DOWNLOAD_ACTION, DELATTACHMENT_ACTION, VIEWATTACHREV_ACTION, DOWNLOADREV_ACTION, SKIN_ACTION);
private StandardURLConfiguration configuration;
private EntityResourceActionLister entityResourceActionLister;
/**
* Used to resolve blanks in entity references when the URL doesn't specify all parts of an entity reference.
*/
private EntityReferenceResolver<EntityReference> defaultReferenceEntityReferenceResolver;
protected abstract WikiReference extractWikiReference(ExtendedURL url);
@Override
public EntityResourceReference resolve(ExtendedURL extendedURL, ResourceType type, Map<String, Object> parameters)
throws CreateResourceReferenceException, UnsupportedResourceReferenceException
{
EntityResourceReference reference = null;
// Extract the wiki reference from the URL
WikiReference wikiReference = extractWikiReference(extendedURL);
// See BinEntityResourceReferenceResolverTest to check the various cases supported.
List<String> pathSegments = extendedURL.getSegments();
List<String> spaceNames = null;
String pageName = null;
String attachmentName = null;
String action = VIEW_ACTION;
if (pathSegments.size() != 0) {
String firstSegment = pathSegments.get(0);
action = firstSegment;
// Generic parsing
// Handle actions specifying an attachment.
if (FILE_ACTION_LIST.contains(firstSegment) && pathSegments.size() >= 4) {
// Last segment is the attachment
attachmentName = pathSegments.get(pathSegments.size() - 1);
// Last but one segment is the page name
pageName = pathSegments.get(pathSegments.size() - 2);
// All segments in between are the space names
spaceNames = extractSpaceNames(pathSegments, 1, pathSegments.size() - 3);
} else {
// Handle actions not specifying any attachment.
Pair<String, Integer> actionAndStartPosition =
computeActionAndStartPosition(firstSegment, pathSegments);
action = actionAndStartPosition.getLeft();
int startPosition = actionAndStartPosition.getRight();
// Normally the last segment is always the page name but we want to handle a special case when we
// have "/view/something" and we wish in this case to consider that "something" is the space. This
// is to handle Nested Documents, so that the user can have a top level Nested Document
// (something.WebHome) and access it from /view/something. If we didn't handle this special case
// the user would get Main.something and thus wouldn't be able to access something.WebHome. He'd
// need to use /view/something/ which is not natural in the Nested Document mode.
if (pathSegments.size() - startPosition == 1) {
spaceNames = Arrays.asList(pathSegments.get(startPosition));
} else {
// Last segment is the page name
pageName = pathSegments.get(pathSegments.size() - 1);
// All segments in between are the space names
spaceNames = extractSpaceNames(pathSegments, startPosition, pathSegments.size() - 2);
}
}
}
if (reference == null) {
reference = new EntityResourceReference(
buildEntityReference(wikiReference, spaceNames, pageName, attachmentName),
EntityResourceAction.fromString(action));
}
copyParameters(extendedURL, reference);
return reference;
}
private Pair<String, Integer> computeActionAndStartPosition(String firstSegment, List<String> pathSegments)
{
String action;
int startPosition;
// - If the first segment is not an action name, then consider that the action is "view"
// (whether isViewActionHidden() is true or false)
// - If the first segment is an action name then always consider that it represents an action
// (whether isViewActionHidden is true or false), e.g. if the first space is called "view" then "view/view"
// will need to be used to produce a view URL for it.
//
// In addition we need to handle the special case of GWT resources, see AbstractExtendedURLResourceTypeResolver
// e.g. if we have "resources/js/xwiki/wysiwyg/xwe/MacroService.gwtrpc" as input then we need to return an
// action of "resources" and a document reference of "js.xwiki.wysiwyg.xwe.MacroService.gwtrpc"
// Note that "resources" is not a real action name which is why we ned a special handling.
// TODO: Remove this GWT handling and move it sto some more generic place
if (!this.entityResourceActionLister.listActions().contains(firstSegment)) {
if (pathSegments.size() > 0 && firstSegment.equals("resources")
&& pathSegments.get(pathSegments.size() - 1).endsWith(".gwtrpc"))
{
action = firstSegment;
startPosition = 1;
} else {
action = VIEW_ACTION;
startPosition = 0;
}
} else {
action = firstSegment;
startPosition = 1;
}
return new ImmutablePair<>(action, startPosition);
}
private List<String> extractSpaceNames(List<String> pathSegments, int startPosition, int stopPosition)
{
if (stopPosition < 0) {
return null;
}
List<String> spaceNames = new ArrayList<>();
ListIterator<String> iterator = pathSegments.listIterator(startPosition);
int total = stopPosition - startPosition + 1;
int count = 0;
while (count < total) {
spaceNames.add(iterator.next());
count++;
}
return spaceNames;
}
/**
* Normalize the extracted space/page to resolve empty/null values and replace them with default values.
*
* @param wikiReference the wiki reference as extracted from the URL
* @param spaceNames the space names as extracted from the URL (can be empty or null)
* @param pageName the page name as extracted from the URL (can be empty or null)
* @param attachmentName the attachment name as extracted from the URL (can be empty or null)
* @return the absolute Entity Reference
*/
private EntityReference buildEntityReference(WikiReference wikiReference, List<String> spaceNames, String pageName,
String attachmentName)
{
EntityReference reference = wikiReference;
EntityType entityType = EntityType.DOCUMENT;
if (spaceNames != null && !spaceNames.isEmpty()) {
EntityReference parent = reference;
for (String spaceName : spaceNames) {
if (!StringUtils.isEmpty(spaceName)) {
reference = new EntityReference(spaceName, EntityType.SPACE, parent);
parent = reference;
}
}
}
if (!StringUtils.isEmpty(pageName)) {
reference = new EntityReference(pageName, EntityType.DOCUMENT, reference);
}
if (!StringUtils.isEmpty(attachmentName)) {
reference = new EntityReference(attachmentName, EntityType.ATTACHMENT, reference);
entityType = EntityType.ATTACHMENT;
}
return this.defaultReferenceEntityReferenceResolver.resolve(reference, entityType);
}
}