@SuppressWarnings("unchecked")
	public Object filter(Object filterTarget, Expression filterExpression,
			EvaluationContext ctx) {
		MethodSecurityExpressionOperations rootObject = (MethodSecurityExpressionOperations) ctx
				.getRootObject().getValue();
		final boolean debug = logger.isDebugEnabled();
		List retainList;

		if (debug) {
			logger.debug("Filtering with expression: "
					+ filterExpression.getExpressionString());
		}

		if (filterTarget instanceof Collection) {
			Collection collection = (Collection) filterTarget;
			retainList = new ArrayList(collection.size());

			if (debug) {
				logger.debug("Filtering collection with " + collection.size()
						+ " elements");
			}

			if (permissionCacheOptimizer != null) {
				permissionCacheOptimizer.cachePermissionsFor(
						rootObject.getAuthentication(), collection);
			}

			for (Object filterObject : (Collection) filterTarget) {
				rootObject.setFilterObject(filterObject);

				if (ExpressionUtils.evaluateAsBoolean(filterExpression, ctx)) {
					retainList.add(filterObject);
				}
			}

			if (debug) {
				logger.debug("Retaining elements: " + retainList);
			}

			collection.clear();
			collection.addAll(retainList);

			return filterTarget;
		}

		if (filterTarget.getClass().isArray()) {
			Object[] array = (Object[]) filterTarget;
			retainList = new ArrayList(array.length);

			if (debug) {
				logger.debug("Filtering array with " + array.length + " elements");
			}

			if (permissionCacheOptimizer != null) {
				permissionCacheOptimizer.cachePermissionsFor(
						rootObject.getAuthentication(), Arrays.asList(array));
			}

			for (Object o : array) {
				rootObject.setFilterObject(o);

				if (ExpressionUtils.evaluateAsBoolean(filterExpression, ctx)) {
					retainList.add(o);
				}
			}

			if (debug) {
				logger.debug("Retaining elements: " + retainList);
			}

			Object[] filtered = (Object[]) Array.newInstance(filterTarget.getClass()
					.getComponentType(), retainList.size());
			for (int i = 0; i < retainList.size(); i++) {
				filtered[i] = retainList.get(i);
			}

			return filtered;
		}

		if (filterTarget instanceof Stream) {
			final Stream<?> original = (Stream<?>) filterTarget;

			return original.filter(filterObject -> {
				rootObject.setFilterObject(filterObject);
				return ExpressionUtils.evaluateAsBoolean(filterExpression, ctx);
			})
					.onClose(original::close);
		}

		throw new IllegalArgumentException(
				"Filter target must be a collection, array, or stream type, but was "
						+ filterTarget);
	}