Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

...

The JCR Property annotation is supported as well, but because it is not as powerful and merely exists for backwards compatibility purposes, we recommend you do not explicitly use it to index data.

In this section

Table of Contents
maxLevel2

 

...

Tutorial

The API is explained on the basis of a simple example. The following shows the custom media item definition:

...

Let's assume the implementation of this recipe interface annotates getName() with @Property but nothing else. If we were to index this content item now, the result of getName() will be added to the content item's body, but nothing else will happen. This is the same behavior as in XperienCentral versions 10.0.0 through 10.11.1. Because this is very limited, we are going to go further. We start by indexing the simple properties and then index the more complex properties, and finally we'll turn some of the properties into facets.

@Indexable

This is a type of annotation which can be used on interfaces, classes, and so forth. It indicates that the annotated type should be indexed in a Solr document. This means that if, for example, RecipeVersion has this annotation, a document will be created for each recipe version and if a property of this document matches a user search, then this document will be returned. Suppose we add the @Indexable annotation to our recipe. With this annotation only, nothing at all will happen and only the default properties for an article version will be indexed, therefore merely adding @Indexable to your class has little effect on our interface. To be able to do more with it, we need to use at least one of the other annotations.

@Field

This annotation is used only if @Indexable is present. It indicates that the result of the annotated method should be indexed. The result may be a single value (for a single-valued field), a collection or an array of values (for a multi-valued field), a map of key/values (for multiple unique fields), or null (ignore the value). Primitive types are supported as well. In the map case, each value is indexed with their unique key, but the parameters of the method's annotation are used. This annotation is inherited, so if it is present on an overridden method for example, the overriding method does not need to specify the annotation as well - it can override it, but it cannot remove an annotation.

...

  • name: "French Fries" (indexed, stored, boost = 1)

  • heading: ["French Fries"]

  • body: ["Long and yellow deliciousness."]

  • language: "en" (indexed, stored)

  • language (NL): "Engels" (indexed, stored)

  • language (EN): "English" (indexed, stored)

@Document

Now that we have indexed a few trivial properties, we want to index some more complex properties: Country and List<Ingredient>. We could index this using an adapter, just like we did for Locale, but since we have access to the Country and Ingredient classes, it is much nicer to make use of @Document and annotate the referred classes. This annotation tells the indexer to not index the value, but instead to treat the value as an object that we should parse and scan for annotations as well. This looks like this.

...

  • inheritLanguage=false - If this is set to true, the language of the target document will be set/overridden to the language of the current document. This may either be the language explicitly defined for the object declaring the annotation or the language inherited from a parent document. This setting is useful if referred documents should be indexed with the same language as the root document, for example, elements on pages.

Facets

You can turn any field into a facet. All you have to do for this is use the following parameter.

...

Now we're finished. This is what we see after opening the Advanced Search after our recipe is indexed (we titled it "My recipe"). You will find that if you open the search dialog in Dutch, the facet titles will be the ones we added via the facet definition. Furthermore, "Recepttaal" will have the option "Engels" instead of "English".

 

Image Modified

 

The complete source and deployable jar can be found in the attachments.

Notes

  • One parameter not described is the extension parameter:
    • extension=null - By default, an annotated class is, except for the referred classes, the only class that is scanned for annotations. In most cases this is fine, but in some cases it is not sufficient. For example, if one needs to invoke an external service that is not accessible from within the object or should not be a public/API method. To address, it is possible to define an extension of a class; a class with the annotated class as constructor parameter. This extension is scanned in addition to the class and comes therefore with more flexibility. It is possible to define an extension per type in the hierarchy level.
      • Extensions are treated like any other class, so if an extension is not annotated with @Indexable, it is not scanned.
      • The owner of a field originating from an extension is not the extension class, but rather the class requiring the extension.
      • An extension must be a concrete class with a single-argument constructor of the type the extension is for. For example: public RecipeVersionExtension(RecipeVersion recipe).
  • It is possible to index result sets from a query as well. To do so, you should create a custom media item that invokes the SQL command that returns the ResultSet you want to index. Then, you should create a Map based on this result set and return this - the returned values will then be indexed with their key as part of the field name. You can use an extension for this. For example:

    Code Block
    themeEclipse
    @Field(body = true)
    public Map<String, String> getFields() {
        Map<String, String> fields = new HashMap<>();
    
        DataSource dataSource = ...;
        String selectQuery = ...;
    
        try(Connection conn = dataSource.getConnection();
            PreparedStatement stmt = conn.prepareStatement(selectQuery)) {
            ResultSet rs = stmt.executeQuery();
                
            for (int i = 0; rs.next(); ++i) {
                fields.put(rs.getMetaData().getColumnName(i), rs.getString(i));
            }
        } catch (SQLException e) {
            LOG.log(Level.WARNING, "Could not (fully) index resultset", e);
        }
    
        return fields;
    }



  • Both interfaces and classes (including implementations) can be indexed, so implementation-specific data is allowed as well.
  • P(ackage-p)rivate/protected methods can be indexed but will be made accessible.
  • Annotated methods may not have parameters and may not return void.
  • If a class that is not exported through the Export-Package directive in the pom.xml adds a facet, this facet's SearchFacetDescriptor will have owner void.class.

 

Back to top

 

...

Overview

Packages

  • nl.gx.webmanager.services.contentindex.annotation
  • nl.gx.webmanager.services.contentindex.adapter

@Indexable

