Tuesday, October 23, 2012

How to work with Configurations in CQ

Use Case: You want to create configuration which can be edited at run time using OSGI console.

Background:

http://www.wemblog.com/2012/01/how-to-set-up-run-mode-in-cq-wem.html

CQ already comes with predefined configuration that you can find using following Xpath queries
//element(*,sling:OsgiConfig)

Or using query builder predicate (http://HOST:PORT/libs/cq/search/content/querydebug.html)

type=nt:file
nodename=*.config
orderby=@jcr:content/jcr:lastModified
orderby.sort=desc

From front end you can find all configurations under config tab in felix console



You can also download all configurations using felix console



You can also go to configuration through Component tab



On file systems you can find configuration under /crx-quickstart/launchpad/config folder.

Configuration can be at any of above location but order of precedence of selecting a configuration is,

1) /apps
2) /libs
3) File system

For more information please see http://dev.day.com/content/kb/home/cq5/CQ5SystemAdministration/LoadingOrderFelixConfig.html

How to Create a felix Configuration:

1) By creating a sling:OsgiConfig node using CRXDE

Step 1:

Step 2:



2) Using xml



3) By Code

How to Read Property:

1) Using generic class (You can create interface)

2) Within bundle




Cool part about configuration that I like most is, You can create environment specific configurations and based on your environment particular configuration will get selected. You can set configurations as given in http://www.wemblog.com/2012/01/how-to-set-up-run-mode-in-cq-wem.html


























Some Questions:

Q: Where does my configuration get stored when I change it through felix console directly

Ans: In CQ5.5 it get stored in repository under /apps/system/config thus get replicated across all cluster. But in CQ5.4 and before it get stored in file system under /crx-quickstart/launchpad/config/<Name of config>.config thus requires changes in all cluster instance.





















Q: What if I just want configuration for particular environment

Ans: In that case you have to set up your config for that environment and then create configuration under that node. See example above.

Q: What is recommended way to create configuration

Ans: It is recommended to change configuration through repository and not through felix console if possible. That way you can track configuration in SVN as well.

Q: I see that there is already a configuration under felix console, How can I create one under repository to override that.
Ans: To do that first go to felix console -> configuration -> open configuration and look for pid

Then create exactly same node under /apps/config<Any enviornment> of type osgiconfig


Q. How to create configuration that has drop down of selected value
A. You can do something like,

How to create Configuration factory to locate services

Some time we want to create configuration factories to create factory of configuration (For example Logger configuration)



In order to create such configuration you could use annotation

 
To read such configuration though supporting class you can use,

@References({
  @Reference (cardinality=ReferenceCardinality.OPTIONAL_MULTIPLE,        policy=ReferencePolicy.DYNAMIC, referenceInterface = MyCustomService.class, name="MyCustomService")
})


How to locate configurations in configuration factory (Note that you might not need to do this if you are using independent configuration factories, for example configuration factories of scheduler) 




Please ask any other question that you want me to add here.

