From 3b16962be8cbe6b3925203f40ccc5b26e87ca98d Mon Sep 17 00:00:00 2001 From: Dave Martin Date: Tue, 7 Dec 2021 13:01:11 +0000 Subject: [PATCH 1/7] Prepare for 3.0.2 * Update build.gradle * updated the way to retrieve locale * added exception handler for JSON settings parsing. * change 'suitable for' to be multi-selection (so the value will be a json array) (#50) * bump to 3.0.2-SNAPSHOT * Fix for #54 (#56) * bump to com.gorylenko.gradle-git-properties and a default for "citation.rights.template" * "com.gorylenko.gradle-git-properties" version "2.4.0-rc2" * added missing config - siteDefaultLanguage * fix for deletes Co-authored-by: alexhuang091 Co-authored-by: Nick dos Remedios --- build.gradle | 4 +- grails-app/conf/application.yml | 5 +- .../collectory/CollectionController.groovy | 9 +- .../org/ala/collectory/DataController.groovy | 67 +++---- .../collectory/ProviderGroupController.groovy | 14 +- .../ala/collectory/GbifRegistryService.groovy | 177 +++++++++--------- .../collectory/ProviderGroupService.groovy | 21 +-- .../ala/collectory/CollectoryTagLib.groovy | 25 +++ grails-app/views/dataResource/list.gsp | 2 +- grails-app/views/dataResource/show.gsp | 2 +- 10 files changed, 166 insertions(+), 160 deletions(-) diff --git a/build.gradle b/build.gradle index 9cc6282e..6fe7bbed 100644 --- a/build.gradle +++ b/build.gradle @@ -13,10 +13,10 @@ buildscript { } plugins { - id "com.gorylenko.gradle-git-properties" version "2.3.1" + id "com.gorylenko.gradle-git-properties" version "2.4.0-rc2" } -version "3.0.1" +version "3.0.2-SNAPSHOT" group "au.org.ala" diff --git a/grails-app/conf/application.yml b/grails-app/conf/application.yml index 059f08b5..be2e329a 100644 --- a/grails-app/conf/application.yml +++ b/grails-app/conf/application.yml @@ -200,7 +200,10 @@ rifcs: resource.publicArchive.url.template: "https://biocache.ala.org.au/archives/gbif/@UID@/@UID@.zip" resource.gbifExport.url.template: "https://biocache.ala.org.au/archives/gbif/@UID@/@UID@.zip" citation.template: "Records provided by @entityName@, accessed through ALA website." -citation.link.template = For more information: "@link@" +citation.link.template: "For more information: @link@" +citation.rights.template: "" + +siteDefaultLanguage: "en" networkTypes: - BCI diff --git a/grails-app/controllers/au/org/ala/collectory/CollectionController.groovy b/grails-app/controllers/au/org/ala/collectory/CollectionController.groovy index feb1275a..2cea52fc 100644 --- a/grails-app/controllers/au/org/ala/collectory/CollectionController.groovy +++ b/grails-app/controllers/au/org/ala/collectory/CollectionController.groovy @@ -202,15 +202,12 @@ class CollectionController extends ProviderGroupController { */ static def entitySpecificDescriptionProcessing(collection, params) { // special handling for collection type - if (params.collectionType) { - List collectionTypes = params.list('collectionType') - collection.collectionType = (collectionTypes as JSON).toString() - params.remove('collectionType') - } + collection.collectionType = (params.collectionType as JSON).toString() + params.remove('collectionType') // special handling for keywords def keywords = params.keywords.tokenize(',') - def trimmedKeywords = keywords.collect { return it.trim() } + def trimmedKeywords = keywords.collect {return it.trim()} collection.keywords = (trimmedKeywords as JSON).toString() params.remove('keywords') diff --git a/grails-app/controllers/au/org/ala/collectory/DataController.groovy b/grails-app/controllers/au/org/ala/collectory/DataController.groovy index 67dc8cb5..7a67428a 100644 --- a/grails-app/controllers/au/org/ala/collectory/DataController.groovy +++ b/grails-app/controllers/au/org/ala/collectory/DataController.groovy @@ -749,9 +749,7 @@ class DataController { Contact.withTransaction { c.save(flush: true) } - if (c.hasErrors()) { - c.errors.each { log.error(it.toString()) } - } + c.errors.each { log.error(it.toString()) } addContentLocation "/ws/contacts/${c.id}" def cm = buildContactModel(c) cm.id = c.id @@ -766,9 +764,7 @@ class DataController { Contact.withTransaction { c.save(flush: true) } - if (c.hasErrors()) { - c.errors.each { log.error(it.toString()) } - } + c.errors.each { log.error(it.toString()) } addContentLocation "/ws/contacts/${c.id}" def cm = buildContactModel(c) cm.id = c.id @@ -808,6 +804,7 @@ class DataController { * @param uid the entity instance */ def contactsForEntity = { + check(params) def contactList = params.pg.getContacts().collect { buildContactForModel(it, params.pg.urlForm()) } addContentLocation "/ws/${params.entity}/${params.pg.uid}/contacts" addVaryAcceptHeader() @@ -834,6 +831,7 @@ class DataController { * @param id the database id of the contact relationship (contactFor) */ def contactForEntity = { + check(params) if (params.id) { def cm = buildContactForModel(ContactFor.get(params.id as Long), params.pg.urlForm()) addContentLocation "/ws/${params.entity}/${params.pg.uid}/contacts/${params.id}" @@ -886,7 +884,7 @@ class DataController { map.contactName = it.primaryContact?.contact?.buildName() ?: "" map.contactEmail = it.primaryContact?.contact?.email ?: "" map.contactPhone = it.primaryContact?.contact?.phone ?: "" - map.uri = it.primaryContact ? "${Holders.grailsApplication.config.grails.serverURL}/ws/${ProviderGroup.urlFormFromUid(it.uid)}/${it.uid}/contacts/${it.primaryContact?.id}" : '' + map.uri = it.primaryContact ? "${Holders.grailsApplication.config.grails.serverURL}/ws/${providerGroupService.urlFormFromUid(it.uid)}/${it.uid}/contacts/${it.primaryContact?.id}" : '' return map } @@ -914,7 +912,7 @@ class DataController { def notifyList = { if (params.uid) { def contactFors = ContactFor.findAllByEntityUidAndNotify(params.uid, true).collect { - buildContactForModel(it, ProviderGroup.urlFormFromUid(params.uid)) + buildContactForModel(it, providerGroupService.urlFormFromUid(params.uid)) } render contactFors as JSON } else { @@ -966,41 +964,32 @@ class DataController { */ def updateContactFor = { def ok = check(params) - if (!ok || !params.pg || !params.id || !params.pg.uid){ + if (!ok){ return } - try { - def props = params.json - props.userLastModified = session.username - - def c = Contact.get(params.id) - def cf = ContactFor.findByContactAndEntityUid(c, params.pg.uid) - if (cf) { - // update - bindData(cf, props as Map, ['entityUid']) - Contact.withTransaction { - c.save(flush: true) - } - if (c.hasErrors()) { - c.errors.each { log.error("Validation error - " + it.toString()) } - } - success 'updated' + def props = params.json + props.userLastModified = session.username + //println "body = " + props + def c = Contact.get(params.id) + def cf = ContactFor.findByContactAndEntityUid(c, params.pg.uid) + if (cf) { + // update + bindData(cf, props as Map, ['entityUid']) + c.save(flush: true) + c.errors.each { log.error(it) } + success 'updated' + } else { + // create + if (c) { + params.pg.addToContacts c, + props.role ?: '', + (props.administrator ?: false) as Boolean, + (props.primaryContact ?: false) as Boolean, + props.userLastModified + created 'contactFor', params.pg.uid } else { - // create - if (c) { - params.pg.addToContacts c, - props.role ?: '', - (props.administrator ?: false) as Boolean, - (props.primaryContact ?: false) as Boolean, - props.userLastModified - created 'contactFor', params.pg.uid - } else { - badRequest "contact doesn't exist" - } + badRequest "contact doesn't exist" } - } catch (Exception e){ - log.error(e.getMessage(), e) - badRequest "Problem processing this update" } } diff --git a/grails-app/controllers/au/org/ala/collectory/ProviderGroupController.groovy b/grails-app/controllers/au/org/ala/collectory/ProviderGroupController.groovy index 7348c6a5..93c49c51 100644 --- a/grails-app/controllers/au/org/ala/collectory/ProviderGroupController.groovy +++ b/grails-app/controllers/au/org/ala/collectory/ProviderGroupController.groovy @@ -753,13 +753,15 @@ abstract class ProviderGroupController { log.info ">>${collectoryAuthService?.username()} deleting ${entityName} " + name activityLogService.log collectoryAuthService?.username(), authService?.userInRole(grailsApplication.config.ROLE_ADMIN), pg.uid, Action.DELETE try { - // remove contact links (does not remove the contact) - ContactFor.findAllByEntityUid(pg.uid).each { - log.info "Removing link to contact " + it.contact?.buildName() - it.delete() + Contact.withTransaction { + // remove contact links (does not remove the contact) + ContactFor.findAllByEntityUid(pg.uid).each { + log.info "Removing link to contact " + it.contact?.buildName() + it.delete() + } + // delete + pg.delete(flush: true) } - // delete - pg.delete(flush: true) flash.message = "${message(code: 'default.deleted.message', args: [message(code: "${entityNameLower}.label", default: entityNameLower), name])}" redirect(action: "list") } catch (org.springframework.dao.DataIntegrityViolationException e) { diff --git a/grails-app/services/au/org/ala/collectory/GbifRegistryService.groovy b/grails-app/services/au/org/ala/collectory/GbifRegistryService.groovy index 4892e707..9604e04f 100644 --- a/grails-app/services/au/org/ala/collectory/GbifRegistryService.groovy +++ b/grails-app/services/au/org/ala/collectory/GbifRegistryService.groovy @@ -2,7 +2,9 @@ package au.org.ala.collectory import com.opencsv.CSVWriter import grails.converters.JSON +import groovy.json.JsonOutput import groovy.json.JsonSlurper +import org.apache.http.HttpEntity import org.apache.http.HttpResponse import org.apache.http.auth.AuthScope import org.apache.http.auth.UsernamePasswordCredentials @@ -74,7 +76,7 @@ class GbifRegistryService { def updateRegistration(ProviderGroup dp, Boolean syncContacts, Boolean syncDataResources) throws Exception { if (dp.gbifRegistryKey) { boolean success = updateRegistrationMetadata(dp) - if (success){ + if(success){ log.info("Successfully updated provider in GBIF: ${dp.gbifRegistryKey}") if (syncContacts){ syncContactsForProviderGroup(dp) @@ -123,7 +125,8 @@ class GbifRegistryService { success = true } } else { - log.info("[DRY-RUN] ORGANISATION Registration request for ${dp.uid} - ${dp.name} " + (organisation as JSON).toString()) + log.info("[DRY-RUN] Registration request for ${dp.uid} - ${dp.name}") + log.info((organisation as JSON).toString()) } success } @@ -145,10 +148,14 @@ class GbifRegistryService { // create the organization and update the collectory DB if (!isDryRun()) { - HttpResponse httpResponse = httpPostJson(grailsApplication.config.gbifApi + API_ORGANIZATION, - organisation) + def http = newHttpInstance(); + HttpPost httpPost = new HttpPost(grailsApplication.config.gbifApi + API_ORGANIZATION) + httpPost.setEntity(new StringEntity((organisation as JSON).toString())) + HttpResponse httpResponse = http.execute(httpPost) + HttpEntity entity = httpResponse.getEntity(); + String responseString = EntityUtils.toString(entity, "UTF-8"); + if (httpResponse.statusCode in [200,201,202,203,204]){ - String responseString = EntityUtils.toString(httpResponse.getEntity(), "UTF-8") dp.gbifRegistryKey = responseString.replaceAll('"', "") // more sloppy GBIF responses log.info("Successfully created provider in GBIF: ${dp.gbifRegistryKey}") dp.save(flush: true) @@ -166,7 +173,7 @@ class GbifRegistryService { } } } else { - log.info("[DRY-RUN] ORGANISATION Registration request for ${dp.uid} - ${dp.name}") + log.info("[DRY RUN] Registration request for ${dp.uid} - ${dp.name}") log.info((organisation as JSON).toString()) } } @@ -264,8 +271,6 @@ class GbifRegistryService { if (httpResponse.statusCode in [200,202,204]){ log.info("Removed contact ${it.key as String}") } - } else { - log.info("[DRY-RUN] CONTACTS Removing contacts for ${organisation.name}") } } } @@ -294,8 +299,6 @@ class GbifRegistryService { if (httpResponse.statusCode in [200,202,204]){ log.info("Added contact") } - } else { - log.info("[DRY-RUN] CONTACT " + (gbifContact as JSON).toString()) } } } @@ -361,21 +364,23 @@ class GbifRegistryService { if (!dataResource.gbifRegistryKey) { log.info("Creating GBIF resource for ${dataResource.uid}") def dataset = newGBIFDatasetInstance(dataResource, organisationRegistryKey) - log.info("Creating dataset in GBIF: ${(dataset as JSON).toString()}") + log.info("Creating dataset in GBIF: ${dataset}") if (dataset) { if (!isDryRun()) { - HttpResponse httpResponse = httpPostJson( - grailsApplication.config.gbifApiUrl + MessageFormat.format(API_DATASET, organisationRegistryKey), - dataset - ) - - if (httpResponse.statusCode in [200, 201, 202, 204]){ - String responseString = EntityUtils.toString(httpResponse.getEntity(), "UTF-8") - dataResource.gbifRegistryKey = responseString.replaceAll('"', "") // more sloppy GBIF responses - log.info("Added dataset ${dataResource.gbifRegistryKey}") - log.info("Successfully created dataset in GBIF: ${dataResource.gbifRegistryKey}") - dataResource.save(flush: true) + def http = newHttpInstance(); + http.parser.'application/json' = http.parser.'text/plain' // handle sloppy responses from GBIF + http.request(Method.POST, ContentType.JSON) { req -> + uri.path = MessageFormat.format(API_DATASET, organisationRegistryKey) + body = (dataset as JSON).toString() + + // on success, save the key in GBIF + response.success = { resp, reader -> + dataResource.gbifRegistryKey = reader.text.replaceAll('"', "") // more sloppy GBIF responses + log.info("Added dataset ${dataResource.gbifRegistryKey}") + log.info("Successfully created dataset in GBIF: ${dataResource.gbifRegistryKey}") + dataResource.save(flush: true) + } } if (Boolean.valueOf(grailsApplication.config.useGbifDoi)) { @@ -384,7 +389,7 @@ class GbifRegistryService { dataResource.save(flush: true) } } else { - log.info("[DRY-RUN] DATASET Registration request for data resource ${dataset.uid} - ${dataset.name}") + log.info("[DRY RUN] Registration request for ${dataset.uid} - ${dataset.name}") log.info((dataset as JSON).toString()) } } else { @@ -394,7 +399,7 @@ class GbifRegistryService { // ensure the organisation is correct in GBIF as ownership varies over time, and that the DOI // is used if configured def dataset = loadDataset(dataResource.gbifRegistryKey) - if (!isDryRun()) { + if(!isDryRun()) { if (Boolean.valueOf(grailsApplication.config.useGbifDoi) && dataResource.gbifDoi != dataset.doi) { log.info("Setting resource[${dataResource.uid}] to use gbifDOI[${dataset.doi}]") dataResource.gbifDoi = dataset.doi @@ -406,24 +411,21 @@ class GbifRegistryService { dataset.publishingOrganizationKey = organisationRegistryKey dataset.deleted = null dataset.license = getGBIFCompatibleLicence(dataResource.licenseType) - if (dataset.license) { + if(dataset.license) { def http = newHttpInstance(); def datasetKey = dataResource.gbifRegistryKey - - HttpPut httpPut = new HttpPut( - grailsApplication.config.gbifApiUrl + - MessageFormat.format(API_DATASET_DETAIL, datasetKey), - ) - httpPut.setEntity(new StringEntity((dataset as JSON).toString())) - HttpResponse httpResponse = http.execute(httpPut) - if (httpResponse.statusCode in [200, 202, 204]){ - log.info("Successfully updated dataset in GBIF: ${datasetKey}") + http.request(Method.PUT, ContentType.JSON) { + uri.path = MessageFormat.format(API_DATASET_DETAIL, datasetKey) + body = (dataset as JSON).toString() + response.success = { resp, reader -> + log.info("Successfully updated dataset in GBIF: ${datasetKey}") + } } } else { log.warn("Unable to update dataset - please check license: ${dataResource.uid} : ${dataResource.name} : ${dataResource.licenseType}") } } else { - log.info("[DRY-RUN] DATASET Updating data resource ${(dataset as JSON).toString()}") + log.info("[DRYRUN] Updating data resource ${dataset}") } } syncEndpoints(dataResource) @@ -432,21 +434,20 @@ class GbifRegistryService { def deleteDataResource(DataResource resource){ def http = newHttpInstance() - if (!isDryRun()) { - - HttpDelete httpDelete = new HttpDelete( - grailsApplication.config.gbifApiUrl + - MessageFormat.format(API_DATASET_DETAIL, resource.gbifRegistryKey)) - HttpResponse httpResponse = http.execute(httpDelete) - if (httpResponse.statusCode in [200,202,204]){ - log.info("Deleted Dataset[${resource.gbifRegistryKey}] from GBIF") - resource.gbifRegistryKey = null - resource.save(flush:true) - } else { - log.info("The delete of ${resource.uid} from GBIF was unsuccessful: ${resp.status}") + if(!isDryRun()) { + http.request(Method.DELETE, ContentType.JSON) { req -> + uri.path = MessageFormat.format(API_DATASET_DETAIL, resource.gbifRegistryKey) + response.success = { resp, reader -> + log.info("Deleted Dataset[${resource.gbifRegistryKey}] from GBIF") + resource.gbifRegistryKey = null + resource.save(flush:true) + } + response.failure = { resp -> + log.info("The delete of ${resource.uid} from GBIF was unsuccessful: ${resp.status}") + } } } else { - log.info("[DRY-RUN] DATASET Deleting ${resource.uid}") + log.info("[DryRun] Deleting ${resource.uid}") } resource } @@ -464,6 +465,8 @@ class GbifRegistryService { if(!isDryRun()) { + http.parser.'application/json' = http.parser.'text/plain' // handle sloppy responses from GBIF + def dwcaUrl = grailsApplication.config.resource.gbifExport.url.template.replaceAll("@UID@", resource.getUid()); if (dataset.endpoints && dataset.endpoints.size() == 1 && dwcaUrl.equals(dataset.endpoints.get(0).url)) { @@ -473,13 +476,9 @@ class GbifRegistryService { // delete the existing ones if (dataset.endpoints) { dataset.endpoints.each { - - HttpDelete httpDelete = new HttpDelete( - grailsApplication.config.gbifApiUrl + - MessageFormat.format(API_DATASET_ENDPOINT_DETAIL, resource.gbifRegistryKey, it.key as String)) - HttpResponse httpResponse = http.execute(httpDelete) - if (httpResponse.statusCode in [200, 202, 204]) { - log.info("Removed endpoint ${it.key as String}") + http.request(Method.DELETE, ContentType.JSON) { req -> + uri.path = MessageFormat.format(API_DATASET_ENDPOINT_DETAIL, resource.gbifRegistryKey, it.key as String) + response.success = { resp, reader -> log.info("Removed endpoint ${it.key as String}") } } } } @@ -489,16 +488,16 @@ class GbifRegistryService { "type": "DWC_ARCHIVE", "url" : dwcaUrl ] - - HttpResponse httpResponse = httpPostJson(grailsApplication.config.gbifApiUrl + - MessageFormat.format(API_DATASET_ENDPOINT, resource.gbifRegistryKey), endpoint - ) - if (httpResponse.statusCode in [200, 202, 204]) { - log.info("Created endpoint for Dataset[${resource.gbifRegistryKey}] with URL[${endpoint.url}]") + http.request(Method.POST, ContentType.JSON) { req -> + uri.path = MessageFormat.format(API_DATASET_ENDPOINT, resource.gbifRegistryKey) + body = (endpoint as JSON).toString() + response.success = { resp, reader -> + log.info("Created endpoint for Dataset[${resource.gbifRegistryKey}] with URL[${endpoint.url}]") + } } } } else { - log.info("[DRY-RUN] ENDPOINT syncing endpoints with registry key ${resource.gbifRegistryKey}, for resource ${resource.uid}") + log.info("[DRYRUN] syncing endpoints with registry key ${resource.gbifRegistryKey}, for resource ${resource.uid}") } } else { log.info("Unable to load dataset info from GBIF with registry key ${resource.gbifRegistryKey}, for resource ${resource.uid}. Not syncing.....") @@ -563,13 +562,10 @@ class GbifRegistryService { * Loads all organizations for a specific country from the GBIF API. */ def loadOrganizationsByCountry(String countryCode, int limit = 1000) { - httpGetJson(grailsApplication.config.gbifApiUrl + - MessageFormat.format(API_ORGANIZATION_COUNTRY_LIMIT, countryCode, limit)) - } - - def httpGetJson(url) { def http = newHttpInstance(false) - HttpGet httpGet = new HttpGet(url) + HttpGet httpGet = new HttpGet( + grailsApplication.config.gbifApiUrl + + MessageFormat.format(API_ORGANIZATION_COUNTRY_LIMIT, countryCode, limit)) HttpResponse httpResponse = http.execute(httpGet) ByteArrayOutputStream bos = new ByteArrayOutputStream() httpResponse.getEntity().writeTo(bos) @@ -578,27 +574,36 @@ class GbifRegistryService { return slurper.parseText(respText) } - HttpResponse httpPostJson(url, object){ - def http = newHttpInstance(false) - HttpPost httpPost = new HttpPost(url) - httpPost.setEntity(new StringEntity((object as JSON).toString())) - http.execute(httpPost) - } - /** * Loads an organization from the GBIF API. */ private def loadOrganization(gbifRegistryKey) { - httpGetJson(grailsApplication.config.gbifApiUrl + + def http = newHttpInstance(false) + HttpGet httpGet = new HttpGet( + grailsApplication.config.gbifApiUrl + MessageFormat.format(API_ORGANIZATION_DETAIL, gbifRegistryKey)) + HttpResponse httpResponse = http.execute(httpGet) + ByteArrayOutputStream bos = new ByteArrayOutputStream() + httpResponse.getEntity().writeTo(bos) + String respText = bos.toString(); + JsonSlurper slurper = new JsonSlurper() + return slurper.parseText(respText) } /** * Loads a dataset from the GBIF API. */ private def loadDataset(gbifRegistryKey) { - httpGetJson(grailsApplication.config.gbifApiUrl + - MessageFormat.format(API_DATASET_DETAIL, gbifRegistryKey)) + def http = newHttpInstance(false) + HttpGet httpGet = new HttpGet( + grailsApplication.config.gbifApiUrl + + MessageFormat.format(API_DATASET_DETAIL, gbifRegistryKey)) + HttpResponse httpResponse = http.execute(httpGet) + ByteArrayOutputStream bos = new ByteArrayOutputStream() + httpResponse.getEntity().writeTo(bos) + String respText = bos.toString(); + JsonSlurper slurper = new JsonSlurper() + return slurper.parseText(respText) } /** @@ -613,16 +618,10 @@ class GbifRegistryService { organisation.description = dp.pubDescription organisation.email = [dp.email] organisation.phone = [dp.phone] - if (dp.websiteUrl) { - organisation.homepage = [dp.websiteUrl] - } + organisation.homepage = [dp.websiteUrl] organisation.latitude = Math.floor(dp.latitude as float) == -1.0 ? null : dp.latitude organisation.longitude = Math.floor(dp.longitude as float) == -1.0 ? null : dp.longitude - - String logoUrl = dp.buildLogoUrl() - if (logoUrl) { - organisation.logoUrl = logoUrl - } + organisation.logoUrl = dp.buildLogoUrl() // convert the 3 digit ISO code to the 2 digit ISO code GBIF needs // Note: GBIF use this for counting "data published by Country X". There are cases where the postal Address @@ -871,10 +870,6 @@ class GbifRegistryService { } } } - if (isDryRun()) { - log.info("[DRY-RUN] Sync complete") - } - [ resourcesRegistered : resourcesRegistered, resourcesUpdated : resourcesUpdated, @@ -995,7 +990,7 @@ class GbifRegistryService { * Creates a new instance of an HTTP builder configured with the standard error handling. * By default, use the basic authentication account */ - private HttpClient newHttpInstance(useAuthentication = true) { + private def HttpClient newHttpInstance(useAuthentication = true) { HttpClientBuilder builder = HttpClientBuilder.create() if (useAuthentication) { // GBIF does not return the expected 401 challenge so this needs to be set preemptively diff --git a/grails-app/services/au/org/ala/collectory/ProviderGroupService.groovy b/grails-app/services/au/org/ala/collectory/ProviderGroupService.groovy index 1bf3526a..5c4e4f2a 100644 --- a/grails-app/services/au/org/ala/collectory/ProviderGroupService.groovy +++ b/grails-app/services/au/org/ala/collectory/ProviderGroupService.groovy @@ -2,7 +2,9 @@ package au.org.ala.collectory import grails.converters.JSON import grails.gorm.transactions.Transactional -import org.springframework.context.i18n.LocaleContextHolder +import grails.util.Holders +import org.grails.web.json.JSONArray +import org.grails.web.json.JSONElement import org.springframework.web.context.request.RequestContextHolder @Transactional @@ -11,6 +13,7 @@ class ProviderGroupService { def collectoryAuthService def grailsApplication def messageSource + def siteLocale = new Locale.Builder().setLanguageTag(Holders.config.siteDefaultLanguage as String).build() def serviceMethod() {} @@ -487,18 +490,10 @@ class ProviderGroupService { Map getSuitableFor() { // the settings in config is an array so that after JSON.parse the original order can be kept. def settings = grailsApplication.config.getProperty('suitableFor', String, '[]') - def suitableForMap = [:] - - try { - suitableForMap = JSON.parse(settings).collectEntries { - def key = it.keySet().first() - def val = messageSource.getMessage("dataresource.suitablefor." + key, null, it.values().first(), LocaleContextHolder.getLocale()) - [key, val] - } - } catch (e) { - log.error('Failed to parse suitableFor settings in config, config value is = ' + settings + ', exception is ' + e) + return JSON.parse(settings).collectEntries{ + def key = it.keySet().first() + def val = messageSource.getMessage("dataresource.suitablefor." + key, null, it.values().first(), siteLocale) + [key, val] } - - return suitableForMap } } diff --git a/grails-app/taglib/au/org/ala/collectory/CollectoryTagLib.groovy b/grails-app/taglib/au/org/ala/collectory/CollectoryTagLib.groovy index c48572e6..726606d9 100644 --- a/grails-app/taglib/au/org/ala/collectory/CollectoryTagLib.groovy +++ b/grails-app/taglib/au/org/ala/collectory/CollectoryTagLib.groovy @@ -812,6 +812,18 @@ class CollectoryTagLib { } } + def formatAndTranslateJsonList = { attrs -> + if (attrs.value && attrs.map){ + try { + def js = new JsonSlurper() + def list = js.parseText(attrs.value?.toString()) + list = list.collect { attrs.map.getOrDefault(it, it) } + out << list.join(", ") + } catch (Exception e){ + out << attrs.value + } + } + } /** * Formats free text so: * line feeds are honoured @@ -2034,6 +2046,19 @@ class CollectoryTagLib { } } + /** + * List of suitable for. + * + * @attr types json list of string + * @attr map translations of strings in types + */ + def suitableFor = { attrs -> + if (attrs.types && attrs.map) { + def list = JSON.parse(attrs.types as String).collect {attrs.map.getOrDefault(it.toString(), it.toString())} + out << '

Includes: ' + list.join(', ') + '.

' + } + } + def taxonomicRangeDescription = { attrs -> if (attrs.obj) { def obj = JSON.parse(attrs.obj) diff --git a/grails-app/views/dataResource/list.gsp b/grails-app/views/dataResource/list.gsp index 225ff100..36473329 100644 --- a/grails-app/views/dataResource/list.gsp +++ b/grails-app/views/dataResource/list.gsp @@ -24,7 +24,7 @@

-
${flash.message}
+
${flash.message}
diff --git a/grails-app/views/dataResource/show.gsp b/grails-app/views/dataResource/show.gsp index 2f15d34f..5c942b66 100644 --- a/grails-app/views/dataResource/show.gsp +++ b/grails-app/views/dataResource/show.gsp @@ -172,7 +172,7 @@

: ${instance.dataCollectionProtocolName}

-

:

+

: ${instance.dataCollectionProtocolDoc}

From 164cea33e90c546336f3ca73d26223df9c9ce84f Mon Sep 17 00:00:00 2001 From: Dave Martin Date: Tue, 7 Dec 2021 13:01:42 +0000 Subject: [PATCH 2/7] 3.0.2 --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 6fe7bbed..47a4b75f 100644 --- a/build.gradle +++ b/build.gradle @@ -16,7 +16,7 @@ plugins { id "com.gorylenko.gradle-git-properties" version "2.4.0-rc2" } -version "3.0.2-SNAPSHOT" +version "3.0.2" group "au.org.ala" From 16bd417341cea22cee8a91d27825fa7568ec8236 Mon Sep 17 00:00:00 2001 From: Nick dos Remedios Date: Tue, 21 Dec 2021 14:16:56 +1100 Subject: [PATCH 3/7] Create README.md --- README.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 00000000..859ae751 --- /dev/null +++ b/README.md @@ -0,0 +1,5 @@ +# collectory +Metadata registry for the Atlas - 2021 version that consolidates [ala-collectory](https://github.com/AtlasOfLivingAustralia/ala-collectory) +and [collectory-plugin](https://github.com/AtlasOfLivingAustralia/collectory-plugin) togerther, using Grails 4. + +[![Build Status](https://travis-ci.org/AtlasOfLivingAustralia/collectory.svg?branch=develop)](https://travis-ci.org/AtlasOfLivingAustralia/collectory) From da8a3b92b91a25ae79d5a3492d7a0f45570f7840 Mon Sep 17 00:00:00 2001 From: Nick dos Remedios Date: Tue, 21 Dec 2021 14:17:47 +1100 Subject: [PATCH 4/7] Update README.md Fix travis URLs --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 859ae751..4bd4e771 100644 --- a/README.md +++ b/README.md @@ -2,4 +2,4 @@ Metadata registry for the Atlas - 2021 version that consolidates [ala-collectory](https://github.com/AtlasOfLivingAustralia/ala-collectory) and [collectory-plugin](https://github.com/AtlasOfLivingAustralia/collectory-plugin) togerther, using Grails 4. -[![Build Status](https://travis-ci.org/AtlasOfLivingAustralia/collectory.svg?branch=develop)](https://travis-ci.org/AtlasOfLivingAustralia/collectory) +[![Build Status](https://travis-ci.com/AtlasOfLivingAustralia/collectory.svg?branch=develop)](https://travis-ci.com/AtlasOfLivingAustralia/collectory) From 2ebc9dcc7c21a3ecd3528c8434cd685da958a215 Mon Sep 17 00:00:00 2001 From: Nick dos Remedios Date: Tue, 21 Dec 2021 14:22:55 +1100 Subject: [PATCH 5/7] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 4bd4e771..57b0e14f 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ # collectory -Metadata registry for the Atlas - 2021 version that consolidates [ala-collectory](https://github.com/AtlasOfLivingAustralia/ala-collectory) +Metadata registry for the ALA and LA - 2021 version that consolidates [ala-collectory](https://github.com/AtlasOfLivingAustralia/ala-collectory) and [collectory-plugin](https://github.com/AtlasOfLivingAustralia/collectory-plugin) togerther, using Grails 4. [![Build Status](https://travis-ci.com/AtlasOfLivingAustralia/collectory.svg?branch=develop)](https://travis-ci.com/AtlasOfLivingAustralia/collectory) From 7849fed5c436d3850dae4c12ea562fe04a1bc758 Mon Sep 17 00:00:00 2001 From: Nick dos Remedios Date: Tue, 21 Dec 2021 14:23:16 +1100 Subject: [PATCH 6/7] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 57b0e14f..f64a50a0 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ # collectory Metadata registry for the ALA and LA - 2021 version that consolidates [ala-collectory](https://github.com/AtlasOfLivingAustralia/ala-collectory) -and [collectory-plugin](https://github.com/AtlasOfLivingAustralia/collectory-plugin) togerther, using Grails 4. +and [collectory-plugin](https://github.com/AtlasOfLivingAustralia/collectory-plugin) together, using Grails 4. [![Build Status](https://travis-ci.com/AtlasOfLivingAustralia/collectory.svg?branch=develop)](https://travis-ci.com/AtlasOfLivingAustralia/collectory) From 5d74f819c4b7b6443cd411fc6530d3c67944a885 Mon Sep 17 00:00:00 2001 From: Nick dos Remedios Date: Tue, 21 Dec 2021 14:25:15 +1100 Subject: [PATCH 7/7] Create LICENSE.txt --- LICENSE.txt | 469 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 469 insertions(+) create mode 100644 LICENSE.txt diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 00000000..56690810 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,469 @@ + MOZILLA PUBLIC LICENSE + Version 1.1 + + --------------- + +1. Definitions. + + 1.0.1. "Commercial Use" means distribution or otherwise making the + Covered Code available to a third party. + + 1.1. "Contributor" means each entity that creates or contributes to + the creation of Modifications. + + 1.2. "Contributor Version" means the combination of the Original + Code, prior Modifications used by a Contributor, and the Modifications + made by that particular Contributor. + + 1.3. "Covered Code" means the Original Code or Modifications or the + combination of the Original Code and Modifications, in each case + including portions thereof. + + 1.4. "Electronic Distribution Mechanism" means a mechanism generally + accepted in the software development community for the electronic + transfer of data. + + 1.5. "Executable" means Covered Code in any form other than Source + Code. + + 1.6. "Initial Developer" means the individual or entity identified + as the Initial Developer in the Source Code notice required by Exhibit + A. + + 1.7. "Larger Work" means a work which combines Covered Code or + portions thereof with code not governed by the terms of this License. + + 1.8. "License" means this document. + + 1.8.1. "Licensable" means having the right to grant, to the maximum + extent possible, whether at the time of the initial grant or + subsequently acquired, any and all of the rights conveyed herein. + + 1.9. "Modifications" means any addition to or deletion from the + substance or structure of either the Original Code or any previous + Modifications. When Covered Code is released as a series of files, a + Modification is: + A. Any addition to or deletion from the contents of a file + containing Original Code or previous Modifications. + + B. Any new file that contains any part of the Original Code or + previous Modifications. + + 1.10. "Original Code" means Source Code of computer software code + which is described in the Source Code notice required by Exhibit A as + Original Code, and which, at the time of its release under this + License is not already Covered Code governed by this License. + + 1.10.1. "Patent Claims" means any patent claim(s), now owned or + hereafter acquired, including without limitation, method, process, + and apparatus claims, in any patent Licensable by grantor. + + 1.11. "Source Code" means the preferred form of the Covered Code for + making modifications to it, including all modules it contains, plus + any associated interface definition files, scripts used to control + compilation and installation of an Executable, or source code + differential comparisons against either the Original Code or another + well known, available Covered Code of the Contributor's choice. The + Source Code can be in a compressed or archival form, provided the + appropriate decompression or de-archiving software is widely available + for no charge. + + 1.12. "You" (or "Your") means an individual or a legal entity + exercising rights under, and complying with all of the terms of, this + License or a future version of this License issued under Section 6.1. + For legal entities, "You" includes any entity which controls, is + controlled by, or is under common control with You. For purposes of + this definition, "control" means (a) the power, direct or indirect, + to cause the direction or management of such entity, whether by + contract or otherwise, or (b) ownership of more than fifty percent + (50%) of the outstanding shares or beneficial ownership of such + entity. + +2. Source Code License. + + 2.1. The Initial Developer Grant. + The Initial Developer hereby grants You a world-wide, royalty-free, + non-exclusive license, subject to third party intellectual property + claims: + (a) under intellectual property rights (other than patent or + trademark) Licensable by Initial Developer to use, reproduce, + modify, display, perform, sublicense and distribute the Original + Code (or portions thereof) with or without Modifications, and/or + as part of a Larger Work; and + + (b) under Patents Claims infringed by the making, using or + selling of Original Code, to make, have made, use, practice, + sell, and offer for sale, and/or otherwise dispose of the + Original Code (or portions thereof). + + (c) the licenses granted in this Section 2.1(a) and (b) are + effective on the date Initial Developer first distributes + Original Code under the terms of this License. + + (d) Notwithstanding Section 2.1(b) above, no patent license is + granted: 1) for code that You delete from the Original Code; 2) + separate from the Original Code; or 3) for infringements caused + by: i) the modification of the Original Code or ii) the + combination of the Original Code with other software or devices. + + 2.2. Contributor Grant. + Subject to third party intellectual property claims, each Contributor + hereby grants You a world-wide, royalty-free, non-exclusive license + + (a) under intellectual property rights (other than patent or + trademark) Licensable by Contributor, to use, reproduce, modify, + display, perform, sublicense and distribute the Modifications + created by such Contributor (or portions thereof) either on an + unmodified basis, with other Modifications, as Covered Code + and/or as part of a Larger Work; and + + (b) under Patent Claims infringed by the making, using, or + selling of Modifications made by that Contributor either alone + and/or in combination with its Contributor Version (or portions + of such combination), to make, use, sell, offer for sale, have + made, and/or otherwise dispose of: 1) Modifications made by that + Contributor (or portions thereof); and 2) the combination of + Modifications made by that Contributor with its Contributor + Version (or portions of such combination). + + (c) the licenses granted in Sections 2.2(a) and 2.2(b) are + effective on the date Contributor first makes Commercial Use of + the Covered Code. + + (d) Notwithstanding Section 2.2(b) above, no patent license is + granted: 1) for any code that Contributor has deleted from the + Contributor Version; 2) separate from the Contributor Version; + 3) for infringements caused by: i) third party modifications of + Contributor Version or ii) the combination of Modifications made + by that Contributor with other software (except as part of the + Contributor Version) or other devices; or 4) under Patent Claims + infringed by Covered Code in the absence of Modifications made by + that Contributor. + +3. Distribution Obligations. + + 3.1. Application of License. + The Modifications which You create or to which You contribute are + governed by the terms of this License, including without limitation + Section 2.2. The Source Code version of Covered Code may be + distributed only under the terms of this License or a future version + of this License released under Section 6.1, and You must include a + copy of this License with every copy of the Source Code You + distribute. You may not offer or impose any terms on any Source Code + version that alters or restricts the applicable version of this + License or the recipients' rights hereunder. However, You may include + an additional document offering the additional rights described in + Section 3.5. + + 3.2. Availability of Source Code. + Any Modification which You create or to which You contribute must be + made available in Source Code form under the terms of this License + either on the same media as an Executable version or via an accepted + Electronic Distribution Mechanism to anyone to whom you made an + Executable version available; and if made available via Electronic + Distribution Mechanism, must remain available for at least twelve (12) + months after the date it initially became available, or at least six + (6) months after a subsequent version of that particular Modification + has been made available to such recipients. You are responsible for + ensuring that the Source Code version remains available even if the + Electronic Distribution Mechanism is maintained by a third party. + + 3.3. Description of Modifications. + You must cause all Covered Code to which You contribute to contain a + file documenting the changes You made to create that Covered Code and + the date of any change. You must include a prominent statement that + the Modification is derived, directly or indirectly, from Original + Code provided by the Initial Developer and including the name of the + Initial Developer in (a) the Source Code, and (b) in any notice in an + Executable version or related documentation in which You describe the + origin or ownership of the Covered Code. + + 3.4. Intellectual Property Matters + (a) Third Party Claims. + If Contributor has knowledge that a license under a third party's + intellectual property rights is required to exercise the rights + granted by such Contributor under Sections 2.1 or 2.2, + Contributor must include a text file with the Source Code + distribution titled "LEGAL" which describes the claim and the + party making the claim in sufficient detail that a recipient will + know whom to contact. If Contributor obtains such knowledge after + the Modification is made available as described in Section 3.2, + Contributor shall promptly modify the LEGAL file in all copies + Contributor makes available thereafter and shall take other steps + (such as notifying appropriate mailing lists or newsgroups) + reasonably calculated to inform those who received the Covered + Code that new knowledge has been obtained. + + (b) Contributor APIs. + If Contributor's Modifications include an application programming + interface and Contributor has knowledge of patent licenses which + are reasonably necessary to implement that API, Contributor must + also include this information in the LEGAL file. + + (c) Representations. + Contributor represents that, except as disclosed pursuant to + Section 3.4(a) above, Contributor believes that Contributor's + Modifications are Contributor's original creation(s) and/or + Contributor has sufficient rights to grant the rights conveyed by + this License. + + 3.5. Required Notices. + You must duplicate the notice in Exhibit A in each file of the Source + Code. If it is not possible to put such notice in a particular Source + Code file due to its structure, then You must include such notice in a + location (such as a relevant directory) where a user would be likely + to look for such a notice. If You created one or more Modification(s) + You may add your name as a Contributor to the notice described in + Exhibit A. You must also duplicate this License in any documentation + for the Source Code where You describe recipients' rights or ownership + rights relating to Covered Code. You may choose to offer, and to + charge a fee for, warranty, support, indemnity or liability + obligations to one or more recipients of Covered Code. However, You + may do so only on Your own behalf, and not on behalf of the Initial + Developer or any Contributor. You must make it absolutely clear than + any such warranty, support, indemnity or liability obligation is + offered by You alone, and You hereby agree to indemnify the Initial + Developer and every Contributor for any liability incurred by the + Initial Developer or such Contributor as a result of warranty, + support, indemnity or liability terms You offer. + + 3.6. Distribution of Executable Versions. + You may distribute Covered Code in Executable form only if the + requirements of Section 3.1-3.5 have been met for that Covered Code, + and if You include a notice stating that the Source Code version of + the Covered Code is available under the terms of this License, + including a description of how and where You have fulfilled the + obligations of Section 3.2. The notice must be conspicuously included + in any notice in an Executable version, related documentation or + collateral in which You describe recipients' rights relating to the + Covered Code. You may distribute the Executable version of Covered + Code or ownership rights under a license of Your choice, which may + contain terms different from this License, provided that You are in + compliance with the terms of this License and that the license for the + Executable version does not attempt to limit or alter the recipient's + rights in the Source Code version from the rights set forth in this + License. If You distribute the Executable version under a different + license You must make it absolutely clear that any terms which differ + from this License are offered by You alone, not by the Initial + Developer or any Contributor. You hereby agree to indemnify the + Initial Developer and every Contributor for any liability incurred by + the Initial Developer or such Contributor as a result of any such + terms You offer. + + 3.7. Larger Works. + You may create a Larger Work by combining Covered Code with other code + not governed by the terms of this License and distribute the Larger + Work as a single product. In such a case, You must make sure the + requirements of this License are fulfilled for the Covered Code. + +4. Inability to Comply Due to Statute or Regulation. + + If it is impossible for You to comply with any of the terms of this + License with respect to some or all of the Covered Code due to + statute, judicial order, or regulation then You must: (a) comply with + the terms of this License to the maximum extent possible; and (b) + describe the limitations and the code they affect. Such description + must be included in the LEGAL file described in Section 3.4 and must + be included with all distributions of the Source Code. Except to the + extent prohibited by statute or regulation, such description must be + sufficiently detailed for a recipient of ordinary skill to be able to + understand it. + +5. Application of this License. + + This License applies to code to which the Initial Developer has + attached the notice in Exhibit A and to related Covered Code. + +6. Versions of the License. + + 6.1. New Versions. + Netscape Communications Corporation ("Netscape") may publish revised + and/or new versions of the License from time to time. Each version + will be given a distinguishing version number. + + 6.2. Effect of New Versions. + Once Covered Code has been published under a particular version of the + License, You may always continue to use it under the terms of that + version. You may also choose to use such Covered Code under the terms + of any subsequent version of the License published by Netscape. No one + other than Netscape has the right to modify the terms applicable to + Covered Code created under this License. + + 6.3. Derivative Works. + If You create or use a modified version of this License (which you may + only do in order to apply it to code which is not already Covered Code + governed by this License), You must (a) rename Your license so that + the phrases "Mozilla", "MOZILLAPL", "MOZPL", "Netscape", + "MPL", "NPL" or any confusingly similar phrase do not appear in your + license (except to note that your license differs from this License) + and (b) otherwise make it clear that Your version of the license + contains terms which differ from the Mozilla Public License and + Netscape Public License. (Filling in the name of the Initial + Developer, Original Code or Contributor in the notice described in + Exhibit A shall not of themselves be deemed to be modifications of + this License.) + +7. DISCLAIMER OF WARRANTY. + + COVERED CODE IS PROVIDED UNDER THIS LICENSE ON AN "AS IS" BASIS, + WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, + WITHOUT LIMITATION, WARRANTIES THAT THE COVERED CODE IS FREE OF + DEFECTS, MERCHANTABLE, FIT FOR A PARTICULAR PURPOSE OR NON-INFRINGING. + THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE COVERED CODE + IS WITH YOU. SHOULD ANY COVERED CODE PROVE DEFECTIVE IN ANY RESPECT, + YOU (NOT THE INITIAL DEVELOPER OR ANY OTHER CONTRIBUTOR) ASSUME THE + COST OF ANY NECESSARY SERVICING, REPAIR OR CORRECTION. THIS DISCLAIMER + OF WARRANTY CONSTITUTES AN ESSENTIAL PART OF THIS LICENSE. NO USE OF + ANY COVERED CODE IS AUTHORIZED HEREUNDER EXCEPT UNDER THIS DISCLAIMER. + +8. TERMINATION. + + 8.1. This License and the rights granted hereunder will terminate + automatically if You fail to comply with terms herein and fail to cure + such breach within 30 days of becoming aware of the breach. All + sublicenses to the Covered Code which are properly granted shall + survive any termination of this License. Provisions which, by their + nature, must remain in effect beyond the termination of this License + shall survive. + + 8.2. If You initiate litigation by asserting a patent infringement + claim (excluding declatory judgment actions) against Initial Developer + or a Contributor (the Initial Developer or Contributor against whom + You file such action is referred to as "Participant") alleging that: + + (a) such Participant's Contributor Version directly or indirectly + infringes any patent, then any and all rights granted by such + Participant to You under Sections 2.1 and/or 2.2 of this License + shall, upon 60 days notice from Participant terminate prospectively, + unless if within 60 days after receipt of notice You either: (i) + agree in writing to pay Participant a mutually agreeable reasonable + royalty for Your past and future use of Modifications made by such + Participant, or (ii) withdraw Your litigation claim with respect to + the Contributor Version against such Participant. If within 60 days + of notice, a reasonable royalty and payment arrangement are not + mutually agreed upon in writing by the parties or the litigation claim + is not withdrawn, the rights granted by Participant to You under + Sections 2.1 and/or 2.2 automatically terminate at the expiration of + the 60 day notice period specified above. + + (b) any software, hardware, or device, other than such Participant's + Contributor Version, directly or indirectly infringes any patent, then + any rights granted to You by such Participant under Sections 2.1(b) + and 2.2(b) are revoked effective as of the date You first made, used, + sold, distributed, or had made, Modifications made by that + Participant. + + 8.3. If You assert a patent infringement claim against Participant + alleging that such Participant's Contributor Version directly or + indirectly infringes any patent where such claim is resolved (such as + by license or settlement) prior to the initiation of patent + infringement litigation, then the reasonable value of the licenses + granted by such Participant under Sections 2.1 or 2.2 shall be taken + into account in determining the amount or value of any payment or + license. + + 8.4. In the event of termination under Sections 8.1 or 8.2 above, + all end user license agreements (excluding distributors and resellers) + which have been validly granted by You or any distributor hereunder + prior to termination shall survive termination. + +9. LIMITATION OF LIABILITY. + + UNDER NO CIRCUMSTANCES AND UNDER NO LEGAL THEORY, WHETHER TORT + (INCLUDING NEGLIGENCE), CONTRACT, OR OTHERWISE, SHALL YOU, THE INITIAL + DEVELOPER, ANY OTHER CONTRIBUTOR, OR ANY DISTRIBUTOR OF COVERED CODE, + OR ANY SUPPLIER OF ANY OF SUCH PARTIES, BE LIABLE TO ANY PERSON FOR + ANY INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES OF ANY + CHARACTER INCLUDING, WITHOUT LIMITATION, DAMAGES FOR LOSS OF GOODWILL, + WORK STOPPAGE, COMPUTER FAILURE OR MALFUNCTION, OR ANY AND ALL OTHER + COMMERCIAL DAMAGES OR LOSSES, EVEN IF SUCH PARTY SHALL HAVE BEEN + INFORMED OF THE POSSIBILITY OF SUCH DAMAGES. THIS LIMITATION OF + LIABILITY SHALL NOT APPLY TO LIABILITY FOR DEATH OR PERSONAL INJURY + RESULTING FROM SUCH PARTY'S NEGLIGENCE TO THE EXTENT APPLICABLE LAW + PROHIBITS SUCH LIMITATION. SOME JURISDICTIONS DO NOT ALLOW THE + EXCLUSION OR LIMITATION OF INCIDENTAL OR CONSEQUENTIAL DAMAGES, SO + THIS EXCLUSION AND LIMITATION MAY NOT APPLY TO YOU. + +10. U.S. GOVERNMENT END USERS. + + The Covered Code is a "commercial item," as that term is defined in + 48 C.F.R. 2.101 (Oct. 1995), consisting of "commercial computer + software" and "commercial computer software documentation," as such + terms are used in 48 C.F.R. 12.212 (Sept. 1995). Consistent with 48 + C.F.R. 12.212 and 48 C.F.R. 227.7202-1 through 227.7202-4 (June 1995), + all U.S. Government End Users acquire Covered Code with only those + rights set forth herein. + +11. MISCELLANEOUS. + + This License represents the complete agreement concerning subject + matter hereof. If any provision of this License is held to be + unenforceable, such provision shall be reformed only to the extent + necessary to make it enforceable. This License shall be governed by + California law provisions (except to the extent applicable law, if + any, provides otherwise), excluding its conflict-of-law provisions. + With respect to disputes in which at least one party is a citizen of, + or an entity chartered or registered to do business in the United + States of America, any litigation relating to this License shall be + subject to the jurisdiction of the Federal Courts of the Northern + District of California, with venue lying in Santa Clara County, + California, with the losing party responsible for costs, including + without limitation, court costs and reasonable attorneys' fees and + expenses. The application of the United Nations Convention on + Contracts for the International Sale of Goods is expressly excluded. + Any law or regulation which provides that the language of a contract + shall be construed against the drafter shall not apply to this + License. + +12. RESPONSIBILITY FOR CLAIMS. + + As between Initial Developer and the Contributors, each party is + responsible for claims and damages arising, directly or indirectly, + out of its utilization of rights under this License and You agree to + work with Initial Developer and Contributors to distribute such + responsibility on an equitable basis. Nothing herein is intended or + shall be deemed to constitute any admission of liability. + +13. MULTIPLE-LICENSED CODE. + + Initial Developer may designate portions of the Covered Code as + "Multiple-Licensed". "Multiple-Licensed" means that the Initial + Developer permits you to utilize portions of the Covered Code under + Your choice of the MPL or the alternative licenses, if any, specified + by the Initial Developer in the file described in Exhibit A. + +EXHIBIT A -Mozilla Public License. + + ``The contents of this file are subject to the Mozilla Public License + Version 1.1 (the "License"); you may not use this file except in + compliance with the License. You may obtain a copy of the License at + https://www.mozilla.org/MPL/ + + Software distributed under the License is distributed on an "AS IS" + basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the + License for the specific language governing rights and limitations + under the License. + + The Original Code is ______________________________________. + + The Initial Developer of the Original Code is ________________________. + Portions created by ______________________ are Copyright (C) ______ + _______________________. All Rights Reserved. + + Contributor(s): ______________________________________. + + Alternatively, the contents of this file may be used under the terms + of the _____ license (the "[___] License"), in which case the + provisions of [______] License are applicable instead of those + above. If you wish to allow use of your version of this file only + under the terms of the [____] License and not to allow others to use + your version of this file under the MPL, indicate your decision by + deleting the provisions above and replace them with the notice and + other provisions required by the [___] License. If you do not delete + the provisions above, a recipient may use your version of this file + under either the MPL or the [___] License." + + [NOTE: The text of this Exhibit A may differ slightly from the text of + the notices in the Source Code files of the Original Code. You should + use the text of this Exhibit A rather than the text found in the + Original Code Source Code for Your Modifications.]