CoreCompReferenceProviderDisabler.java

/*
 * #%L
 * wcm.io
 * %%
 * Copyright (C) 2021 wcm.io
 * %%
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * #L%
 */
package io.wcm.caconfig.extensions.references.impl;

import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
import org.osgi.service.component.runtime.ServiceComponentRuntime;
import org.osgi.service.component.runtime.dto.ComponentDescriptionDTO;
import org.osgi.service.event.Event;
import org.osgi.service.event.EventConstants;
import org.osgi.service.event.EventHandler;
import org.osgi.service.metatype.annotations.AttributeDefinition;
import org.osgi.service.metatype.annotations.Designate;
import org.osgi.service.metatype.annotations.ObjectClassDefinition;

/**
 * With Adobe Core Components 2.12.0, an own implementation of a context-aware reference provider was added
 * to the core components. It implements a much simplified logic compared to {@link ConfigurationReferenceProvider}
 * and does not properly detect the last modification date if one of the wcm.io persistence strategies
 * is used which stores the configuration in AEM pages that can be properly activated.
 *
 * <p>
 * To avoid conflicts with the two implementations trying to achieve the same, this services checks if
 * the Core Component reference provider is present, and deactivates it.
 * </p>
 *
 * <p>
 * Unfortunately there is no better way, as the Core Component reference provider does not support to be disabled via
 * OSGi configuration, and the AEM WCM Core implementation does not sort the reference providers by service ranking.
 * If both reference providers are active at the same time it's a matter of luck who "wins".
 * </p>
 *
 * <p>
 * The implementation of this service is heavily inspired by the "Component Disabler" of ACS AEM Commons.
 * </p>
 */
// There is not unit test for this class - it's too difficult in mock context.
@Component(service = EventHandler.class, property = {
    EventConstants.EVENT_TOPIC + "=org/osgi/framework/BundleEvent/STARTED",
    EventConstants.EVENT_TOPIC + "=org/osgi/framework/BundleEvent/REGISTERED"
})
@Designate(ocd = CoreCompReferenceProviderDisabler.Config.class)
public class CoreCompReferenceProviderDisabler implements EventHandler {

  @ObjectClassDefinition(name = "wcm.io Context-Aware Configuration Reference Provider Core Component Disabler",
      description = "Disables the context-aware configuration reference provider from Adobe Core Components, which "
          + "does not reliably detect the last modification date of configuration stored in AEM pages.")
  @interface Config {

    @AttributeDefinition(name = "Enabled",
        description = "Enable to automatic disable the reference provider from Core Components.")
    boolean enabled() default true;

  }

  static final String CORECOMP_REFERENCE_PROVIDER = "com.adobe.cq.wcm.core.components.internal.services.CaConfigReferenceProvider";

  @Reference
  private ServiceComponentRuntime scr;

  private BundleContext bundleContext;
  private boolean enabled;

  @Activate
  void activate(BundleContext context, Config config) {
    this.bundleContext = context;
    enabled = config.enabled();
    disableComponent();
  }

  @Override
  public void handleEvent(Event event) {
    // we don't care about the event, just make sure the component we want to disable is still disabled
    disableComponent();
  }

  private void disableComponent() {
    if (!enabled) {
      return;
    }
    for (Bundle bundle : bundleContext.getBundles()) {
      ComponentDescriptionDTO dto = scr.getComponentDescriptionDTO(bundle, CORECOMP_REFERENCE_PROVIDER);
      if (dto != null && scr.isComponentEnabled(dto)) {
        scr.disableComponent(dto);
      }
    }
  }

}