/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 org.apache.jena.permissions.model.impl;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.function.Function;
import org.apache.jena.graph.Node;
import org.apache.jena.graph.Triple;
import org.apache.jena.permissions.SecuredItem;
import org.apache.jena.permissions.SecurityEvaluator;
import org.apache.jena.permissions.SecurityEvaluator.Action;
import org.apache.jena.permissions.impl.ItemHolder;
import org.apache.jena.permissions.impl.SecuredItemInvoker;
import org.apache.jena.permissions.model.SecuredModel;
import org.apache.jena.permissions.model.SecuredRDFList;
import org.apache.jena.permissions.model.SecuredRDFNode;
import org.apache.jena.permissions.utils.RDFListIterator;
import org.apache.jena.permissions.utils.RDFListSecFilter;
import org.apache.jena.rdf.model.*;
import org.apache.jena.shared.AddDeniedException;
import org.apache.jena.shared.AuthenticationRequiredException;
import org.apache.jena.shared.DeleteDeniedException;
import org.apache.jena.shared.ReadDeniedException;
import org.apache.jena.shared.UpdateDeniedException;
import org.apache.jena.util.iterator.ExtendedIterator;
import org.apache.jena.util.iterator.WrappedIterator;
import org.apache.jena.vocabulary.RDF;
public class SecuredRDFListImpl extends SecuredResourceImpl implements
SecuredRDFList {
// called plain node but still returns a secured node
private class PlainNodeMap implements Function<RDFList, RDFNode> {
@Override
public RDFNode apply(final RDFList o) {
return SecuredRDFNodeImpl.getInstance(getModel(), o
.getRequiredProperty(listFirst()).getObject());
}
}
private class SecuredListMap implements Function<RDFList, SecuredRDFList> {
@Override
public SecuredRDFList apply(final RDFList o) {
return SecuredRDFListImpl.getInstance(getModel(), o);
}
}
private class SecuredNodeMap implements Function<RDFList, SecuredRDFNode> {
private Property p;
public SecuredNodeMap(Property p) {
this.p = p;
}
@Override
public SecuredRDFNode apply(final RDFList o) {
return SecuredRDFNodeImpl.getInstance(getModel(), o
.getRequiredProperty(p).getObject());
}
}
/**
* Get an instance of SecuredProperty
*
* @param securedModel
* the Secured Model to use.
* @param rdfList
* The rdfList to secure
* @return The SecuredProperty
*/
public static <T extends RDFList> SecuredRDFList getInstance(
final SecuredModel securedModel, final T rdfList) {
if (securedModel == null) {
throw new IllegalArgumentException(
"Secured securedModel may not be null");
}
if (rdfList == null) {
throw new IllegalArgumentException("RDFList may not be null");
}
// check that property has a securedModel.
RDFList goodList = rdfList;
if (goodList.getModel() == null) {
goodList = securedModel.createList(rdfList.asJavaList().iterator());
}
final ItemHolder<RDFList, SecuredRDFList> holder = new ItemHolder<RDFList, SecuredRDFList>(
goodList);
final SecuredRDFListImpl checker = new SecuredRDFListImpl(securedModel,
holder);
// if we are going to create a duplicate proxy, just return this
// one.
if (goodList instanceof SecuredRDFList) {
if (checker.isEquivalent((SecuredRDFList) goodList)) {
return (SecuredRDFList) goodList;
}
}
return holder.setSecuredItem(new SecuredItemInvoker(rdfList.getClass(),
checker));
}
/** Error message if validity check fails */
protected String m_errorMsg = null;
/** Pointer to the node that is the tail of the list */
protected RDFList m_tail = null;
/** The URI for the 'first' property in this list */
protected Property m_listFirst = RDF.first;
/** The URI for the 'rest' property in this list */
protected Property m_listRest = RDF.rest;
/** The URI for the 'nil' Resource in this list */
protected Resource m_listNil = RDF.nil;
/** The URI for the rdf:type of this list */
protected Resource m_listType = RDF.List;
private final ItemHolder<RDFList, SecuredRDFList> holder;
protected SecuredRDFListImpl(final SecuredModel securedModel,
final ItemHolder<RDFList, SecuredRDFList> holder) {
super(securedModel, holder);
this.holder = holder;
}
@Override
public void add(final RDFNode value) throws UpdateDeniedException,
AddDeniedException, AuthenticationRequiredException {
checkUpdate();
checkCreateNewList(value, listNil());
holder.getBaseItem().add(value);
}
@Override
public SecuredRDFList append(final Iterator<? extends RDFNode> nodes)
throws ReadDeniedException, AuthenticationRequiredException {
SecuredRDFList copy = copy();
if (nodes.hasNext()) {
if (((RDFList) copy.getBaseItem()).size() > 0) {
copy.concatenate(copy.getModel().createList(nodes));
} else {
copy = copy.getModel().createList(nodes);
}
}
return copy;
}
@Override
public RDFList append(final RDFList list) throws ReadDeniedException,
AuthenticationRequiredException {
if (holder.getBaseItem().isEmpty()) {
return list.size() == 0 ? ModelFactory.createDefaultModel()
.createList() : list.copy();
} else {
final RDFList copy = copy();
if (list.size() > 0) {
copy.concatenate(list.copy());
}
return copy;
}
}
@Override
public void apply(final ApplyFn fn) throws ReadDeniedException,
AuthenticationRequiredException {
// iterator() checks Read
final ExtendedIterator<RDFNode> i = iterator();
try {
while (i.hasNext()) {
fn.apply(i.next());
}
} finally {
i.close();
}
}
@Override
public void apply(final Set<Action> perms, final ApplyFn fn)
throws ReadDeniedException, AuthenticationRequiredException {
// iterator() checks Read
final ExtendedIterator<RDFNode> i = iterator(perms);
try {
while (i.hasNext()) {
fn.apply(i.next());
}
} finally {
i.close();
}
}
@Override
public List<RDFNode> asJavaList() throws ReadDeniedException,
AuthenticationRequiredException {
// iterator() checks Read
return iterator().toList();
}
/**
* Removes val from underlying list.
*
* @param val
* @return the modified RDFList.
*/
private RDFList baseRemove(final RDFList val) {
RDFList prev = null;
RDFList cell = holder.getBaseItem();
final boolean searching = true;
while (searching && !cell.isEmpty()) {
if (cell.equals(val)) {
// found the value to be removed
final RDFList tail = cell.getTail();
if (prev != null) {
prev.setTail(tail);
}
cell.removeProperties();
// return this unless we have removed the head element
return (prev == null) ? tail : this;
} else {
// not found yet
prev = cell;
cell = cell.getTail();
}
}
// not found
return this;
}
private void checkCreateNewList(final RDFNode value, final Resource tail)
throws AddDeniedException, AuthenticationRequiredException {
checkCreate(new Triple(SecurityEvaluator.FUTURE, listFirst().asNode(),
value.asNode()));
checkCreate(new Triple(SecurityEvaluator.FUTURE, listRest().asNode(),
tail.asNode()));
}
private Set<Statement> collectStatements(final Set<Action> actions) {
final Set<Statement> stmts = new HashSet<Statement>();
final ExtendedIterator<RDFList> iter = WrappedIterator.create(
new RDFListIterator(holder.getBaseItem())).filterKeep(
new RDFListSecFilter<RDFList>(this, actions));
try {
while (iter.hasNext()) {
stmts.addAll(iter.next().listProperties().toSet());
}
return stmts;
} finally {
iter.close();
}
}
@Override
public void concatenate(final Iterator<? extends RDFNode> nodes)
throws UpdateDeniedException, AddDeniedException,
AuthenticationRequiredException {
checkUpdate();
if (holder.getBaseItem().isEmpty()) {
// concatenating list onto the empty list is an error
throw new EmptyListUpdateException(
"Tried to concatenate onto the empty list");
} else {
Triple t = new Triple(SecurityEvaluator.FUTURE, listFirst()
.asNode(), Node.ANY);
if (!canCreate(t)) {
final List<RDFNode> list = new ArrayList<RDFNode>();
while (nodes.hasNext()) {
final RDFNode n = nodes.next();
t = new Triple(SecurityEvaluator.FUTURE, listFirst()
.asNode(), n.asNode());
checkCreate(t);
list.add(n);
}
holder.getBaseItem().concatenate(list.iterator());
} else {
holder.getBaseItem().concatenate(nodes);
}
}
}
@Override
public void concatenate(final RDFList list) throws UpdateDeniedException,
AddDeniedException, AuthenticationRequiredException {
checkUpdate();
if (holder.getBaseItem().isEmpty()) {
// concatenating list onto the empty list is an error
throw new EmptyListUpdateException(
"Tried to concatenate onto the empty list");
} else {
Triple t = new Triple(SecurityEvaluator.FUTURE, listFirst()
.asNode(), Node.ANY);
if (!canCreate(t)) {
final ExtendedIterator<RDFNode> iter = list.iterator();
try {
while (iter.hasNext()) {
t = new Triple(SecurityEvaluator.FUTURE, listFirst()
.asNode(), iter.next().asNode());
checkCreate(t);
}
} finally {
iter.close();
}
}
holder.getBaseItem().concatenate(list);
}
}
@Override
public SecuredRDFList cons(final RDFNode value)
throws UpdateDeniedException, AddDeniedException,
AuthenticationRequiredException {
checkUpdate();
checkCreateNewList(value, holder.getBaseItem());
return SecuredRDFListImpl.getInstance(getModel(), holder.getBaseItem()
.cons(value));
}
@Override
public boolean contains(final RDFNode value) throws ReadDeniedException,
AuthenticationRequiredException {
// iterator() checks Read
final ExtendedIterator<RDFNode> iter = iterator();
try {
while (iter.hasNext()) {
if (value.equals(iter.next())) {
return true;
}
}
return false;
} finally {
iter.close();
}
}
@Override
public SecuredRDFList copy() throws ReadDeniedException,
AuthenticationRequiredException {
SecuredRDFList retval = null;
if (canRead()) {
final ExtendedIterator<RDFNode> iter = getSecuredRDFListIterator(
Action.Read).mapWith(
list -> list.getRequiredProperty(listFirst()).getObject());
if (iter.hasNext()) {
retval = getModel().createList(iter);
} else {
retval = getModel().createList();
}
} else {
retval = getModel().createList();
}
return retval;
}
@Override
public SecuredRDFNode get(final int i) throws ReadDeniedException,
AuthenticationRequiredException {
checkRead();
final ExtendedIterator<SecuredRDFNode> iter = getSecuredRDFListIterator(
Action.Read).mapWith(new SecuredNodeMap(listFirst()));
int idx = 0;
try {
while (iter.hasNext()) {
if (i == idx) {
return iter.next();
} else {
idx++;
iter.next();
}
}
throw new ListIndexException();
} finally {
iter.close();
}
}
@Override
public SecuredRDFNode getHead() throws ReadDeniedException,
AuthenticationRequiredException {
checkRead();
Statement s = holder.getBaseItem().getRequiredProperty(listFirst());
checkRead(s);
return SecuredRDFNodeImpl.getInstance(getModel(), s.getObject());
}
private ExtendedIterator<RDFList> getSecuredRDFListIterator(
final Action perm) {
return WrappedIterator
.create(new RDFListIterator(holder.getBaseItem())).filterKeep(
new RDFListSecFilter<RDFList>(this, perm));
}
private ExtendedIterator<RDFList> getSecuredRDFListIterator(
final Set<Action> perm) {
return WrappedIterator
.create(new RDFListIterator(holder.getBaseItem())).filterKeep(
new RDFListSecFilter<RDFList>(this, perm));
}
@Override
public boolean getStrict() {
return holder.getBaseItem().getStrict();
}
@Override
public SecuredRDFList getTail() throws ReadDeniedException,
AuthenticationRequiredException {
checkRead();
Statement s = holder.getBaseItem().getRequiredProperty(listRest());
checkRead(s);
return SecuredRDFListImpl.getInstance(getModel(),
s.getObject().as(RDFList.class));
}
@Override
public String getValidityErrorMessage() throws ReadDeniedException,
AuthenticationRequiredException {
checkRead();
return holder.getBaseItem().getValidityErrorMessage();
}
@Override
public int indexOf(final RDFNode value) throws ReadDeniedException,
AuthenticationRequiredException {
checkRead();
final ExtendedIterator<SecuredRDFNode> iter = getSecuredRDFListIterator(
Action.Read).mapWith(new SecuredNodeMap(listFirst()));
try {
int retval = 0;
while (iter.hasNext()) {
if (value.equals(iter.next())) {
return retval;
} else {
retval++;
}
}
return -1;
} finally {
iter.close();
}
}
@Override
public int indexOf(final RDFNode value, final int start)
throws ReadDeniedException, AuthenticationRequiredException {
checkRead();
final ExtendedIterator<SecuredRDFNode> iter = getSecuredRDFListIterator(
Action.Read).mapWith(new SecuredNodeMap(listFirst()));
try {
int retval = 0;
while (iter.hasNext() && (retval < start)) {
iter.next();
retval++;
}
while (iter.hasNext()) {
if (value.equals(iter.next())) {
return retval;
} else {
retval++;
}
}
return -1;
} finally {
iter.close();
}
}
@Override
public boolean isEmpty() throws ReadDeniedException,
AuthenticationRequiredException {
checkRead();
final ExtendedIterator<RDFNode> iter = iterator();
try {
return !iter.hasNext();
} finally {
iter.close();
}
}
@Override
public boolean isValid() throws ReadDeniedException,
AuthenticationRequiredException {
checkRead();
return holder.getBaseItem().isValid();
}
@Override
public ExtendedIterator<RDFNode> iterator() throws ReadDeniedException,
AuthenticationRequiredException {
checkRead();
return getSecuredRDFListIterator(Action.Read).mapWith(
new PlainNodeMap());
}
@Override
public ExtendedIterator<RDFNode> iterator(final Set<Action> constraints)
throws ReadDeniedException, AuthenticationRequiredException {
checkRead();
final Set<Action> req = new HashSet<Action>(constraints);
req.add(Action.Read);
return getSecuredRDFListIterator(req).mapWith(new PlainNodeMap());
}
public Class<? extends RDFList> listAbstractionClass() {
return RDFList.class;
}
public Property listFirst() {
return m_listFirst;
}
public Resource listNil() {
return m_listNil;
}
public Property listRest() {
return m_listRest;
}
public Resource listType() {
return m_listType;
}
@Override
public <T> ExtendedIterator<T> mapWith(final Function<RDFNode, T> fn)
throws ReadDeniedException, AuthenticationRequiredException {
return iterator().mapWith(fn);
}
@Override
public Object reduce(final ReduceFn fn, final Object initial)
throws ReadDeniedException, AuthenticationRequiredException {
Object acc = initial;
for (final Iterator<RDFNode> i = iterator(); i.hasNext();) {
acc = fn.reduce(i.next(), acc);
}
return acc;
}
@Override
public Object reduce(final Set<Action> requiredActions, final ReduceFn fn,
final Object initial) throws EmptyListException,
ListIndexException, InvalidListException, ReadDeniedException,
AuthenticationRequiredException {
Object acc = initial;
final Set<Action> perms = new HashSet<Action>(requiredActions);
perms.add(Action.Read);
for (final Iterator<RDFNode> i = iterator(perms); i.hasNext();) {
acc = fn.reduce(i.next(), acc);
}
return acc;
}
@Override
public RDFList remove(final RDFNode val) throws UpdateDeniedException,
DeleteDeniedException, AuthenticationRequiredException {
checkUpdate();
RDFList cell = null;
boolean denied = false;
if (!canDelete(new Triple(Node.ANY, listFirst().asNode(), val.asNode()))) {
// iterate over the deletable items
final ExtendedIterator<RDFList> iter = getSecuredRDFListIterator(Action.Delete);// .mapWith(new
// SecuredListMap());
while (iter.hasNext()) {
cell = iter.next();
if (val.equals(cell.getRequiredProperty(listFirst())
.getObject())) {
if (canDelete(new Triple(cell.asNode(), listFirst()
.asNode(), val.asNode()))) {
return SecuredRDFListImpl.getInstance(getModel(),
baseRemove(cell));
} else {
denied = true;
}
}
}
if (denied) {
throw new DeleteDeniedException(
SecuredItem.Util.triplePermissionMsg(getModelNode()));
} else {
return this;
}
} else {
return SecuredRDFListImpl.getInstance(getModel(), holder
.getBaseItem().remove(val));
}
}
@Override
@Deprecated
public void removeAll() throws UpdateDeniedException,
AuthenticationRequiredException {
removeList();
}
@Override
public SecuredRDFList removeHead() throws UpdateDeniedException,
DeleteDeniedException, AuthenticationRequiredException {
checkUpdate();
final ExtendedIterator<SecuredRDFList> iter = getSecuredRDFListIterator(
Action.Read).mapWith(new SecuredListMap());
try {
if (!iter.hasNext()) {
throw new EmptyListException(
"Attempted to delete the head of a nil list");
}
final SecuredRDFList cell = iter.next();
final Statement s = cell.getRequiredProperty(RDF.first);
checkDelete(s);
return SecuredRDFListImpl.getInstance(getModel(), baseRemove(cell));
} finally {
iter.close();
}
}
@Override
public void removeList() throws UpdateDeniedException,
AuthenticationRequiredException {
checkUpdate();
final Triple t = new Triple(Node.ANY, listFirst().asNode(), Node.ANY);
// have to be able to read and delete to delete all.
final Set<Action> perms = SecurityEvaluator.Util.asSet(new Action[] {
Action.Delete, Action.Read });
if (getSecurityEvaluator().evaluate(
getSecurityEvaluator().getPrincipal(), perms,
this.getModelNode(), t)) {
holder.getBaseItem().removeList();
} else {
for (final Statement s : collectStatements(perms)) {
if (canDelete(s)) {
s.remove();
}
}
}
}
@Override
public SecuredRDFNode replace(final int i, final RDFNode value)
throws UpdateDeniedException, AuthenticationRequiredException,
ListIndexException {
checkUpdate();
final SecuredNodeMap map = new SecuredNodeMap(listFirst());
final ExtendedIterator<SecuredRDFList> iter = getSecuredRDFListIterator(
Action.Read).mapWith(new SecuredListMap());
int idx = 0;
try {
while (iter.hasNext()) {
if (i == idx) {
final SecuredRDFList list = iter.next();
final SecuredRDFNode retval = map.apply(list);
final Triple t = new Triple(list.asNode(), listFirst()
.asNode(), retval.asNode());
final Triple t2 = new Triple(list.asNode(), listFirst()
.asNode(), value.asNode());
checkUpdate(t, t2);
final RDFList base = (RDFList) list.getBaseItem();
base.getRequiredProperty(listFirst()).changeObject(value);
return retval;
} else {
idx++;
iter.next();
}
}
throw new ListIndexException();
} finally {
iter.close();
}
}
@Override
public boolean sameListAs(final RDFList list) throws ReadDeniedException,
AuthenticationRequiredException {
checkRead();
ExtendedIterator<RDFNode> thisIter = null;
ExtendedIterator<RDFNode> thatIter = null;
try {
thisIter = iterator();
thatIter = list.iterator();
while (thisIter.hasNext() && thatIter.hasNext()) {
final RDFNode thisN = thisIter.next();
final RDFNode thatN = thatIter.next();
if ((thisN == null) || !thisN.equals(thatN)) {
// not equal at this position
return false;
}
}
return !(thisIter.hasNext() || thatIter.hasNext());
} finally {
if (thisIter != null) {
thisIter.close();
}
if (thatIter != null) {
thatIter.close();
}
}
}
@Override
public SecuredRDFNode setHead(final RDFNode value)
throws EmptyListException, AuthenticationRequiredException {
final ExtendedIterator<SecuredRDFList> iter = getSecuredRDFListIterator(
Action.Read).mapWith(new SecuredListMap());
try {
if (iter.hasNext()) {
return replace(0, value);
} else {
throw new EmptyListException(
"Tried to set the head of an empty list");
}
} finally {
iter.close();
}
}
@Override
public void setStrict(final boolean strict) throws UpdateDeniedException,
AuthenticationRequiredException {
checkUpdate();
holder.getBaseItem().setStrict(strict);
}
@Override
public SecuredRDFList setTail(final RDFList tail)
throws UpdateDeniedException, AuthenticationRequiredException {
checkUpdate();
final Statement rest = holder.getBaseItem().getRequiredProperty(
listRest());
final RDFNode retval = rest.getObject();
final Triple t = new Triple(holder.getBaseItem().asNode(), listRest()
.asNode(), retval.asNode());
final Triple t2 = new Triple(holder.getBaseItem().asNode(), listRest()
.asNode(), tail.asNode());
checkUpdate(t, t2);
rest.changeObject(tail);
return SecuredRDFListImpl.getInstance(getModel(),
retval.as(RDFList.class));
}
@Override
public int size() throws ReadDeniedException,
AuthenticationRequiredException {
checkRead();
final Triple t = new Triple(Node.ANY, listFirst().asNode(), Node.ANY);
if (canRead(t)) {
return holder.getBaseItem().size();
}
final ExtendedIterator<RDFNode> iter = iterator();
int i = 0;
while (iter.hasNext()) {
i++;
iter.next();
}
return i;
}
@Override
public SecuredRDFList with(final RDFNode value)
throws UpdateDeniedException, AddDeniedException,
AuthenticationRequiredException {
checkUpdate();
checkCreate(new Triple(SecurityEvaluator.FUTURE, listFirst().asNode(),
value.asNode()));
return SecuredRDFListImpl.getInstance(getModel(), holder.getBaseItem()
.with(value));
}
}