Snippets

StackHub SkyArc License Helper for StackHub Products

Created by Steve Eynon last modified

SkyArc License Helper for StackHub Products

A helper class that finds valid StackHub licences on SkyArc systems. Note that the StackHubLic class and extension disabling is only available in SkySpark 3.0.12 or later.

Copy and paste the Fantom class below into your SkyArc project.

Repeated calls to the findXXX() methods whittle down the number of valid licences until one is left. The remaining licence may be retrieved with get().

A LicErr is thrown should no licence match the given criteria. This may be caught to disable the containing SkyArc extension.

licence validation may be performed during the extension onStart() event. Typical usage would be:

using skyarcd::Ext
using skyarcd::ExtMeta
using haystack::Ref
using stackhub::StackHubLic

@ExtMeta { name = "acmeExt" }
const class AcmeExt : Ext {

   ** Validate the licence file.
   override Void onStart() {
       try {
           lic := LicHelper(sys)
               .findVendor(Ref("87654321-87654321", "Acme"))
               .findPackage(Depend("acmeExt 1.0"))
               .findProduct(Ref("12345678-12345678", "AcmeExt"))
               .validate.get

           cap := LicHelper.parseCapacity(lic)

           // ... validate licence capacity here ...

           log.info("${lic.product} licensed to ${lic.licencee} --> okay")

       } catch (LicErr err) {
           // if a valid licence can not be found, disable this extension
           StackHubLic.extToFault(this, err.msg)
       }
   }
}

Note findVendor() should be called with your StackHub Vendor ID available from the My Products page.

findProduct() should be called with your StackHub Product ID, available from the My Product edit pages.

findPackage() should probably be called with the details of the current pod:

findPackage(Depend("${typeof.pod.name} ${typeof.pod.version}"))

Contact StackHub should you require help with Fantom code.

using stackhub::StackHubLic
using haystack::Ref
using skyarcd::Sys

** A helper class that finds valid StackHub licences.
** 
** Repeated calls to the 'findXXX()' methods whittle down the number of valid licences until one is 
** left. The remaining licence may be retrieved with 'get()'.
** 
** A'LicErr' is thrown should no licence match the given criteria. This may be caught to disable 
** the containing SkyArc extension.
** 
** licence validation may be performed during the extension 'onStart()' event. Typical usage would be:
** 
** pre>
** using skyarcd::Ext
** using skyarcd::ExtMeta
** using haystack::Ref
** using stackhub::StackHubLic
** 
** @ExtMeta { name = "acmeExt" }
** const class AcmeExt : Ext {
**
**    ** Validate the licence file.
**    override Void onStart() {
**        try {
**            lic := LicHelper(sys)
**                .findVendor(Ref("87654321-87654321", "Acme"))
**                .findPackage(Depend("acmeExt 1.0"))
**                .findProduct(Ref("12345678-12345678", "AcmeExt"))
**                .validate.get
** 
**            cap := LicHelper.parseCapacity(lic)
** 
**            // ... validate licence capacity here ...
**
**            log.info("${lic.product} licensed to ${lic.licencee} --> okay")
**            
**        } catch (LicErr err) {
**            // if a valid licence can not be found, disable this extension
**            StackHubLic.extToFault(this, err.msg)
**        }
**    }
**}
** <pre
** 
** Note 'findVendor()' should be called with your StackHub Vendor ID available from the 
** [My Products]`https://stackhub.org/my/products/` page.
** 
** 'findProduct()' should be called with your StackHub Product ID, available from the
** [My Product]`https://stackhub.org/my/products/` edit pages. 
** 
** 'findPackage()' should probably be called with the details of the current pod:
** 
**   findPackage(Depend("${typeof.pod.name} ${typeof.pod.version}"))
** 
class LicHelper {
	** StackHub licences.
	StackHubLic[] lics

	** Creates a helper with all available StackHub licences.
	new makeSys(Sys sys) {
		this.lics = StackHubLic.list(sys)
	}

	** Creates a helper with the given licences.
	new makeLics(StackHubLic[] lics) {
		this.lics = lics
	}

