001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.wicket.request.mapper; 018 019import java.util.ArrayList; 020import java.util.Collections; 021import java.util.Iterator; 022import java.util.List; 023import java.util.concurrent.CopyOnWriteArrayList; 024 025import org.apache.wicket.request.IRequestHandler; 026import org.apache.wicket.request.IRequestMapper; 027import org.apache.wicket.request.Request; 028import org.apache.wicket.request.Url; 029import org.slf4j.Logger; 030import org.slf4j.LoggerFactory; 031 032 033/** 034 * Thread safe compound {@link IRequestMapper}. The mappers are searched depending on their 035 * compatibility score and the orders they were registered. If two or more {@link IRequestMapper}s 036 * have the same compatibility score, the last registered mapper has highest priority. 037 * 038 * @author igor.vaynberg 039 * @author Matej Knopp 040 */ 041public class CompoundRequestMapper implements ICompoundRequestMapper 042{ 043 private static final Logger LOG = LoggerFactory.getLogger(CompoundRequestMapper.class); 044 045 static class MapperWithScore implements Comparable<MapperWithScore> 046 { 047 private final IRequestMapper mapper; 048 private final int compatibilityScore; 049 050 public MapperWithScore(final IRequestMapper mapper, final int compatibilityScore) 051 { 052 this.mapper = mapper; 053 this.compatibilityScore = compatibilityScore; 054 } 055 056 @Override 057 public int compareTo(final MapperWithScore o) 058 { 059 return (compatibilityScore < o.compatibilityScore ? 1 060 : (compatibilityScore > o.compatibilityScore ? -1 : 0)); 061 } 062 063 public IRequestMapper getMapper() 064 { 065 return mapper; 066 } 067 068 @Override 069 public boolean equals(Object o) 070 { 071 if (this == o) 072 return true; 073 if (!(o instanceof MapperWithScore)) 074 return false; 075 076 MapperWithScore that = (MapperWithScore)o; 077 078 if (compatibilityScore != that.compatibilityScore) 079 return false; 080 return mapper.equals(that.mapper); 081 } 082 083 @Override 084 public int hashCode() 085 { 086 int result = mapper.hashCode(); 087 result = 31 * result + compatibilityScore; 088 return result; 089 } 090 091 @Override 092 public String toString() 093 { 094 return "Mapper: " + mapper.getClass().getName() + "; Score: " + compatibilityScore; 095 } 096 } 097 098 private final List<IRequestMapper> mappers = new CopyOnWriteArrayList<>(); 099 100 @Override 101 public CompoundRequestMapper add(final IRequestMapper mapper) 102 { 103 mappers.add(0, mapper); 104 return this; 105 } 106 107 @Override 108 public CompoundRequestMapper remove(final IRequestMapper mapper) 109 { 110 mappers.remove(mapper); 111 return this; 112 } 113 114 /** 115 * Searches the registered {@link IRequestMapper}s to find one that can map the {@link Request}. 116 * Each registered {@link IRequestMapper} is asked to provide its compatibility score. Then the 117 * mappers are asked to map the request in order depending on the provided compatibility 118 * score. 119 * <p> 120 * The mapper with highest compatibility score which can map the request is returned. 121 * 122 * @param request 123 * @return RequestHandler for the request or <code>null</code> if no mapper for the request is 124 * found. 125 */ 126 @Override 127 public IRequestHandler mapRequest(final Request request) 128 { 129 List<MapperWithScore> list = new ArrayList<>(mappers.size()); 130 131 for (IRequestMapper mapper : this) 132 { 133 int score = mapper.getCompatibilityScore(request); 134 list.add(new MapperWithScore(mapper, score)); 135 } 136 137 Collections.sort(list); 138 139 if (LOG.isDebugEnabled()) 140 { 141 logMappers(list, request.getUrl().toString()); 142 } 143 144 for (MapperWithScore mapperWithScore : list) 145 { 146 IRequestMapper mapper = mapperWithScore.getMapper(); 147 IRequestHandler handler = mapper.mapRequest(request); 148 if (handler != null) 149 { 150 return handler; 151 } 152 } 153 154 return null; 155 } 156 157 /** 158 * Logs all mappers with a positive compatibility score 159 * 160 * @param mappersWithScores 161 * the list of all mappers 162 * @param url 163 * the url to match by these mappers 164 */ 165 private void logMappers(final List<MapperWithScore> mappersWithScores, final String url) 166 { 167 final List<MapperWithScore> compatibleMappers = new ArrayList<>(); 168 for (MapperWithScore mapperWithScore : mappersWithScores) 169 { 170 if (mapperWithScore.compatibilityScore > 0) 171 { 172 compatibleMappers.add(mapperWithScore); 173 } 174 } 175 if (compatibleMappers.size() == 0) 176 { 177 LOG.debug("No compatible mapper found for URL '{}'", url); 178 } 179 else if (compatibleMappers.size() == 1) 180 { 181 LOG.debug("One compatible mapper found for URL '{}' -> '{}'", url, compatibleMappers.get(0)); 182 } 183 else 184 { 185 LOG.debug("Multiple compatible mappers found for URL '{}'", url); 186 for (MapperWithScore compatibleMapper : compatibleMappers) 187 { 188 LOG.debug(" * {}", compatibleMapper); 189 } 190 } 191 } 192 193 /** 194 * Searches the registered {@link IRequestMapper}s to find one that can map the 195 * {@link IRequestHandler}. Each registered {@link IRequestMapper} is asked to map the 196 * {@link IRequestHandler} until a mapper which can map the {@link IRequestHandler} is found or 197 * no more mappers are left. 198 * <p> 199 * The mappers are searched in reverse order as they have been registered. More recently 200 * registered mappers have bigger priority. 201 * 202 * @param handler 203 * @return Url for the handler or <code>null</code> if no mapper for the handler is found. 204 */ 205 @Override 206 public Url mapHandler(final IRequestHandler handler) 207 { 208 for (IRequestMapper mapper : this) 209 { 210 Url url = mapper.mapHandler(handler); 211 if (url != null) 212 { 213 return url; 214 } 215 } 216 return null; 217 } 218 219 /** 220 * The scope of the compound mapper is the highest score of the registered mappers. 221 * 222 * {@inheritDoc} 223 */ 224 @Override 225 public int getCompatibilityScore(final Request request) 226 { 227 int score = Integer.MIN_VALUE; 228 for (IRequestMapper mapper : this) 229 { 230 score = Math.max(score, mapper.getCompatibilityScore(request)); 231 } 232 return score; 233 } 234 235 @Override 236 public Iterator<IRequestMapper> iterator() 237 { 238 return mappers.iterator(); 239 } 240}