29 comments:

  1. Can you configure replication agents the same way? Would be cool to have a single package with all agents for envs.

    Thanks

    ReplyDelete
    Replies
    1. Unfortunately you can not but it will be a good enhancement request. Please create enhancement request in daycare.

      Yogesh

      Delete
  2. So all the documentation is around OSGiConfig, but if you edit the configurations in Felix, it generates a new nt:file, leaving the old OSGiConfig the way it was, so wouldn't the proper formate be a nt:file?

    ReplyDelete
    Replies
    1. nt:file is not recommended formate although CQ uses this file formate to store custom configuration :). If you are creating your own then I would suggest to use above formate. Also above code should work for both nt:file and sling:OsgiConfig

      Delete
  3. Hi Yogesh,Can u pls share code base for this?

    ReplyDelete
    Replies
    1. Hello example code if already there in post, Let me know if you want any specific code.

      Yogesh

      Delete
    2. As package can u pls upload it.

      Delete
    3. Hello,

      I am very sorry but do not have any specific package for this. But it should be very easy to implement this. Please let me know if you are getting any error while implementing this ?

      Yogesh

      Delete
  4. Hi,

    I created config factory as below

    import java.util.Dictionary;
    import org.apache.felix.scr.annotations.Component;
    import org.apache.felix.scr.annotations.Properties;
    import org.apache.felix.scr.annotations.Property;
    import org.apache.felix.scr.annotations.Service;
    import org.osgi.framework.Constants;
    import org.osgi.service.component.ComponentContext;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    @Component( immediate=true, label="Cache Clear Configuration Service", description = " Cache Clear Configuration Service", metatype=true )
    @Service
    @Properties({
    @Property(name = Constants.SERVICE_DESCRIPTION, value = "Cache Clear Configuration Service"),
    @Property(name = Constants.SERVICE_VENDOR, value = "XYZ")})
    public class ConfigurationServiceImpl implements ConfigurationService{
    private static final Logger log = LoggerFactory.getLogger(ConfigurationServiceImpl.class);
    @Property(label = "REST Service Url By ARL", value = "http://localhost:8080/abbc")
    public static final String REST_SERVIVE_BY_ARL= "RESTEndPointUrl";
    private String RESTEndPointUrl;
    protected void activate(ComponentContext context){
    try {
    log.info("activate method called");
    Dictionary props = context.getProperties();
    RESTEndPointUrl = (String) props.get(REST_SERVIVE_BY_ARL);
    } catch (Exception e){
    log.error(e.getMessage(), e);
    } }
    public String getRESTSericeUrlByARL() {
    return rESTEndPointUrl;
    }
    public void setRESTSericeUrlByARL(String rESTEndPointUrl) {
    this.akamaiRESTEndPointUrl = rESTEndPointUrl;
    }}

    I could see values stored under /apps/system/config/com......How I can access those array of values.Currently it is stored as binary data.

    Thanks
    LM

    ReplyDelete
    Replies
    1. LM,

      You should be able to access value using something like
      this. RESTEndPointUrl =OsgiUtil.toStringArray(properties.get(REST_SERVIVE_BY_ARL)); where this. RESTEndPointUrl is string array and REST_SERVIVE_BY_ARL is multivalue property. You can manually create configuration as well, for example in your case file name would be
      /apps//config/.ConfigurationServiceImpl.xml and content would be


      <jcr:root xmlns:sling="http://sling.apache.org/jcr/sling/1.0" xmlns:jcr="http://www.jcp.org/jcr/1.0"
      jcr:primaryType="sling:OsgiConfig"
      RESTEndPointUrl="[SOMETHING]"/

      Yogesh

      Delete
  5. If I have two sites in one CQ5 instance, for e.g en, en_US. How will I be able to configure different values for same property for both sites ?

    Thanks in advance,
    Vipul

    ReplyDelete
    Replies
    1. Vipul,

      If it is static content then you can can use i18n feature http://www.wemblog.com/2011/12/how-to-use-multi-language-translation.html one with jcr:language=en and other with en_US. If it is read from dialog then in that case you have to implement some thing at code level.

      Yogesh

      Delete
  6. Hi Yogesh,

    CQ ConfigAdmin picks a folder based on the runmode like your example config.author.dev. I have a usecase where-in my custom service is supposed to use two different set of properties based on whether its day or night. I know we could have an if else in service itslef and taking a superset of all the parameters, so wondering if runmode can help me here. Like if I create two different folders say config.author.dev.day & config.author.dev.night and then creating under each of it osgi:config node same as PID of my service. This will make it more flexible but have not found a way to do the same so far. Can you help?

    ReplyDelete
    Replies
    1. Run mode can not help in this case. As Run mode are set during startup. You need to use if then condition for this to work and then use ConfigUtil service to read from different configurations.

      Delete
  7. Hi Yogesh,

    Is it possible to display a more user-friendly name in Felix console for the config factory instance instead of the PID? Our customer would like to create multiple factory instances of the same service and be able to easily distinguish between all of the instances with a more user-friendly name.

    Thank you in advance.
    GS

    ReplyDelete
    Replies
    1. for config factory you can use friendly name after pid .. some thing like com.mycompany.something-myfriendlyname1 com.mycompany.something-myfriendlyname2 so on.

      Delete
  8. Hi, for the Configuration of Factory items, how do those get registered with the Facade class?
    I see the bind and unbind, are those functions named by convention? Is there some piece missing? I have everything working except this last piece (Felix is registering the individual services but I can't seem to access them from the facade class functions)

    ReplyDelete
    Replies
    1. facade class is just a helper in case you want to get particular service within factory service based on key.

      Delete
  9. Hey Yogesh,

    I was trying to create a property on a Service using the option#2, code way. I am seeing a strange behavior. The service will be "unbound" until it is SAVED at least once. When I tried to find on the net, I found a similar thread but can't make out what was the solution. (https://forums.adobe.com/thread/1088425).

    My code...
    package com.cq.querybuilder;

    import org.apache.felix.scr.annotations.Component;
    import org.apache.felix.scr.annotations.Properties;
    import org.apache.felix.scr.annotations.Property;
    import org.apache.felix.scr.annotations.PropertyUnbounded;
    import org.apache.felix.scr.annotations.Service;


    @Service(value=BaseConfig.class)
    @Component(label = "com.cq.querybuilder.BaseConfig", description = "Place Holder for configurations", metatype = true, immediate=true)
    @Properties({ @Property(name="service.description", value="My trial 001"),
    @Property(name="service.vendor", value="YEs !!!")})
    public class BaseConfig {

    /** The Constant PROPERTY_EMAIL_TITLE */
    @Property(
    label = "Property Title",
    description = "Title of the Property.",
    value = "Customer Input")
    private static final String PROPERTY_XYZ_TITLE = "propertyTilte";

    }

    ReplyDelete
    Replies
    1. Can you create a corresponding osgi config in repository as well to test ? for example in your case you have to create com.cq.querybuilder.BaseConfig.xml under some /apps/config node and then set up property there.

      Delete
  10. Bingo -:) It worked.
    Thanks Yogesh....

    ReplyDelete
  11. Hi thanks for sharing!
    I'm trying to create a Configuration factory. Anyway I can't understand how you can make it works.
    In particular I get stuck with the bind method.
    protected void bindMyCustomService(ServiceReference ref) {
    synchronized (this. myCustomServices) {
    String customKey = ref.getProperty(KEY);
    MyCustomServices operation = this.componentContext.locateService("MyCustomService",ref);
    //Or you can use
    //MyCustomServices operation = ref.getProperty("service.pid");
    if (operation != null) {
    myCustomServices.put(customKey,ref);
    }
    }
    }
    I get a compile-time error with your code due to the incompability of the ServiceReference object with the MyCustomService class of the map. If I try to do a cast before to put the object into the Map I get a CastException.

    Any advice about that? Thanks again

    ReplyDelete
  12. Hi Yogesh,

    Thanks for sharing.

    I created configurations by creating node sling:OsgiConfig. I have a property which is likely to change often, which I need to configure through felix console. How do I do that? Or is it not a good idea to keep these kind of properties in OsgiConfig node?
    Thanks

    ReplyDelete
    Replies
    1. If these property can be changed by author or admin on regular basis and not during code deployment then I would suggest not to use osgi:config approach at all. Use some kind custom property that user can change through UI and then read it through code.

      Delete
    2. Thanks for the response. Is it a best way to provide this as property that can be configured through Felix console? So, I have osgi:config node for the class for those parameters that will not change and as well as have the @Property configuration parameters in the class which can be managed through Felix console.
      Thanks

      Delete
    3. @ashtrick,

      I think your approach of defining changeable property through @Property should work.

      Yogesh

      Delete
  13. Hi Yogesh,

    Is it possible to override the factory configuration PID from crxde using sling:osgiconfig type.

    Scenario is :-

    There is one manually authored configuration inside my factory configuration and its PID value is com.xperian.cms.base.swse.services.xperianswseconfiguration.6ad86ce9-752b-410d-8ada-23a06111e369

    6ad86ce9-752b-410d-8ada-23a06111e369 is appended bt cq. But when i want to made its osgi.config from crxde it is giving me an error.
    "Could not save changes. Received 403 (Forbidden) for saving changes in workspace crx.default. Failed to resolve path com.xperian.cms.base.swse.services.xperianswseconfiguration.6ad86ce9-752b-410d-8ada-23a06111e369 relative to node /apps/xperian/configuration/config.author"

    Is there any way to override this values ?

    or we have to create the new one ?

    ReplyDelete
    Replies
    1. Hello Mohit,

      Note that random PID value is something AEM generated. If you have custom factory config. I would suggest to give some meaningful name to it, For example config.author/com.day.commons.datasource.jdbcpool.JdbcPoolService-MySQL.xml or config.author/com.day.commons.datasource.jdbcpool.JdbcPoolService-teradata.xml or config.author/com.day.commons.datasource.jdbcpool.JdbcPoolService-Oracle.xml

      They all should work. I would suggest to use felix console for randomly generated PID.

      Yogesh

      Delete
  14. ###### Helper Service Interface #####
    public interface QueryFacetPredicateProvider {

    List getFacetPredicates(String providerClassName);
    }

    ##### Helper Service Impl ######
    @Component(label = "Query Facets Provider", description = "Provides query facets from the QueryFacetConfiguration", immediate = true, metatype = false, enabled = true)
    @Service
    @References({@Reference(referenceInterface = QueryFacetConfigurationFactory.class,
    cardinality = ReferenceCardinality.OPTIONAL_MULTIPLE,
    policy = ReferencePolicy.DYNAMIC, name = "FacetConfigurationFactory")})
    public class QueryFacetPredicateProviderService implements QueryFacetPredicateProvider {

    private static final Logger LOGGER = LoggerFactory.getLogger(QueryFacetPredicateProviderService.class);

    private Map factoryConfigs;

    protected synchronized void bindFacetConfigurationFactory(final QueryFacetConfigurationFactory config) {
    if (factoryConfigs == null) {
    factoryConfigs = new HashMap<>();
    }

    LOGGER.debug("Adding facets configuration for provider " + config.getQueryClass());

    factoryConfigs.put(config.getQueryClass(), config);
    }

    protected synchronized void unbindFacetConfigurationFactory(final QueryFacetConfigurationFactory config) {
    LOGGER.debug("Removing facets configuration for provider " + config.getQueryClass());
    factoryConfigs.remove(config.getQueryClass());
    }

    /**
    *
    * @param providerClassName
    * @return
    */
    public List getFacetPredicates(String providerClassName) {

    String [] configuredFacets = factoryConfigs.get(providerClassName).getFacets();
    List facetPredicates = new ArrayList<>();

    for(String facet : configuredFacets) {

    if(facet.split("#").length > 1) {
    Predicate facetPredicate = new Predicate(facet.split("#")[0],facet.split("#")[1]);
    facetPredicate.set("property",facet.split("#")[2]);
    facetPredicates.add(facetPredicate);
    } else {
    LOGGER.debug("Configuration not properly set for facet {}",facet);
    }
    }

    return facetPredicates;
    }
    }



    ##### XML File name ####
    net.cms.aem.search.sql2.core.facets.QueryFacetConfigurationFactoryService.Full-text-search.xml

    I have tried with net.cms.aem.search.sql2.core.facets.QueryFacetConfigurationFactory.Full-text-search.xml as well.

    Thanks in advance
    Ameesh

    ReplyDelete