	** Finds all StackHub licences created by the given StackHub vendor ID.
	** Throws 'LicErr' if none are found and 'checked' is 'true'.
	** 
	** Note that the 'dis' part of the 'Ref' is ignored for matching purposes.
	This findVendor(Ref vendorRef, Bool checked := true) {
		whittleDown("Could not find licence for vendor: ${refDis(vendorRef)}", checked) { vendorRef == it.vendor }
	}

	** Finds all StackHub licences for the given product.
	** Throws 'LicErr' if none are found and 'checked' is 'true'.
	** 
	** Note that the 'dis' part of the 'Ref' is ignored for matching purposes.
	This findProduct(Ref productRef, Bool checked := true) {
		whittleDown("Could not find licence for product: ${refDis(productRef)}", checked) { productRef == it.product }
	}

	** Finds all StackHub licences that match *any* of the given products.
	** Throws 'LicErr' if none are found and 'checked' is 'true'.
	** 
	** Note that the 'dis' part of the 'Ref' is ignored for matching purposes.
	This findProducts(Ref[] productRefs, Bool checked := true) {
		whittleDown("Could not find licence for products: " + productRefs.join(", ") { refDis(it) }, checked) { productRefs.contains(it.product) }		
	}

	** Finds all StackHub licences that match the given package.
	** Throws 'LicErr' if none are found and 'checked' is 'true'.
	This findPackage(Depend pkg, Bool checked := true) {
		if (pkg.size != 1 || pkg.isPlus || pkg.isRange) 
			throw ArgErr("Package should have a simple version: $pkg")
		return whittleDown("Could not find licence for package: ${pkg}", checked) |StackHubLic lic->Bool| {
			deps := (Depend[]) lic.props.get("packages", "").split(';').exclude { it.isEmpty }.map { Depend(it) }
			return deps.any |dep| {
				dep.name == pkg.name && dep.match(pkg.version)
			}
		}
	}
	
	** Finds all StackHub licences that match the given function.
	** Throws 'LicErr' (with 'errMsg') if none are found and 'checked' is 'true'.
	This findAll(Str errMsg, |StackHubLic->Bool| func, Bool checked := true) {
		whittleDown(errMsg, checked, func)
	}
	
	** Asserts that 'lics' contains exactly one licence and is valid.
	** Throws 'LicErr' if not.
	This validate() {
		lic := get(true)
		if (!lic.isValid)
			throw LicErr("Invalid licence - ${lic.err}", [lic])
		return this
	}
	
	** Returns the first licence from 'lics'. Throws 'LicErr' if 'lics' is empty or has more than 
	** one licence.
	StackHubLic? get(Bool checked := true) {
		if (checked && lics.size > 1)
			throw LicErr("Found multiple valid licences", lics)
		if (checked && lics.size == 0)
			throw LicErr("Could not find licence")
		return lics.first
	}
	
	** Creates a duplicate copy of this helper.
	This dup() {
		LicHelper(lics.dup)
	}
	
	** Parses the 'capacity' field of the given licence into a map of units to quantity.
	** Returns an empty map if the 'capacity' property is not found in the licence.
	static Str:Int parseCapacity(StackHubLic lic) {
		caps  := Str:Int[:]
		lic.props.get("capacity")?.split(';')?.each {
			caps[it.split[1]] = it.split[0].toInt
		}
		return caps
	}
	
	** Whittles down 'lics' and throws 'LicErr' should it become empty and 'checked' is 'true'.
	private This whittleDown(Str errMsg, Bool checked, |StackHubLic->Bool| f) {
		oldLics := lics.dup 
		lics = lics.findAll(f)
		if (checked && lics.isEmpty)
			throw LicErr(errMsg, oldLics)
		return this
	}
	
	** Pretty prints a 'Ref' with its ID and optional 'dis'.
	private static Str refDis(Ref ref) {
		ref.id + (ref.disVal == null ? "" : " ${ref.disVal}")
	}
}

const class LicErr : Err {
	** The licences in error
	const StackHubLic[] lics
	
	** The first licence from 'lics'
	const StackHubLic?	lic
	
	new make(Str msg) : super(msg) {
		this.lics	= StackHubLic#.emptyList
	}

	new makeLics(Str msg, StackHubLic[] lics) : super.make(msg) {
		this.lics	= lics
		this.lic	= lics.first
	}	
}

Comments (0)