Use @Indexable on classes and interfaces.

  • This annotation is not inherited.
  • There are two parameters.
    • languageGetter=null - By default, a document is language agnostic. It is possible to make document indexing website language specific by specifying a LanguageGetter here.
    • extension=null - By default, an annotated class is, except for the referred classes, the only class that is scanned for annotations. In most cases this is fine, but in some cases it is not sufficient. For example, if one needs to invoke an external service that is not accessible from within the object, or should not be a public/API method. To fix this, it is possible to define an extension of a class; a class with the annotated class as constructor parameter. This extension is scanned in addition to the class, and comes therefore with more flexibility. It is possible to define an extension per type in the hierarchy level.
      • Extensions are treated like any other class, so if an extension is not annotated with @Indexable, it is not scanned.
      • The owner of a field originating from an extension is not the extension class, but the class requiring the extension.
      • An extension must be a concrete class with a single-argument constructor of the type the extension is for. For example: public PageVersionExtension(PageVersion page).

@Field and @ReferField

Use @Field and @ReferField on methods to index the return value(s).

  • This annotation is inherited and may be overridden.
  • Use @Field to annotate methods that should be taken into account of the owner class itself is indexed, and @ReferField for when the declaring class will be part of the indexed document.
  • They have the following parameters:
    • stored=true  - Specifies whether this field should be stored. A field that is not stored is not retrievable and usable on the client, but it can be used when searching.
    • indexed=false - Specifies whether this field should be indexed. A field is indexed by default if it is either boosted or it is a facet. There is typically no reason to explicitly set this parameter to true.
    • body=false - By default, a value is not indexed in an aggregate field. Setting this property causes the values to be indexed to body, which makes it so that they can cause a document to be found when searching on the value.
    • heading=false - This is the same as body, except that setting this property to true promotes body to heading, giving the value a slightly bigger boost.
    • boost=0.0  - Setting this property allows this field to influence the containing document's score, based on the relevance of the field compared to other fields in the document. A positive boost makes the document score higher, while a negative boost lowers the score. Note that a boost between 0 and 1 sounds like a negative boost, but it is still a boost. Reasonable values for boost lie between -1 and 1. A boost of 0 means no explicit boost is used.
    • facet=false - Specifies whether the field should be a facet.
    • adapter=null - A FieldAdapter. Adapters allow developers to change the value returned by the annotated method before it is indexed/stored. Adapters should also be used if a value should be indexed in a language-specific way.
      • An adapter may be
        • a concrete class, in which case it should have a zero-arguments constructor;
        • an interface of which an implementation is exposed via OSGi (whiteboard pattern).
      • An adapter may decide to adapt a value to nothing (that is, return null), causing no value to be indexed/stored. This is also what happens if an adapt method terminates exceptionally, although in this case a warning is logged as well.
      • If an annotated method returns null, this value is given to the adapter anyway in order to give it the chance to make something out of it.

@Document and @ReferDocument

Use @Document and @ReferDocument on methods to tell the indexer to scan the class of the return value(s).

  • This annotation is inherited and may be overridden.
  • Use @Document to annotate methods that should be taken into account of the owner class itself is indexed, and @ReferDocument for when the declaring class will be part of the indexed document.
  • They have the following parameters:
    • inheritLanguage=false - If this is set to true, the language of the target document will be set/overridden to the language of the current document. This may either be the language explicitly defined for the object declaring the annotation or the language inherited from a parent document. This setting is useful if referred documents should be indexed with the same language as the root document. For example, elements on pages.
    • adapter=null - A DocumentAdapter that changes the object whose class will be scanned for indexable properties.
      • An adapter may be
        • a concrete class, in which case it should have a zero-arguments constructor;
        • an interface of which an implementation is exposed via OSGi (whiteboard pattern).
      • An adapter may decide to adapt a value to nothing (that is, return null), causing no value to be scanned. This is also what happens if an adapt method terminates exceptionally, although in this case a warning is logged as well.
      • If an annotated method returns null, this value is given to the adapter anyway in order to give it the chance to make something out of it.

A Few More Details

  • Multiple (unique) annotations may be used on a single method.
  • Annotated methods may not have parameters, and may not return void.
  • Both interfaces and classes (including implementations) can be indexed, so implementation-specific data is allowed as well.
  • P(ackage-p)rivate/protected methods can be indexed, but will be made accessible.
  • The new method annotations are ignored if their class does not have the @Indexable annotation.
    • If it does, only methods annotated with @Property will be indexed. Properties are not stored or indexed, but their value is added to the body.
      • @Property annotated method is only used for indexing if the return value's class is String.class.
  • Methods/adapters may return any value. Primitives are always boxed, and arrays, collections, and maps are interpreted as multiple values. Each value is treated separately and handed to an adapter.
  • Adapter instances and language getters are cached in an OSGi-aware cache, therefore reloading your bundle will invalidate the old entries but not the extensions.
  • The default XperienCentral widgets have a position of 0, 100, 200, and so forth.
  • Supported Solr types are:
    • int, float, long, double, boolean (note that shorts are not supported)
    • String
    • Date
  • Do not use the @Property annotation.

    • It exists only for the sake of backwards compatibility
    • It is ignored if the @Indexable annotation is present on the declaring class.
  • Page metadata will be indexed as well - it will be included in the page version's Solr document providing it is annotated.

Debugging

  • If your facet does not appear in the Advanced Search, ensure that:
    • you have created one of your annotated content items;
    • the value you expect to see is actually used and does not return null;
    • the content item is indexed;
    • your path is fully lowercased.
  • If a class that is not exported through the Export-Package directive in the pom.xml adds a facet, this facet's SearchFacetDescriptor will have owner void.class.
  • Many things are logged at log level FINE. This should give you detailed information about what values are indexed for a specific method, and why.

 

Back to top