1 /* 2 * Copyright 2002-2018 the original author or authors. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 module hunt.stomp.handler.DestinationPatternsMessageCondition; 18 19 import hunt.stomp.handler.AbstractMessageCondition; 20 import hunt.stomp.Message; 21 22 import hunt.collection; 23 import hunt.Nullable; 24 import hunt.text.PathMatcher; 25 26 import std.array; 27 import std.string; 28 29 /** 30 * A {@link MessageCondition} for matching the destination of a Message 31 * against one or more destination patterns using a {@link PathMatcher}. 32 * 33 * @author Rossen Stoyanchev 34 * @since 4.0 35 */ 36 class DestinationPatternsMessageCondition 37 : AbstractMessageCondition!(DestinationPatternsMessageCondition, string) { 38 39 /** 40 * The name of the "lookup destination" header. 41 */ 42 enum string LOOKUP_DESTINATION_HEADER = "lookupDestination"; 43 44 45 private Set!(string) patterns; 46 47 private PathMatcher pathMatcher; 48 49 50 /** 51 * Creates a new instance with the given destination patterns. 52 * Each pattern that is not empty and does not start with "/" is prepended with "/". 53 * @param patterns 0 or more URL patterns; if 0 the condition will match to every request. 54 */ 55 this(string[] patterns...) { 56 this(patterns.dup, null); 57 } 58 59 /** 60 * Alternative constructor accepting a custom PathMatcher. 61 * @param patterns the URL patterns to use; if 0, the condition will match to every request. 62 * @param pathMatcher the PathMatcher to use 63 */ 64 this(string[] patterns, PathMatcher pathMatcher) { 65 this(new ArrayList!(string)(patterns), pathMatcher); 66 } 67 68 private this(Collection!(string) patterns, PathMatcher pathMatcher) { 69 this.pathMatcher = (pathMatcher !is null ? pathMatcher : new AntPathMatcher()); 70 this.patterns = prependLeadingSlash(patterns, this.pathMatcher); 71 } 72 73 74 private static Set!(string) prependLeadingSlash(Collection!(string) patterns, PathMatcher pathMatcher) { 75 bool slashSeparator = pathMatcher.combine("a", "a") == ("a/a"); 76 Set!(string) result = new LinkedHashSet!(string)(patterns.size()); 77 foreach (string pattern ; patterns) { 78 if (slashSeparator && !pattern.empty() && !pattern.startsWith("/")) { 79 pattern = "/" ~ pattern; 80 } 81 result.add(pattern); 82 } 83 return result; 84 } 85 86 87 Set!(string) getPatterns() { 88 return this.patterns; 89 } 90 91 override 92 protected Collection!(string) getContent() { 93 return this.patterns; 94 } 95 96 override 97 protected string getToStringInfix() { 98 return " || "; 99 } 100 101 102 /** 103 * Returns a new instance with URL patterns from the current instance ("this") and 104 * the "other" instance as follows: 105 * <ul> 106 * <li>If there are patterns in both instances, combine the patterns in "this" with 107 * the patterns in "other" using {@link hunt.framework.util.PathMatcher#combine(string, string)}. 108 * <li>If only one instance has patterns, use them. 109 * <li>If neither instance has patterns, use an empty string (i.e. ""). 110 * </ul> 111 */ 112 // override 113 DestinationPatternsMessageCondition combine(DestinationPatternsMessageCondition other) { 114 Set!(string) result = new LinkedHashSet!(string)(); 115 if (!this.patterns.isEmpty() && !other.patterns.isEmpty()) { 116 foreach (string pattern1 ; this.patterns) { 117 foreach (string pattern2 ; other.patterns) { 118 result.add(this.pathMatcher.combine(pattern1, pattern2)); 119 } 120 } 121 } 122 else if (!this.patterns.isEmpty()) { 123 result.addAll(this.patterns); 124 } 125 else if (!other.patterns.isEmpty()) { 126 result.addAll(other.patterns); 127 } 128 else { 129 result.add(""); 130 } 131 return new DestinationPatternsMessageCondition(result, this.pathMatcher); 132 } 133 134 /** 135 * Check if any of the patterns match the given Message destination and return an instance 136 * that is guaranteed to contain matching patterns, sorted via 137 * {@link hunt.framework.util.PathMatcher#getPatternComparator(string)}. 138 * @param message the message to match to 139 * @return the same instance if the condition contains no patterns; 140 * or a new condition with sorted matching patterns; 141 * or {@code null} either if a destination can not be extracted or there is no match 142 */ 143 // override 144 DestinationPatternsMessageCondition getMatchingCondition(MessageBase message) { 145 string destination = cast(string)cast(Nullable!string)message.getHeaders().get(LOOKUP_DESTINATION_HEADER); 146 if (destination is null) { 147 return null; 148 } 149 if (this.patterns.isEmpty()) { 150 return this; 151 } 152 153 List!(string) matches = new ArrayList!(string)(); 154 foreach (string pattern ; this.patterns) { 155 if (pattern == destination || this.pathMatcher.match(pattern, destination)) { 156 matches.add(pattern); 157 } 158 } 159 if (matches.isEmpty()) { 160 return null; 161 } 162 163 // TODO: Tasks pending completion -@zxp at 10/31/2018, 10:08:57 AM 164 // 165 // matches.sort(this.pathMatcher.getPatternComparator(destination)); 166 return new DestinationPatternsMessageCondition(matches, this.pathMatcher); 167 } 168 169 /** 170 * Compare the two conditions based on the destination patterns they contain. 171 * Patterns are compared one at a time, from top to bottom via 172 * {@link hunt.framework.util.PathMatcher#getPatternComparator(string)}. 173 * If all compared patterns match equally, but one instance has more patterns, 174 * it is considered a closer match. 175 * <p>It is assumed that both instances have been obtained via 176 * {@link #getMatchingCondition(Message)} to ensure they contain only patterns 177 * that match the request and are sorted with the best matches on top. 178 */ 179 // override 180 int compareTo(DestinationPatternsMessageCondition other, MessageBase message) { 181 string destination = cast(string)cast(Nullable!string)message.getHeaders().get(LOOKUP_DESTINATION_HEADER); 182 if (destination is null) { 183 return 0; 184 } 185 186 // TODO: Tasks pending completion -@zxp at 10/31/2018, 10:09:43 AM 187 // 188 return 1; 189 190 // Comparator!(string) patternComparator = this.pathMatcher.getPatternComparator(destination); 191 // Iterator!(string) iterator = this.patterns.iterator(); 192 // Iterator!(string) iteratorOther = other.patterns.iterator(); 193 // while (iterator.hasNext() && iteratorOther.hasNext()) { 194 // int result = patternComparator.compare(iterator.next(), iteratorOther.next()); 195 // if (result != 0) { 196 // return result; 197 // } 198 // } 199 200 // if (iterator.hasNext()) { 201 // return -1; 202 // } 203 // else if (iteratorOther.hasNext()) { 204 // return 1; 205 // } 206 // else { 207 // return 0; 208 // } 209 } 210 211 }