001/* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, 013 * software distributed under the License is distributed on an 014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 015 * KIND, either express or implied. See the License for the 016 * specific language governing permissions and limitations 017 * under the License. 018 * 019 */ 020package org.apache.mina.core.filterchain; 021 022import java.util.ArrayList; 023import java.util.Collections; 024import java.util.Iterator; 025import java.util.LinkedHashMap; 026import java.util.List; 027import java.util.ListIterator; 028import java.util.Map; 029import java.util.Random; 030import java.util.concurrent.CopyOnWriteArrayList; 031 032import org.apache.mina.core.filterchain.IoFilter.NextFilter; 033import org.apache.mina.core.filterchain.IoFilterChain.Entry; 034import org.apache.mina.core.session.IoSession; 035import org.slf4j.Logger; 036import org.slf4j.LoggerFactory; 037 038/** 039 * The default implementation of {@link IoFilterChainBuilder} which is useful 040 * in most cases. {@link DefaultIoFilterChainBuilder} has an identical interface 041 * with {@link IoFilter}; it contains a list of {@link IoFilter}s that you can 042 * modify. The {@link IoFilter}s which are added to this builder will be appended 043 * to the {@link IoFilterChain} when {@link #buildFilterChain(IoFilterChain)} is 044 * invoked. 045 * <p> 046 * However, the identical interface doesn't mean that it behaves in an exactly 047 * same way with {@link IoFilterChain}. {@link DefaultIoFilterChainBuilder} 048 * doesn't manage the life cycle of the {@link IoFilter}s at all, and the 049 * existing {@link IoSession}s won't get affected by the changes in this builder. 050 * {@link IoFilterChainBuilder}s affect only newly created {@link IoSession}s. 051 * 052 * <pre> 053 * IoAcceptor acceptor = ...; 054 * DefaultIoFilterChainBuilder builder = acceptor.getFilterChain(); 055 * builder.addLast( "myFilter", new MyFilter() ); 056 * ... 057 * </pre> 058 * 059 * @author <a href="http://mina.apache.org">Apache MINA Project</a> 060 * @org.apache.xbean.XBean 061 */ 062public class DefaultIoFilterChainBuilder implements IoFilterChainBuilder { 063 064 private final static Logger LOGGER = LoggerFactory.getLogger(DefaultIoFilterChainBuilder.class); 065 066 private final List<Entry> entries; 067 068 /** 069 * Creates a new instance with an empty filter list. 070 */ 071 public DefaultIoFilterChainBuilder() { 072 entries = new CopyOnWriteArrayList<Entry>(); 073 } 074 075 /** 076 * Creates a new copy of the specified {@link DefaultIoFilterChainBuilder}. 077 * 078 * @param filterChain The FilterChain we will copy 079 */ 080 public DefaultIoFilterChainBuilder(DefaultIoFilterChainBuilder filterChain) { 081 if (filterChain == null) { 082 throw new IllegalArgumentException("filterChain"); 083 } 084 entries = new CopyOnWriteArrayList<Entry>(filterChain.entries); 085 } 086 087 /** 088 * @see IoFilterChain#getEntry(String) 089 * 090 * @param name The Filter's name we are looking for 091 * @return The found Entry 092 */ 093 public Entry getEntry(String name) { 094 for (Entry e : entries) { 095 if (e.getName().equals(name)) { 096 return e; 097 } 098 } 099 100 return null; 101 } 102 103 /** 104 * @see IoFilterChain#getEntry(IoFilter) 105 * 106 * @param filter The Filter we are looking for 107 * @return The found Entry 108 */ 109 public Entry getEntry(IoFilter filter) { 110 for (Entry e : entries) { 111 if (e.getFilter() == filter) { 112 return e; 113 } 114 } 115 116 return null; 117 } 118 119 /** 120 * @see IoFilterChain#getEntry(Class) 121 * 122 * @param filterType The FilterType we are looking for 123 * @return The found Entry 124 */ 125 public Entry getEntry(Class<? extends IoFilter> filterType) { 126 for (Entry e : entries) { 127 if (filterType.isAssignableFrom(e.getFilter().getClass())) { 128 return e; 129 } 130 } 131 132 return null; 133 } 134 135 /** 136 * @see IoFilterChain#get(String) 137 * 138 * @param name The Filter's name we are looking for 139 * @return The found Filter, or null 140 */ 141 public IoFilter get(String name) { 142 Entry e = getEntry(name); 143 if (e == null) { 144 return null; 145 } 146 147 return e.getFilter(); 148 } 149 150 /** 151 * @see IoFilterChain#get(Class) 152 * 153 * @param filterType The FilterType we are looking for 154 * @return The found Filter, or null 155 */ 156 public IoFilter get(Class<? extends IoFilter> filterType) { 157 Entry e = getEntry(filterType); 158 if (e == null) { 159 return null; 160 } 161 162 return e.getFilter(); 163 } 164 165 /** 166 * @see IoFilterChain#getAll() 167 * 168 * @return The list of Filters 169 */ 170 public List<Entry> getAll() { 171 return new ArrayList<Entry>(entries); 172 } 173 174 /** 175 * @see IoFilterChain#getAllReversed() 176 * 177 * @return The list of Filters, reversed 178 */ 179 public List<Entry> getAllReversed() { 180 List<Entry> result = getAll(); 181 Collections.reverse(result); 182 return result; 183 } 184 185 /** 186 * @see IoFilterChain#contains(String) 187 * 188 * @param name The Filter's name we want to check if it's in the chain 189 * @return <tt>true</tt> if the chain contains the given filter name 190 */ 191 public boolean contains(String name) { 192 return getEntry(name) != null; 193 } 194 195 /** 196 * @see IoFilterChain#contains(IoFilter) 197 * 198 * @param filter The Filter we want to check if it's in the chain 199 * @return <tt>true</tt> if the chain contains the given filter 200 */ 201 public boolean contains(IoFilter filter) { 202 return getEntry(filter) != null; 203 } 204 205 /** 206 * @see IoFilterChain#contains(Class) 207 * 208 * @param filterType The FilterType we want to check if it's in the chain 209 * @return <tt>true</tt> if the chain contains the given filterType 210 */ 211 public boolean contains(Class<? extends IoFilter> filterType) { 212 return getEntry(filterType) != null; 213 } 214 215 /** 216 * @see IoFilterChain#addFirst(String, IoFilter) 217 * 218 * @param name The filter's name 219 * @param filter The filter to add 220 */ 221 public synchronized void addFirst(String name, IoFilter filter) { 222 register(0, new EntryImpl(name, filter)); 223 } 224 225 /** 226 * @see IoFilterChain#addLast(String, IoFilter) 227 * 228 * @param name The filter's name 229 * @param filter The filter to add 230 */ 231 public synchronized void addLast(String name, IoFilter filter) { 232 register(entries.size(), new EntryImpl(name, filter)); 233 } 234 235 /** 236 * @see IoFilterChain#addBefore(String, String, IoFilter) 237 * 238 * @param baseName The filter baseName 239 * @param name The filter's name 240 * @param filter The filter to add 241 */ 242 public synchronized void addBefore(String baseName, String name, IoFilter filter) { 243 checkBaseName(baseName); 244 245 for (ListIterator<Entry> i = entries.listIterator(); i.hasNext();) { 246 Entry base = i.next(); 247 if (base.getName().equals(baseName)) { 248 register(i.previousIndex(), new EntryImpl(name, filter)); 249 break; 250 } 251 } 252 } 253 254 /** 255 * @see IoFilterChain#addAfter(String, String, IoFilter) 256 * 257 * @param baseName The filter baseName 258 * @param name The filter's name 259 * @param filter The filter to add 260 */ 261 public synchronized void addAfter(String baseName, String name, IoFilter filter) { 262 checkBaseName(baseName); 263 264 for (ListIterator<Entry> i = entries.listIterator(); i.hasNext();) { 265 Entry base = i.next(); 266 if (base.getName().equals(baseName)) { 267 register(i.nextIndex(), new EntryImpl(name, filter)); 268 break; 269 } 270 } 271 } 272 273 /** 274 * @see IoFilterChain#remove(String) 275 * 276 * @param name The Filter's name to remove from the list of Filters 277 * @return The removed IoFilter 278 */ 279 public synchronized IoFilter remove(String name) { 280 if (name == null) { 281 throw new IllegalArgumentException("name"); 282 } 283 284 for (ListIterator<Entry> i = entries.listIterator(); i.hasNext();) { 285 Entry e = i.next(); 286 if (e.getName().equals(name)) { 287 entries.remove(i.previousIndex()); 288 return e.getFilter(); 289 } 290 } 291 292 throw new IllegalArgumentException("Unknown filter name: " + name); 293 } 294 295 /** 296 * @see IoFilterChain#remove(IoFilter) 297 * 298 * @param filter The Filter we want to remove from the list of Filters 299 * @return The removed IoFilter 300 */ 301 public synchronized IoFilter remove(IoFilter filter) { 302 if (filter == null) { 303 throw new IllegalArgumentException("filter"); 304 } 305 306 for (ListIterator<Entry> i = entries.listIterator(); i.hasNext();) { 307 Entry e = i.next(); 308 if (e.getFilter() == filter) { 309 entries.remove(i.previousIndex()); 310 return e.getFilter(); 311 } 312 } 313 314 throw new IllegalArgumentException("Filter not found: " + filter.getClass().getName()); 315 } 316 317 /** 318 * @see IoFilterChain#remove(Class) 319 * 320 * @param filterType The FilterType we want to remove from the list of Filters 321 * @return The removed IoFilter 322 */ 323 public synchronized IoFilter remove(Class<? extends IoFilter> filterType) { 324 if (filterType == null) { 325 throw new IllegalArgumentException("filterType"); 326 } 327 328 for (ListIterator<Entry> i = entries.listIterator(); i.hasNext();) { 329 Entry e = i.next(); 330 if (filterType.isAssignableFrom(e.getFilter().getClass())) { 331 entries.remove(i.previousIndex()); 332 return e.getFilter(); 333 } 334 } 335 336 throw new IllegalArgumentException("Filter not found: " + filterType.getName()); 337 } 338 339 public synchronized IoFilter replace(String name, IoFilter newFilter) { 340 checkBaseName(name); 341 EntryImpl e = (EntryImpl) getEntry(name); 342 IoFilter oldFilter = e.getFilter(); 343 e.setFilter(newFilter); 344 return oldFilter; 345 } 346 347 public synchronized void replace(IoFilter oldFilter, IoFilter newFilter) { 348 for (Entry e : entries) { 349 if (e.getFilter() == oldFilter) { 350 ((EntryImpl) e).setFilter(newFilter); 351 return; 352 } 353 } 354 throw new IllegalArgumentException("Filter not found: " + oldFilter.getClass().getName()); 355 } 356 357 public synchronized void replace(Class<? extends IoFilter> oldFilterType, IoFilter newFilter) { 358 for (Entry e : entries) { 359 if (oldFilterType.isAssignableFrom(e.getFilter().getClass())) { 360 ((EntryImpl) e).setFilter(newFilter); 361 return; 362 } 363 } 364 throw new IllegalArgumentException("Filter not found: " + oldFilterType.getName()); 365 } 366 367 /** 368 * @see IoFilterChain#clear() 369 */ 370 public synchronized void clear() { 371 entries.clear(); 372 } 373 374 /** 375 * Clears the current list of filters and adds the specified 376 * filter mapping to this builder. Please note that you must specify 377 * a {@link Map} implementation that iterates the filter mapping in the 378 * order of insertion such as {@link LinkedHashMap}. Otherwise, it will 379 * throw an {@link IllegalArgumentException}. 380 * 381 * @param filters The list of filters to set 382 */ 383 public void setFilters(Map<String, ? extends IoFilter> filters) { 384 if (filters == null) { 385 throw new IllegalArgumentException("filters"); 386 } 387 388 if (!isOrderedMap(filters)) { 389 throw new IllegalArgumentException("filters is not an ordered map. Please try " 390 + LinkedHashMap.class.getName() + "."); 391 } 392 393 filters = new LinkedHashMap<String, IoFilter>(filters); 394 for (Map.Entry<String, ? extends IoFilter> e : filters.entrySet()) { 395 if (e.getKey() == null) { 396 throw new IllegalArgumentException("filters contains a null key."); 397 } 398 if (e.getValue() == null) { 399 throw new IllegalArgumentException("filters contains a null value."); 400 } 401 } 402 403 synchronized (this) { 404 clear(); 405 for (Map.Entry<String, ? extends IoFilter> e : filters.entrySet()) { 406 addLast(e.getKey(), e.getValue()); 407 } 408 } 409 } 410 411 @SuppressWarnings("unchecked") 412 private boolean isOrderedMap(Map<String,? extends IoFilter> map) { 413 Class<?> mapType = map.getClass(); 414 415 if (LinkedHashMap.class.isAssignableFrom(mapType)) { 416 if (LOGGER.isDebugEnabled()) { 417 LOGGER.debug("{} is an ordered map.", mapType.getSimpleName() ); 418 } 419 420 return true; 421 } 422 423 if (LOGGER.isDebugEnabled()) { 424 LOGGER.debug("{} is not a {}", mapType.getName(), LinkedHashMap.class.getSimpleName()); 425 } 426 427 // Detect Jakarta Commons Collections OrderedMap implementations. 428 Class<?> type = mapType; 429 430 while (type != null) { 431 for (Class<?> i : type.getInterfaces()) { 432 if (i.getName().endsWith("OrderedMap")) { 433 if (LOGGER.isDebugEnabled()) { 434 LOGGER.debug("{} is an ordered map (guessed from that it implements OrderedMap interface.)", 435 mapType.getSimpleName()); 436 } 437 return true; 438 } 439 } 440 type = type.getSuperclass(); 441 } 442 443 if (LOGGER.isDebugEnabled()) { 444 LOGGER.debug("{} doesn't implement OrderedMap interface.", mapType.getName() ); 445 } 446 447 // Last resort: try to create a new instance and test if it maintains 448 // the insertion order. 449 LOGGER.debug("Last resort; trying to create a new map instance with a " 450 + "default constructor and test if insertion order is maintained."); 451 452 Map<String,IoFilter> newMap; 453 454 try { 455 newMap = (Map<String,IoFilter>) mapType.newInstance(); 456 } catch (Exception e) { 457 if (LOGGER.isDebugEnabled()) { 458 LOGGER.debug("Failed to create a new map instance of '{}'.", mapType.getName(), e); 459 } 460 return false; 461 } 462 463 Random rand = new Random(); 464 List<String> expectedNames = new ArrayList<String>(); 465 IoFilter dummyFilter = new IoFilterAdapter(); 466 467 for (int i = 0; i < 65536; i++) { 468 String filterName; 469 470 do { 471 filterName = String.valueOf(rand.nextInt()); 472 } while (newMap.containsKey(filterName)); 473 474 newMap.put(filterName, dummyFilter); 475 expectedNames.add(filterName); 476 477 Iterator<String> it = expectedNames.iterator(); 478 479 for (Object key : newMap.keySet()) { 480 if (!it.next().equals(key)) { 481 if (LOGGER.isDebugEnabled()) { 482 LOGGER.debug("The specified map didn't pass the insertion order test after {} tries.", (i + 1)); 483 } 484 return false; 485 } 486 } 487 } 488 489 LOGGER.debug("The specified map passed the insertion order test."); 490 491 return true; 492 } 493 494 public void buildFilterChain(IoFilterChain chain) throws Exception { 495 for (Entry e : entries) { 496 chain.addLast(e.getName(), e.getFilter()); 497 } 498 } 499 500 @Override 501 public String toString() { 502 StringBuilder buf = new StringBuilder(); 503 buf.append("{ "); 504 505 boolean empty = true; 506 507 for (Entry e : entries) { 508 if (!empty) { 509 buf.append(", "); 510 } else { 511 empty = false; 512 } 513 514 buf.append('('); 515 buf.append(e.getName()); 516 buf.append(':'); 517 buf.append(e.getFilter()); 518 buf.append(')'); 519 } 520 521 if (empty) { 522 buf.append("empty"); 523 } 524 525 buf.append(" }"); 526 527 return buf.toString(); 528 } 529 530 private void checkBaseName(String baseName) { 531 if (baseName == null) { 532 throw new IllegalArgumentException("baseName"); 533 } 534 535 if (!contains(baseName)) { 536 throw new IllegalArgumentException("Unknown filter name: " + baseName); 537 } 538 } 539 540 private void register(int index, Entry e) { 541 if (contains(e.getName())) { 542 throw new IllegalArgumentException("Other filter is using the same name: " + e.getName()); 543 } 544 545 entries.add(index, e); 546 } 547 548 private final class EntryImpl implements Entry { 549 private final String name; 550 551 private volatile IoFilter filter; 552 553 private EntryImpl(String name, IoFilter filter) { 554 if (name == null) { 555 throw new IllegalArgumentException("name"); 556 } 557 if (filter == null) { 558 throw new IllegalArgumentException("filter"); 559 } 560 561 this.name = name; 562 this.filter = filter; 563 } 564 565 public String getName() { 566 return name; 567 } 568 569 public IoFilter getFilter() { 570 return filter; 571 } 572 573 private void setFilter(IoFilter filter) { 574 this.filter = filter; 575 } 576 577 public NextFilter getNextFilter() { 578 throw new IllegalStateException(); 579 } 580 581 @Override 582 public String toString() { 583 return "(" + getName() + ':' + filter + ')'; 584 } 585 586 public void addAfter(String name, IoFilter filter) { 587 DefaultIoFilterChainBuilder.this.addAfter(getName(), name, filter); 588 } 589 590 public void addBefore(String name, IoFilter filter) { 591 DefaultIoFilterChainBuilder.this.addBefore(getName(), name, filter); 592 } 593 594 public void remove() { 595 DefaultIoFilterChainBuilder.this.remove(getName()); 596 } 597 598 public void replace(IoFilter newFilter) { 599 DefaultIoFilterChainBuilder.this.replace(getName(), newFilter); 600 } 601 } 602}