001: /* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
002: *
003: * Licensed under the Apache License, Version 2.0 (the "License");
004: * you may not use this file except in compliance with the License.
005: * You may obtain a copy of the License at
006: *
007: * http://www.apache.org/licenses/LICENSE-2.0
008: *
009: * Unless required by applicable law or agreed to in writing, software
010: * distributed under the License is distributed on an "AS IS" BASIS,
011: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
012: * See the License for the specific language governing permissions and
013: * limitations under the License.
014: */
015:
016: package org.acegisecurity.intercept.web;
017:
018: import java.beans.PropertyEditorSupport;
019: import java.io.BufferedReader;
020: import java.io.IOException;
021: import java.io.StringReader;
022: import java.util.ArrayList;
023: import java.util.List;
024:
025: import org.acegisecurity.util.StringSplitUtils;
026: import org.apache.commons.logging.Log;
027: import org.apache.commons.logging.LogFactory;
028: import org.springframework.util.StringUtils;
029:
030: /**
031: * Property editor to assist with the setup of a {@link FilterInvocationDefinitionSource}.<p>The class creates and
032: * populates a {@link RegExpBasedFilterInvocationDefinitionMap} or {@link PathBasedFilterInvocationDefinitionMap}
033: * (depending on the type of patterns presented).</p>
034: * <p>By default the class treats presented patterns as regular expressions. If the keyword
035: * <code>PATTERN_TYPE_APACHE_ANT</code> is present (case sensitive), patterns will be treated as Apache Ant paths
036: * rather than regular expressions.</p>
037: *
038: * @author Ben Alex
039: * @version $Id: FilterInvocationDefinitionSourceEditor.java 1784 2007-02-24 21:00:24Z luke_t $
040: */
041: public class FilterInvocationDefinitionSourceEditor extends
042: PropertyEditorSupport {
043: //~ Static fields/initializers =====================================================================================
044:
045: private static final Log logger = LogFactory
046: .getLog(FilterInvocationDefinitionSourceEditor.class);
047: public static final String DIRECTIVE_CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON = "CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON";
048: public static final String DIRECTIVE_PATTERN_TYPE_APACHE_ANT = "PATTERN_TYPE_APACHE_ANT";
049:
050: //~ Methods ========================================================================================================
051:
052: public void setAsText(String s) throws IllegalArgumentException {
053: FilterInvocationDefinitionDecorator source = new FilterInvocationDefinitionDecorator();
054:
055: if ((s == null) || "".equals(s)) {
056: // Leave target object empty
057: source
058: .setDecorated(new RegExpBasedFilterInvocationDefinitionMap());
059: } else {
060: // Check if we need to override the default definition map
061: if (s.lastIndexOf(DIRECTIVE_PATTERN_TYPE_APACHE_ANT) != -1) {
062: source
063: .setDecorated(new PathBasedFilterInvocationDefinitionMap());
064:
065: if (logger.isDebugEnabled()) {
066: logger
067: .debug(("Detected "
068: + DIRECTIVE_PATTERN_TYPE_APACHE_ANT + " directive; using Apache Ant style path expressions"));
069: }
070: } else {
071: source
072: .setDecorated(new RegExpBasedFilterInvocationDefinitionMap());
073: }
074:
075: if (s
076: .lastIndexOf(DIRECTIVE_CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON) != -1) {
077: if (logger.isDebugEnabled()) {
078: logger
079: .debug("Detected "
080: + DIRECTIVE_CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
081: + " directive; Instructing mapper to convert URLs to lowercase before comparison");
082: }
083:
084: source.setConvertUrlToLowercaseBeforeComparison(true);
085: }
086:
087: BufferedReader br = new BufferedReader(new StringReader(s));
088: int counter = 0;
089: String line;
090:
091: List mappings = new ArrayList();
092:
093: while (true) {
094: counter++;
095:
096: try {
097: line = br.readLine();
098: } catch (IOException ioe) {
099: throw new IllegalArgumentException(ioe.getMessage());
100: }
101:
102: if (line == null) {
103: break;
104: }
105:
106: line = line.trim();
107:
108: if (logger.isDebugEnabled()) {
109: logger.debug("Line " + counter + ": " + line);
110: }
111:
112: if (line.startsWith("//")) {
113: continue;
114: }
115:
116: // Attempt to detect malformed lines (as per SEC-204)
117: if (line
118: .lastIndexOf(DIRECTIVE_CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON) != -1) {
119: // Directive found; check for second directive or name=value
120: if ((line
121: .lastIndexOf(DIRECTIVE_PATTERN_TYPE_APACHE_ANT) != -1)
122: || (line.lastIndexOf("=") != -1)) {
123: throw new IllegalArgumentException(
124: "Line appears to be malformed: " + line);
125: }
126: }
127:
128: // Attempt to detect malformed lines (as per SEC-204)
129: if (line.lastIndexOf(DIRECTIVE_PATTERN_TYPE_APACHE_ANT) != -1) {
130: // Directive found; check for second directive or name=value
131: if ((line
132: .lastIndexOf(DIRECTIVE_CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON) != -1)
133: || (line.lastIndexOf("=") != -1)) {
134: throw new IllegalArgumentException(
135: "Line appears to be malformed: " + line);
136: }
137: }
138:
139: // Skip lines that are not directives
140: if (line.lastIndexOf('=') == -1) {
141: continue;
142: }
143:
144: if (line.lastIndexOf("==") != -1) {
145: throw new IllegalArgumentException(
146: "Only single equals should be used in line "
147: + line);
148: }
149:
150: // Tokenize the line into its name/value tokens
151: // As per SEC-219, use the LAST equals as the delimiter between LHS and RHS
152: String name = StringSplitUtils.substringBeforeLast(
153: line, "=");
154: String value = StringSplitUtils.substringAfterLast(
155: line, "=");
156:
157: if (!StringUtils.hasText(name)
158: || !StringUtils.hasText(value)) {
159: throw new IllegalArgumentException(
160: "Failed to parse a valid name/value pair from "
161: + line);
162: }
163:
164: // Attempt to detect malformed lines (as per SEC-204)
165: if (source.isConvertUrlToLowercaseBeforeComparison()
166: && source.getDecorated() instanceof PathBasedFilterInvocationDefinitionMap) {
167: // Should all be lowercase; check each character
168: // We only do this for Ant (regexp have control chars)
169: for (int i = 0; i < name.length(); i++) {
170: String character = name.substring(i, i + 1);
171:
172: if (!character.toLowerCase().equals(character)) {
173: throw new IllegalArgumentException(
174: "You are using the "
175: + DIRECTIVE_CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
176: + " with Ant Paths, yet you have specified an uppercase character in line: "
177: + line + " (character '"
178: + character + "')");
179: }
180: }
181: }
182:
183: FilterInvocationDefinitionSourceMapping mapping = new FilterInvocationDefinitionSourceMapping();
184: mapping.setUrl(name);
185:
186: String[] tokens = org.springframework.util.StringUtils
187: .commaDelimitedListToStringArray(value);
188:
189: for (int i = 0; i < tokens.length; i++) {
190: mapping.addConfigAttribute(tokens[i].trim());
191: }
192:
193: mappings.add(mapping);
194: }
195: source.setMappings(mappings);
196: }
197:
198: setValue(source.getDecorated());
199: }
200: }
|