Skip to content

Toolkit Enhancements

thofrey edited this page Apr 1, 2014 · 7 revisions

Table of Contents

  1. Overview
  2. Assertions via Assert.cfc
  3. Simple Pattern Matching with SimplePatternMatcher.cfc
  4. 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.

Overview

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:

Assertions via Assert.cfc

Overview

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.

Before and After Examples

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.

Using Assertions in Mach-II Extended Components

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.") />

Assertion Methods

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.

doesNotContain Method

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.

Example Syntax
    <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.

Arguments
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.

hasLength Method

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.

Example Syntax
    <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.

Arguments
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.

hasText Method

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.

Example Syntax
    <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.

Arguments
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.

isNumber Method

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).

Example Syntax
    <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).

Arguments
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.

isTrue Method

The isTrue method asserts that the given expression is true.

Example Syntax
    <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.

Arguments
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.

notEmpty Method

The notEmpty method asserts that the passed query, struct or array is not empty.

Example Syntax
    <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.

Arguments
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.

Simple Pattern Matching with SimplePatternMatcher.cfc

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.

Example Patterns

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

Public Methods

The init method

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() />

The isPattern method

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

The match method

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.

Advanced Path Matching via Patterns with AntPathMatcher.cfc

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.

Wildcards

The utility uses three different wildcards.

Wilcard Description
* Matches zero or more characters.
? Matches exactly one character.
** Matches zero or more directories.

Example Patterns

    <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
.

Public Methods

The init method

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() />

The isPattern method

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

The match method

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

The matchStart method

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

The extractPathWithinPattern method

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
Clone this wiki locally