RM-2130 Support for specific instantiable collections.

It's impossible to instantiate/clone a generic collection, so if a member of the collection
changes then our only option is to attempt to remove it.  This has two problems. Firstly,
this is far more brutal than we desire in many cases. Secondly, some collections do not
implement the remove method (e.g. anything returned by Arrays.asList()).

As an attempt to work around this issue we've created some specific implementation
processors. This introduces new potential problems - e.g. if someone has used an exotic
type of list it will be replaced with an ArrayList by the ListPostMethodInvocationProcessor.

+review RM

git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/modules/recordsmanagement/DEV/ENFORCE@107355 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
This commit is contained in:
Tom Page
2015-06-29 15:18:49 +00:00
parent a476d21d08
commit e9a5f28f85
9 changed files with 614 additions and 109 deletions

View File

@@ -24,7 +24,7 @@ import java.util.Iterator;
import org.springframework.stereotype.Component;
/**
* Collection Post Method Invocation Processor
* Collection Post Method Invocation Processor.
*
* @author Tuna Aksoy
* @since 3.0
@@ -44,40 +44,81 @@ public class CollectionPostMethodInvocationProcessor extends BasePostMethodInvoc
/**
* @see org.alfresco.module.org_alfresco_module_rm.classification.interceptor.processor.BasePostMethodInvocationProcessor#process(java.lang.Object)
*/
@SuppressWarnings("rawtypes")
@SuppressWarnings({ "rawtypes", "unchecked" })
@Override
public <T> T process(T object)
{
T result = object;
Collection collection = ((Collection) object);
if (result != null)
if (collection != null)
{
Collection collection = ((Collection) result);
if (!collection.isEmpty())
BasePostMethodInvocationProcessor processor = pickProcessor(collection);
if (processor != null)
{
Iterator iterator = collection.iterator();
while (iterator.hasNext())
{
Object next = iterator.next();
// TODO: Can we guarantee that all the elements of a collection can be processed by the same processor?
BasePostMethodInvocationProcessor processor = getPostMethodInvocationProcessor().getProcessor(next);
if (processor != null)
{
Object processed = processor.process(next);
if (processed == null)
{
iterator.remove();
}
else if (!processed.equals(next))
{
// TODO Support this, as it will be hit by e.g. collections of collections.
throw new IllegalStateException("Modifying members of a collection is not yet supported.");
}
}
}
object = (T) processCollection(collection, processor);
}
}
return result;
return object;
}
/**
* Process a collection using the supplied processor.
*
* @param collection The collection to be processed.
* @param processor A collection suitable for access by someone with the current security clearance.
*/
protected <T> Collection<T> processCollection(Collection<T> collection, BasePostMethodInvocationProcessor processor)
{
Iterator<T> iterator = collection.iterator();
while (iterator.hasNext())
{
Object next = iterator.next();
Object processed = processor.process(next);
try
{
if (processed == null)
{
iterator.remove();
}
else if (!processed.equals(next))
{
// Modifying members of this type of collection is not supported, so filter the whole collection.
return null;
}
}
catch (UnsupportedOperationException e)
{
// If the collection cannot be modified and it contains classified data then the whole thing must be filtered.
return null;
}
}
return collection;
}
/**
* Pick a suitable processor for the members of the collection. We assume that all the elements of a collection can
* be processed by the same processor.
*
* @param collection The collection to be processed.
* @return The chosen processor, or {@code null} if no suitable processor could be found.
*/
@SuppressWarnings("rawtypes")
private BasePostMethodInvocationProcessor pickProcessor(Collection collection)
{
Iterator iterator = collection.iterator();
while (iterator.hasNext())
{
Object next = iterator.next();
if (next != null)
{
BasePostMethodInvocationProcessor processor = getPostMethodInvocationProcessor().getProcessor(next);
if (processor != null)
{
return processor;
}
}
}
return null;
}
}

View File

