/**
* Copyright 2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 the "License";
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
package io.neba.core.util;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.resource.ResourceResolver;
import javax.jcr.Node;
import javax.jcr.RepositoryException;
import java.util.Iterator;
import java.util.NoSuchElementException;
import static io.neba.api.Constants.SYNTHETIC_RESOURCETYPE_ROOT;
import static org.apache.sling.api.resource.ResourceUtil.isSyntheticResource;
/**
* Iterates the type hierarchy of a {@link Resource} starting with the resource's
* "sling:resourceType". {@link #next()} will either yield the current resource
* {@link Resource#getResourceSuperType() supertype} or, if this supertype does
* not exist, the resource supertype of the resource identified by the current
* {@link Resource#getResourceType() resource type}. <br />
* Note that this iterator can be empty. {@link Resource#getResourceType()}
* falls back to the {@link javax.jcr.Node#getPrimaryNodeType() primary node type}
* if no "sling:resourceType" property is set; however the node type hierarchy
* is covered by the {@link NodeTypeHierarchyIterator} and is not used by this iterator.
*
* @see ResourceResolver#getParentResourceType(String)
* @author Olaf Otto
*/
public class ResourceTypeHierarchyIterator implements Iterator<String>, Iterable<String> {
/**
* @param resource must not be <code>null</code>.
* @return never <code>null</code>.
*/
public static ResourceTypeHierarchyIterator typeHierarchyOf(final Resource resource) {
return new ResourceTypeHierarchyIterator(resource);
}
private final ResourceResolver resolver;
private String currentResourceType;
private String nextResourceType;
private boolean isSyntheticResource;
/**
* @param resource must not be <code>null</code>.
*/
public ResourceTypeHierarchyIterator(final Resource resource) {
if (resource == null) {
throw new IllegalArgumentException("Constructor parameter resource must not be null.");
}
this.resolver = resource.getResourceResolver();
this.isSyntheticResource = isSyntheticResource(resource);
if (this.isSyntheticResource) {
// Synthetic resources do not represent nodes, thus their type is
// intentionally provided by the resource implementation
// and does not fall back to the primary type of a node.
this.currentResourceType = resource.getResourceType();
} else {
String resourceType = resource.getResourceType();
Node node = resource.adaptTo(Node.class);
if (node != null) {
// If a resource represents a node, the resource type must
// not be the node type since we intend to traverse the sling:resourceType hierarchy.
// However, the resourceType provided by resource#getResourceType could be the node type since
// Resource#getResourceType falls back to the node type of no sling:resourceType is specified.
try {
String nodeType = node.getPrimaryNodeType().getName();
if (!nodeType.equals(resourceType)) {
this.currentResourceType = resourceType;
}
} catch (RepositoryException e) {
throw new RuntimeException("Unable to obtain the node type.", e);
}
} else {
this.currentResourceType = resourceType;
}
}
this.nextResourceType = this.currentResourceType;
}
public boolean hasNext() {
return this.nextResourceType != null || this.currentResourceType != null && resolveNext();
}
public String next() {
if (!hasNext()) {
throw new NoSuchElementException();
}
String type = this.nextResourceType;
this.nextResourceType = null;
this.currentResourceType = type;
return type;
}
private boolean resolveNext() {
String nextResourceType = this.resolver.getParentResourceType(this.currentResourceType);
if (nextResourceType == null && isProvideSyntheticResourceRoot()) {
nextResourceType = SYNTHETIC_RESOURCETYPE_ROOT;
}
this.nextResourceType = nextResourceType;
return nextResourceType != null;
}
/**
* This iterator provides a virtual resource type root
* common to all synthetic resources to enable mapping to all
* resources including synthetic ones.
*
* @see io.neba.api.Constants#SYNTHETIC_RESOURCETYPE_ROOT
*/
public boolean isProvideSyntheticResourceRoot() {
return this.isSyntheticResource && !SYNTHETIC_RESOURCETYPE_ROOT.equals(this.currentResourceType);
}
public void remove() {
throw new UnsupportedOperationException("The resource hierarchy iterator is read-only.");
}
@Override
public Iterator<String> iterator() {
return this;
}
}