001/* 002 * The contents of this file are subject to the terms of the Common Development and 003 * Distribution License (the License). You may not use this file except in compliance with the 004 * License. 005 * 006 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the 007 * specific language governing permission and limitations under the License. 008 * 009 * When distributing Covered Software, include this CDDL Header Notice in each file and include 010 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL 011 * Header, with the fields enclosed by brackets [] replaced by your own identifying 012 * information: "Portions Copyright [year] [name of copyright owner]". 013 * 014 * Copyright 2013-2015 ForgeRock AS. 015 */ 016 017package org.forgerock.json.resource; 018 019import static org.forgerock.util.Reject.checkNotNull; 020 021import java.util.Arrays; 022import java.util.Collection; 023import java.util.List; 024import java.util.concurrent.CopyOnWriteArrayList; 025 026import org.forgerock.services.context.Context; 027import org.forgerock.util.promise.Promise; 028 029/** 030 * A chain of filters terminated by a target request handler. The filter chain 031 * is thread safe and supports updates to the list of filters and the target 032 * request handler while actively processing requests. 033 */ 034public final class FilterChain implements RequestHandler { 035 /* 036 * A request handler which represents the current position in the filter 037 * chain. Maintains a reference to the filter chain which was in use at the 038 * time when the cursor was created. 039 */ 040 private final class Cursor implements RequestHandler { 041 private final int pos; 042 private final Filter[] snapshot; 043 044 private Cursor() { 045 this(filters.toArray(new Filter[0]), 0); 046 } 047 048 private Cursor(final Filter[] snapshot, final int pos) { 049 this.snapshot = snapshot; 050 this.pos = pos; 051 } 052 053 @Override 054 public Promise<ActionResponse, ResourceException> handleAction(final Context context, 055 final ActionRequest request) { 056 if (hasNext()) { 057 return get().filterAction(context, request, next()); 058 } else { 059 return target.handleAction(context, request); 060 } 061 } 062 063 @Override 064 public Promise<ResourceResponse, ResourceException> handleCreate(final Context context, 065 final CreateRequest request) { 066 if (hasNext()) { 067 return get().filterCreate(context, request, next()); 068 } else { 069 return target.handleCreate(context, request); 070 } 071 } 072 073 @Override 074 public Promise<ResourceResponse, ResourceException> handleDelete(final Context context, 075 final DeleteRequest request) { 076 if (hasNext()) { 077 return get().filterDelete(context, request, next()); 078 } else { 079 return target.handleDelete(context, request); 080 } 081 } 082 083 @Override 084 public Promise<ResourceResponse, ResourceException> handlePatch(final Context context, 085 final PatchRequest request) { 086 if (hasNext()) { 087 return get().filterPatch(context, request, next()); 088 } else { 089 return target.handlePatch(context, request); 090 } 091 } 092 093 @Override 094 public Promise<QueryResponse, ResourceException> handleQuery(final Context context, 095 final QueryRequest request, final QueryResourceHandler handler) { 096 if (hasNext()) { 097 return get().filterQuery(context, request, handler, next()); 098 } else { 099 return target.handleQuery(context, request, handler); 100 } 101 } 102 103 @Override 104 public Promise<ResourceResponse, ResourceException> handleRead(final Context context, 105 final ReadRequest request) { 106 if (hasNext()) { 107 return get().filterRead(context, request, next()); 108 } else { 109 return target.handleRead(context, request); 110 } 111 } 112 113 @Override 114 public Promise<ResourceResponse, ResourceException> handleUpdate(final Context context, 115 final UpdateRequest request) { 116 if (hasNext()) { 117 return get().filterUpdate(context, request, next()); 118 } else { 119 return target.handleUpdate(context, request); 120 } 121 } 122 123 private Filter get() { 124 return snapshot[pos]; 125 } 126 127 private boolean hasNext() { 128 return pos < snapshot.length; 129 } 130 131 private Cursor next() { 132 return new Cursor(snapshot, pos + 1); 133 } 134 135 } 136 137 private final List<Filter> filters = new CopyOnWriteArrayList<>(); 138 private volatile RequestHandler target; 139 140 /** 141 * Creates an empty filter chain. 142 * 143 * @param target 144 * The target request handler which will be invoked once 145 * processing has reached the end of the filter chain. 146 */ 147 public FilterChain(final RequestHandler target) { 148 this.target = checkNotNull(target, "Cannot create FilterChain with null target RequestHandler"); 149 } 150 151 /** 152 * Creates a filter chain containing the provided list of filters. 153 * 154 * @param target 155 * The target request handler which will be invoked once 156 * processing has reached the end of the filter chain. 157 * @param filters 158 * The list of filters to be processed before invoking the 159 * target. 160 */ 161 public FilterChain(final RequestHandler target, final Collection<Filter> filters) { 162 this.target = checkNotNull(target, "Cannot create FilterChain with null target RequestHandler"); 163 this.filters.addAll(filters); 164 } 165 166 /** 167 * Creates a filter chain containing the provided list of filters. 168 * 169 * @param target 170 * The target request handler which will be invoked once 171 * processing has reached the end of the filter chain. 172 * @param filters 173 * The list of filters to be processed before invoking the 174 * target. 175 */ 176 public FilterChain(final RequestHandler target, final Filter... filters) { 177 this.target = checkNotNull(target, "Cannot create FilterChain with null target RequestHandler"); 178 this.filters.addAll(Arrays.asList(filters)); 179 } 180 181 /** 182 * Returns a modifiable list containing the list of filters in this filter 183 * chain. Updates to the filter chain are thread safe and may be performed 184 * while the processing requests. 185 * 186 * @return A modifiable list containing the list of filters in this filter 187 * chain. 188 */ 189 public List<Filter> getFilters() { 190 return filters; 191 } 192 193 /** 194 * Returns the target request handler which will be invoked once processing 195 * has reached the end of the filter chain. 196 * 197 * @return The target request handler which will be invoked once processing 198 * has reached the end of the filter chain. 199 */ 200 public RequestHandler getTarget() { 201 return target; 202 } 203 204 @Override 205 public Promise<ActionResponse, ResourceException> handleAction(final Context context, 206 final ActionRequest request) { 207 return new Cursor().handleAction(context, request); 208 } 209 210 @Override 211 public Promise<ResourceResponse, ResourceException> handleCreate(final Context context, 212 final CreateRequest request) { 213 return new Cursor().handleCreate(context, request); 214 } 215 216 @Override 217 public Promise<ResourceResponse, ResourceException> handleDelete(final Context context, 218 final DeleteRequest request) { 219 return new Cursor().handleDelete(context, request); 220 } 221 222 @Override 223 public Promise<ResourceResponse, ResourceException> handlePatch(final Context context, 224 final PatchRequest request) { 225 return new Cursor().handlePatch(context, request); 226 } 227 228 @Override 229 public Promise<QueryResponse, ResourceException> handleQuery(final Context context, 230 final QueryRequest request, final QueryResourceHandler handler) { 231 return new Cursor().handleQuery(context, request, handler); 232 } 233 234 @Override 235 public Promise<ResourceResponse, ResourceException> handleRead(final Context context, 236 final ReadRequest request) { 237 return new Cursor().handleRead(context, request); 238 } 239 240 @Override 241 public Promise<ResourceResponse, ResourceException> handleUpdate(final Context context, 242 final UpdateRequest request) { 243 return new Cursor().handleUpdate(context, request); 244 } 245 246 /** 247 * Sets the target request handler which will be invoked once processing has 248 * reached the end of the filter chain. The target request handler may be 249 * updated while the processing requests. 250 * 251 * @param target 252 * The target request handler which will be invoked once 253 * processing has reached the end of the filter chain. 254 * @return This a reference to this filter chain. 255 */ 256 public FilterChain setTarget(final RequestHandler target) { 257 this.target = checkNotNull(target, "Cannot set target RequestHandler to null value"); 258 return this; 259 } 260 261 @Override 262 public String toString() { 263 final StringBuilder builder = new StringBuilder(); 264 builder.append(filters.toString()); 265 builder.append(" -> "); 266 builder.append(target.toString()); 267 return builder.toString(); 268 } 269 270}