@@ -0,0 +1,47 @@
/*
* Copyright (C) 2005-2015 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco 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 3 of the License, or
* (at your option) any later version.
*
* Alfresco 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 Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.module.org_alfresco_module_rm.classification.interceptor.processor;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
/**
* List Post Method Invocation Processor. This replaces the existing list with a filtered {@link ArrayList}. By doing
* this we gain the ability to replace members of a list, which is not possible using the
* {@link CollectionPostMethodInvocationProcessor}. The downside is that whatever type of list was provided gets
* replaced with an {@code ArrayList}.
*
* @author Tom Page
* @since 3.0
*/
public class ListPostMethodInvocationProcessor extends ModifiableCollectionPostMethodInvocationProcessor
{
@Override
protected Class<?> getClassName()
{
return List.class;
}
@Override
protected <T> Collection<T> createEmptyCollection(Collection<T> collection)
{
return new ArrayList<>();
}
}

View File

@@ -0,0 +1,61 @@
/*
* Copyright (C) 2005-2015 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco 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 3 of the License, or
* (at your option) any later version.
*
* Alfresco 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 Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.module.org_alfresco_module_rm.classification.interceptor.processor;
import java.util.ArrayList;
import java.util.Collection;
/**
* List Post Method Invocation Processor. This replaces the existing list with a filtered {@link ArrayList}. By doing
* this we gain the ability to replace members of a list, which is not possible using the
* {@link CollectionPostMethodInvocationProcessor}. The downside is that whatever type of list was provided gets
* replaced with an {@code ArrayList}.
*
* @author Tom Page
* @since 3.0
*/
public abstract class ModifiableCollectionPostMethodInvocationProcessor extends CollectionPostMethodInvocationProcessor
{
@Override
abstract protected Class<?> getClassName();
/**
* Create an empty modifiable collection.
*
* @param collection The source collection to try to mimic.
* @return The new empty collection.
*/
abstract protected <T> Collection<T> createEmptyCollection(Collection<T> collection);
/** {@inheritDoc} */
@Override
protected <T> Collection<T> processCollection(Collection<T> collection, BasePostMethodInvocationProcessor processor)
{
Collection<T> returnList = createEmptyCollection(collection);
for (T member : collection)
{
T processed = processor.process(member);
if (processed != null)
{
returnList.add(processed);
}
}
return returnList;
}
}

View File

@@ -0,0 +1,47 @@
/*
* Copyright (C) 2005-2015 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco 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 3 of the License, or
* (at your option) any later version.
*
* Alfresco 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 Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.module.org_alfresco_module_rm.classification.interceptor.processor;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
/**
* Set Post Method Invocation Processor. This replaces the existing set with a filtered {@link HashSet}. By doing
* this we gain the ability to replace members of a set, which is not possible using the
* {@link CollectionPostMethodInvocationProcessor}. The downside is that whatever type of set was provided gets
* replaced with an {@code HashSet}.
*
* @author Tom Page
* @since 3.0
*/
public class SetPostMethodInvocationProcessor extends ModifiableCollectionPostMethodInvocationProcessor
{
@Override
protected Class<?> getClassName()
{
return Set.class;
}
@Override
protected <T> Collection<T> createEmptyCollection(Collection<T> collection)
{
return new HashSet<>();
}
}

View File

@@ -0,0 +1,45 @@
/*
* Copyright (C) 2005-2015 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco 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 3 of the License, or
* (at your option) any later version.
*
* Alfresco 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 Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.module.org_alfresco_module_rm.classification.interceptor.processor;
import java.util.Collection;
import java.util.SortedSet;
import java.util.TreeSet;
/**
* Sorted Set Post Method Invocation Processor. This replaces the existing set with a filtered {@link TreeSet}.
*
* @author Tom Page
* @since 3.0
*/
public class SortedSetPostMethodInvocationProcessor extends ModifiableCollectionPostMethodInvocationProcessor
{
@Override
protected Class<?> getClassName()
{
return SortedSet.class;
}
@Override
protected <T> Collection<T> createEmptyCollection(Collection<T> collection)
{
SortedSet<T> sortedSet = (SortedSet<T>) collection;
return new TreeSet<>(sortedSet.comparator());
}
}