-
Notifications
You must be signed in to change notification settings - Fork 24
Toolkit Enhancements
- Overview
-
Assertions via
Assert.cfc
-
Simple Pattern Matching with
SimplePatternMatcher.cfc
-
Advanced Path Matching via Patterns with
AntPathMatcher.cfc
This was a working document and out of date. See the concrete documentation for each feature in the Overview section.
These M2SFP features has been filed under tickets listed in the overview section.
Various minor to somewhat large toolkit enhancements have been planned for Mach-II 1.8. The purpose of this document is to provide details on the proposed implementation of these various enhancements:
- Assert Documentation- (trac-wiki) #216 "enhancement: Implement a simple assertion utility (closed: completed)")
- Simple Pattern Matcher Documentation- (trac-wiki) #215 "enhancement: Implement Simple Pattern Matcher (closed: completed)")
- ANT Path Matcher Documentation - (trac-wiki) #217 "enhancement: Implement ANT path matcher utility (closed: completed)")
This new utility CFC provides assertion methods to aid in the basic validation of arguments. An assertion in this case is an expectation or assumption that something must be true at run time for the code to continue to work. A typical location to do an assertion is to check configure-time parameters in a Mach-II filter at runtime in an effort to "assert" they are valid and to thrown an exception if an assertion fails. A major advantage of doing run-time checking of parameters that when an assertion fails does occur it is detected immediately rather than later through its often obscure side-effects. It is easy to pin-point the error without time consuming debugging because if an assertion fails the developer receives an exception message (and optional details) along with the location of the failed assertion.
The Assert.cfc
that comes bundled with Mach-II offers several different types of basic assertion methods that we will look at later. However, it should be noted that the assertion utility is not designed to be used for user input validation (such as form validation), but as an utility to aid developers in performing simple checking of incoming arguments or parameters.
Here is an example of some basic validation of a configuration parameter used in the timespan cache strategy that is bundled with Mach-II.
<cfif isParameterDefined("timespan")>
<cfif getParameter("timespan") NEQ "forever" AND ListLen(getParameter("timespan")) NEQ 4>
<cfthrow type="MachII.caching.strategies.TimeSpanCache"
message="Invalid timespan of '#getParameter("timespan")#'."
detail="Timespan must be set to 'forever' or a list of 4 numbers (days, hours, minutes, seconds)." />
<cfelse>
<cfset setTimespanString(getParameter("timespan")) />
</cfif>
</cfif>
We need to validate that the incoming parameter timespan
has the value of either forever
or 0,0,10,0
. This is not the most bullet-proof validation of configuration parameters, but it is sufficient in aiding a developer if they mis-configure the timespan cache. Without throwing an exception, the developer might be faced with a strange error like Cannot get list element in position 4 (list len is 3)
and cause them to think there is an error in the code base instead of a simple configuration error (like defining the timespan as 0,0,10
and forgetting that it needs to be four list entries long).
Below is an example using an assertion that will throw an exception if the expression (the first argument of the isTrue
method) evaluates to false.
<cfif isParameterDefined("timespan")
AND getAssert().isTrue(getParameter("timespan") EQ "forever" OR ListLen(getParameter("timespan")) EQ 4
, "Invalid timespan of '#getParameter("timespan")#'."
, "Timespan must be set to 'forever' or a list of 4 numbers (days, hours, minutes, seconds).")>
<cfset setTimespanString(getParameter("timespan")) />
</cfif>
As you can see, the assert code is easier to read and has more compact code.
Performing assertions on incoming parameters in Listeners, Filters, Plugins and Properties are important aspect of developing these components. The Mach-II Assert class is available in all Mach-II developer extended components and can be gotten by calling the following code in our example listener CFC:
<cfset getAssert().hasText(getParameter('someParam'), "The 'someParam' parameter needs to have some text.") />
The MachII.utils.Assert
CFC comes bundled with six basic assertion methods. Let's take a deeper look at what can be done with these methods.
The doesNotContain
method asserts that the given text does not contain the given substring (case-senstive). It the substring is found, an exception is thrown.
<cfset assert = CreateObject("component", "MachII.util.Assert).init() />
<cfset substring = "Jumps" />
<cfset assert.doesNotContain("The quick brown fox jumps over the lazy dog", substring, "The string contains substring which it should not.") />
The above code would not generate an exception at runtime because the text does not have the substring of Jumps
(case-sensitive) in it and therefor no match is found.
Argument Name | Required | Default | Description |
---|---|---|---|
text | true | n/a | The text to check the substring against. |
substring | true | n/a | The substring to find within the text. (case-sensitive) |
message | false | [Assertion failed] - this text argument must not contain the substring '#arguments.substring#'. | The message to throw if the assertion fails. |
detail | false | nothing | The detail (additional information) to throw if the assertion fails. |
The hasLength
method asserts that the given text is not empty (does not trim the incoming text). If the length of the text is 0, an exception is thrown.
<cfset assert = CreateObject("component", "MachII.util.Assert).init() />
<cfset howSmartAmI = "" />
<cfset assert.hasLength(howSmartAmI, "You must not be smart, because the length is 0.") />
The above code would generate an exception at runtime because the howSmartAmI
variable currently has a length of 0.
Argument Name | Required | Default | Description |
---|---|---|---|
text | true | n/a | The text to check the length. |
message | false | [Assertion failed] - this text argument must have length; it cannot be empty. | The message to throw if the assertion fails. |
detailfalse | nothing | The detail (additional information) to throw if the assertion fails. |
The hasText
method assert that the given string has valid text content; it must not be a zero length string and must contain at least one non-whitespace character.
<cfset assert = CreateObject("component", "MachII.util.Assert).init() />
<cfset text = "I seriously blushed when I sprouted that corn stalk from my cabeza." />
<cfset assert.hasText(text, "You have corn stalks in your head!?!?!") />
The above code would not throw an exception because the text variables has at least one non-whitespace character in it.
Argument Name | Required | Default | Description |
---|---|---|---|
text | true | n/a | The text to check if there at least one non-whitespace character. |
message | false | [Assertion failed] - this text argument must contain valid text content. | The message to throw if the assertion fails. |
detail | false | nothing | The detail (additional information) to throw if the assertion fails. |
The isNumber
method assert that the given text is a number such as a whole number (ex. 10
) or a decimal (ex. 9000.25
). This will fail if there are commas in the string (such to denote the thousands place).
<cfset assert = CreateObject("component", "MachII.util.Assert).init() />
<cfset dollarsInBankAccount = 1,101.53 />
<cfset assert.isNumber(dollarsInBankAccount , "That couldn't be the amount in your bank account!") />
The above assertion would thrown an exception because it is not a valid number (due to the comma).
Argument Name | Required | Default | Description |
---|---|---|---|
text | true | n/a | The text to check if a number (10 , 9000.25 , etc.; no commas). |
message | false | [Assertion failed] - this text argument must be numeric. | The message to throw if the assertion fails. |
detail | false | nothing | The detail (additional information) to throw if the assertion fails. |
The isTrue
method asserts that the given expression is true.
<cfset assert = CreateObject("component", "MachII.util.Assert).init() />
<cfset a = RandRange(0, 100) />
<cfset b = RandRange(0, 100) />
<cfset assert.isTrue(a GT b, "It appears that '#a#' is less than '#b#'.") />
It is unknown whether the assert would pass or fail because the values are randomly generated, however if an exception was thrown we would be able to see what numbers were randomly generated.
Argument Name | Required | Default | Description |
---|---|---|---|
expression | true | n/a | The expression to check if not true. |
message | false | [Assertion failed] - this expression argument must be true. |
The message to throw if the assertion fails. |
detail | false | nothing | The detail (additional information) to throw if the assertion fails. |
The notEmpty
method asserts that the passed query, struct or array is not empty.
<cfset assert = CreateObject("component", "MachII.util.Assert).init() />
<cfset people = ArrayNew(1) />
<cfset assert.notEmpty(people, "It appears that there are no people!") />
The above code would generate an exception because the people variable is empty.
Argument Name | Required | Default | Description |
---|---|---|---|
object | true | n/a | The object (query, struct or array) to check if not empty. |
message | false | [Assertion failed] - this object argument cannot be an empty query, struct or array. | The message to throw if the assertion fails. |
detail | false | nothing | The detail (additional information) to throw if the assertion fails. |
The MachII.util.SimplePatternMatcher
(1.8) or MachII.util.matching.SimplePatternMatcher
(1.9+) is a new utility introduced in Mach-II 1.8 that makes matches with a simple *
wildcard. This allows you to check a string against a pattern using the *
wildcard. For complex pattern matching when a path separator is being used such as a file path (/
or \
) or url (/
) use the AntPathMatcher.cfc
.
Pattern | Text to Match | Does Pattern Match? |
---|---|---|
[nothing] | [nothing] | false (no pattern) |
1 |
[nothing] | false |
* |
Mach-II |
true |
123 |
123 |
true (exact match) |
get* |
getMe |
true |
get* |
setMe |
false |
*stuff* |
getMeTest |
false |
*stuff* |
getstuffTest |
true |
3*3 |
3 |
false |
3*3 |
33 |
true |
12*45*78 |
12345678 |
true |
12*45*78 |
123456789 |
false |
This method initializes the simple pattern matcher and takes no arguments..
This is how you would create an instance of the utility (1.8):
<!--- Using the default path separator --->
<cfset matcher = CreateObject("component", "MachII.util.SimplePatternMatcher").init() />
This is how you would create an instance of the utility (1.9+):
<!--- Using the default path separator --->
<cfset matcher = CreateObject("component", "MachII.util.matching.SimplePatternMatcher").init() />
This method checks if the passed path has a pattern in it by checking to see if the pattern has any *
's in it and returns a boolean result.
Example code:
<cfoutput>
Is 'test.*' a pattern: #matcher.isPattern("test.*")#</br>
Is 'test...' a pattern: #matcher.isPattern("test...")#
</cfoutput>
Example output:
Is 'test.*' a pattern: true
Is 'test...' a pattern: false
Performs a match of simple a pattern or array of patterns against the text. It can perform a match against a single pattern or an array of patterns.
Example code using a single pattern:
<cfoutput>Does this pattern match: #matcher.match("give*", "giveMeLotsOfMoney")#</cfoutput>
Example output using a single pattern:
Does this pattern match: true
Example code using multiple patterns in an array:
<cfset patterns = ArrayNew(1) />
<cfset patterns[1] = "123*" />
<cfset patterns[2] = "abc*" />
<cfset patterns[3] = "m2*" />
<cfoutput>Does this pattern match: #matcher.match(patterns, "12345678")#</cfoutput>
Example output using multiple patterns in an array:
Does this pattern match: true (matches the 123* pattern)
For a real world example of the simple pattern matcher in use, check out Removing Event-Handlers for Security Reasons wiki entry.
The MachII.util.AntPathMatcher
(1.8) or MachII.util.matching.AntPathMatcher
(1.9+) is a new utility that was introduced back in Mach-II 1.6 that makes matches against a path with a pattern using ANT-style wildcards. These wildcards look like RegEx? however it they really denote directives. This utility is the major algorithm that drives the view-loader functionality (load views by conventions) that was introduced in Mach-II 1.8.
One of the more common uses for the utility is see if a file path matches a certain pattern. This is useful for loading files on convention. For example, we use the utility for the new pattern view-loader in Mach-II 1.8 which loads all views that match a certain pattern. By default, the pattern used for the view-loader is /views/**/*.cfm
which will match all .cfm
files (the *.cfm
part of the pattern) in the /views
directory (the /views/
part of the pattern) and any directory below that (the **/
part of the pattern and without this part the matched paths would be to .cfm
files that are in the /views/
directory only).
It should be noted that this utility can be used for things other than file paths. Any path that uses path separator (such as .
dots in CFC paths) can be used. The utility uses /
as the path separator when matching patterns against a path, but this be changed by passing in an argument to the init
method of the utility. For example, you could use the utility to match URLs against a pattern.
The utility uses three different wildcards.
Wilcard | Description |
---|---|
* |
Matches zero or more characters. |
? |
Matches exactly one character. |
** |
Matches zero or more directories. |
<td><em>Matches:</em><br />
/views/products/index.cfm<br />
/views/products/SE10/index.cfm<br />
/views/products/SE10/details.cfm<br />
/views/products/ST80/index.cfm<br />
/views/products/ST80/details.cfm<br />
<br />
<em>Does Not Match:</em><br />
/views/index.cfm<br />
/views/aboutUs/index.cfm<br />
/views/aboutUs/managementTeam.cfm</td>
</tr>
<tr>
<td>/views/**/*.cfm<br />
.</td>
<td><em>Matches:</em><br />
/views/index.cfm<br />
/views/aboutUs/index.cfm<br />
/views/aboutUs/managementTeam.cfm<br />
/views/products/index.cfm<br />
/views/products/SE10/index.cfm<br />
/views/products/SE10/details.cfm<br />
/views/products/ST80/index.cfm<br />
/views/products/ST80/details.cfm<br />
<br />
<em>Does Not Match:</em><br />
/views/index.htm<br />
/views/readme.txt</td>
</tr>
<tr>
<td>/views/index??.cfm<br />
_</td>
<td><em>Matches:</em><br />
/views/index01.cfm<br />
/views/index02.cfm<br />
/views/indexAA.cfm<br />
<br />
<em>Does Not Match:</em><br />
/views/index01.htm<br />
/views/index1.cfm<br />
/views/indexA.cfm<br />
/views/indexOther.cfm<br />
/views/anotherDir/index01.cfm<br />
<br />
(Remember that ? matches a single character, so the above example matches only
files that start with "index", followed by two wildcard characters and then
".cfm".)</td>
</tr>
</tbody>
Pattern / Path Separator | Pattern Results |
---|---|
/views/products/**/*.cfm . |
This method initializes the path pattern matcher and optionally allows you to provide a different path separator (which defaults to /
if not passed).
This is how you would create an instance of the utility (1.8):
<!--- Using the default path separator --->
<cfset matcher = CreateObject("component", "MachII.util.AntPathMatcher").init() />
<!--- Setting with a path separator of '.' --->
<cfset matcher = CreateObject("component", "MachII.util.AntPathMatcher").init(".") />
This is how you would create an instance of the utility (1.9+):
<!--- Using the default path separator --->
<cfset matcher = CreateObject("component", "MachII.util.matcher.SimplePatternMatcher").init() />
This method checks if the passed path has a pattern in it by checking to see if the pattern has any *
's or ?
's in it and returns a boolean result.
Example code:
<cfoutput>
Is '/views/index.cfm' a pattern: #matcher.isPattern("/views/index.cfm")#</br>
Is '/views/*.cfm' a pattern: #matcher.isPattern("/views/*.cfm")#
</cfoutput>
Example output:
Is '/views/index.cfm' a pattern: false
Is '/views/*.cfm' a pattern: true
This method matches the passed path against the pattern according to the matching strategy.
Example code:
<cfoutput>
Does this match: #matcher.match("*bla*/**/bla/**", "XXXblaXXXX/testing/testing/bla/testing/testing")#<br/>
Does this match: #matcher.match("/bla*bla/test", "/blaXXXbl/test")#
</cfoutput>
Example output:
Does this match: true
Does this match: false
This method matches the given path against the corresponding part of the given pattern according to the matching strategy and determines whether the pattern at least matches as far as the given base path goes, assuming that a full path may then match as well.
Example code:
<cfoutput>
Does this match: #matcher.matchStart("test/t*.txt", "test")#<br/>
Does this match: #matcher.matchStart("test*", "tst")#
</cfoutput>
Example output:
Does this match: true
Does this match: false
This method uses a given a pattern and a full path, determine the pattern-mapped part (starting with the element the first wildcard is located in).
Example code:
<cfoutput>
#matcher.extractPathWithinPattern("/d?cs/*", "/docs/cvs/commit")#</br>
#matcher.extractPathWithinPattern("/docs/c?s/*.html", "/docs/cvs/commit.html")#
</cfoutput>
Example output:
docs/cvs/commit
cvs/commit.html