Taxonomy Plugin Store Ancestor Keys Automatically

Default Taxonomy Field Stores Ancestor Information

As of version 15.3, a Taxonomy field type is available in the Document Type Editor. When used, taxonomy keys are stored in a JCR property that is defined there, typically by a developer. 

When the Taxonomy Picker is used in a document, the picked categories will be stored in that multiple JCR property.

Additionally, the taxonomy plugin will store all ancestor information in a similar multiple property, with the same base name, postfixed by "__with_ancestors"

This feature can be very convenient when searching for documents based on a high level taxonomy category.

For example a document instance with a geography-based taxonomy field and two selected items would get:

/content/documents/myproject/content/example-doc/example-doc:
  myproject:mytaxonomy: [san-francisco, london]
  myproject:mytaxonomy__with_ancestors: [northamerica, usa, california, san-francisco, europe, uk, london]

Configure Taxonomy Mixin Field to Store Ancestor Information

Legacy Documentation (Taxonomy Mixin Field)

Below documentation applies to the legacy Taxonomy Mixin Field, from version 15.3.0.

The legacy, mixin-based, taxonomy field can be configured to store ancestor information besides the default hippotaxonomy:keys property (or the path defined by 'fieldPath', see "Extra Taxonomy Field". 

To enable, set flag storeKeysWithAncestors to true in the DAO service.
The ancestor information is stored in multiple property hippotaxonomy:keyswithancestors, unless specified in fieldWithAncestorsPath.

/hippo:configuration/hippo:frontend/cms/cms-services/classificationDaoService:
  storeKeysWithAncestors: true
  // optional, defaults to hippotaxonomy:keyswithancestors
  fieldWithAncestorsPath: myproject:mycategories_withancestors

Groovy Script to Populate Ancestor Information on Existing Documents

Since taxonomy keys and ancestor keys are stored on save, it may be wanted to run an updater  script to update existing documents accordingly without the need to open and save every document. 

See below for an example script that may serve as base to do that.

package org.hippoecm.frontend.plugins.cms.admin.updater

import org.hippoecm.repository.util.JcrUtils
import org.onehippo.repository.update.BaseNodeUpdateVisitor
import javax.jcr.Node
import javax.jcr.NodeIterator
import javax.jcr.RepositoryException
import javax.jcr.Session
import javax.jcr.query.Query

import org.onehippo.taxonomy.api.TaxonomyNodeTypes

/**
 * Groovy script to populate a taxonomy property for storing all ancestor keys, based on an existing taxonomy property.
 *
 * XPath query: //element(*, myproject:contentdocument)
 * Parameters: { "keysProperty" : "hippotaxonomy:keys",
 *               "keysWithAncestorsProperty" : "hippotaxonomy:keyswithancestors" }
 */

class PopulateTaxonomyKeysWithAncestors extends BaseNodeUpdateVisitor {

    boolean logSkippedNodePaths() {
        return false
    }

    boolean skipCheckoutNodes() {
        return false
    }

    Node firstNode(final Session session) throws RepositoryException {
        return null
    }

    Node nextNode() throws RepositoryException {
        return null
    }

    boolean doUpdate(Node node) {

        def keysProperty = parametersMap["keysProperty"]
        def keysWithAncestorsProperty = parametersMap["keysWithAncestorsProperty"]

        String[] keys = JcrUtils.getMultipleStringProperty(node, keysProperty, null)
        if (keys == null) {
            log.debug "Not recreating ${keysWithAncestorsProperty}: no ${keysProperty} on node ${node.path}"
            return false
        }
        log.debug "Recreating ${keysWithAncestorsProperty} based on ${keysProperty}=${keys} on node ${node.path}"

        final def keysWithAncestors = new LinkedHashSet<>();
        def queryMgr = node.getSession().getWorkspace().getQueryManager()
        for (def key : keys) {

            def ancestors = getKeyWithAncestors(key, queryMgr)
            log.debug "   adding values ${ancestors}"
            keysWithAncestors.addAll(ancestors)
        }
        String[] values = keysWithAncestors.toArray(new String[0])
        node.setProperty(keysWithAncestorsProperty, values)

        return true
    }

    boolean undoUpdate(Node node) {
        throw new UnsupportedOperationException('Updater does not implement undoUpdate method')
    }

    def getKeyWithAncestors(def key, def queryMgr) {

        def keyWithAncestors = new LinkedList()

        def statement = "content/taxonomies//element(*, hippotaxonomy:category)[hippotaxonomy:key = '" +
                key + "']"
        def query = queryMgr.createQuery(statement, Query.XPATH)
        final NodeIterator nodes = query.execute().nodes
        // there should be only one (unique key)
        if (nodes.hasNext()) {
            def cat = nodes.nextNode()
            // only category nodes up the tree have the key property
            while (cat.hasProperty(TaxonomyNodeTypes.HIPPOTAXONOMY_KEY)) {
                keyWithAncestors.add(cat.getProperty(TaxonomyNodeTypes.HIPPOTAXONOMY_KEY).string)
                cat = cat.parent
            }
        }
        else {
            log.debug("No category node found by key ${key}")
        }

        // same order as MixinClassificationDaoPlugin
        Collections.reverse(keyWithAncestors)

        return keyWithAncestors;
    }
}

 

Did you find this page helpful?
How could this documentation serve you better?
On this page
    Did you find this page helpful?
    How could this documentation serve you better?