Sii Polska

SII UKRAINE

SII SWEDEN

  • Szkolenia
  • Kariera
Dołącz do nas Kontakt
Wstecz

Sii Polska

SII UKRAINE

SII SWEDEN

Wstecz

30.01.2026

AEM Sites – konfiguracja z użyciem Sling Context‑Aware Configuration

30.01.2026

AEM Sites – konfiguracja z użyciem Sling Context Aware Configuration

Przy tworzeniu jakiegokolwiek bardziej złożonego projektu w AEM prędzej czy później pojawia się temat przechowywania i dostępu do różnych danych konfiguracyjnych. Mogą to być zarówno informacje platformowe, klucze API, konfiguracje komponentów, jak i inne parametry.

W projektach dane te zazwyczaj przechowuje się z użyciem jednego lub kilku spośród poniższych mechanizmów:

  • Run Mode,
  • konfiguracje OSGi,
  • właściwości stron (Page Properties),
  • niestandardowe strony konfiguracji,
  • Sling Context‑Aware Configuration.

Należy pamiętać, że każdy z nich został stworzony do konkretnego celu i zgodnie z dobrymi praktykami nie powinien być używany w sposób niezgodny z przeznaczeniem.

  • Run Modes – powinny być wykorzystywane do danych związanych ze środowiskiem, np. konfiguracje autora lub publish.
  • Konfiguracje OSGi – najlepiej stosować je do nieedytowalnych przez autorów systemowych konfiguracji, takich jak: schedulery, serwisy, cache, integracje.
  • Page Properties – dobrze nadają się do konfiguracji zachowania pojedynczej strony, ale słabo się skalują i są trudne w zarządzaniu przy dużych projektach. Nie powinno się tam przechowywać informacji technicznych.
  • Niestandardowe strony konfiguracji – zwykle są elastyczne, ale wymagają dodatkowego rozwoju i dalszego utrzymania.
  • Sling Context‑Aware Configuration – w tę opcję zagłębimy się dokładniej w dalszej części artykułu.

Sling Context‑Aware Configuration

Zgodnie z dokumentacją, Context‑Aware Configurations (CAC) to konfiguracje powiązane z konkretnym zasobem lub drzewem zasobów, np.: stroną, stroną główną witryny czy tenantem. Obsługują one:

  • dziedziczenie – nie trzeba ustawiać wartości na każdej stronie poddrzewa,
  • fallback – wartości są wyszukiwane aż do korzenia konfiguracji.

Domyślna implementacja przechowuje dane pod ścieżką/conf, zapewniając dodatkowe fallbacki. Konfiguracja jest powiązana z poddrzewem treści poprzez właściwość sling:configRef, wskazującą na odpowiedni węzeł konfiguracji. Wszystkie zasoby pod tym poddrzewem próbują znaleźć odpowiednie konfiguracje, przechodząc w górę hierarchii, jeśli to konieczne.

/content
   /tenant1
      @sling:configRef="/conf/tenant1"
      /region1
         @sling:configRef="/conf/tenant1/region1"
	     /site1
         @sling:configRef="/conf/tenant1/region1/site1"
         
/conf
   /tenant1
      /sling:configs
         /x.y.z.MyConfig
            @prop1 = 'tenant1"
      /region1
         /sling:configs
            /x.y.z.MyConfig
               @prop1 = 'region1"
	     /site1
            /sling:configs
               /x.y.z.MyConfig
                 @prop1 = 'site1'

Dane konfiguracyjne są przechowywane pod węzłem sling:configs, a same węzły konfiguracji są nazwane zgodnie z nazwami klas, np. x.y.z.MyConfig, i zawierają pola, np. prop1.

Jak zobaczymy w dalszej części, mechanizm ten jest bardzo elastyczny.

Interfejsy konfiguracji

@Configuration(label="Configuration", description="Sample configuration")
public @interface MyConfig {

    @Property(label="Parameter #1", description="Describe me")
    String param1();
}

Są to zwykłe interfejsy z adnotacjami @Configuration i @Property. Możliwe jest również tworzenie konfiguracji zagnieżdżonych oraz tablic:

@Configuration(label="Configuration List", description="Sample list configuration")
public @interface MyConfigList {

    @Property(label="Key Value Pairs", description="List of key value pairs")
    KeyValue[] keyValues();
	
	@interface KeyValue {
		@Property
		String key();
		
		@Property
		String valye
	}
}

Odczyt konfiguracji

Resource contentResource = resourceResolver.getResource("/content/tenant1/region1/site1/page1");
MyConfig config = contentResource.adaptTo(ConfigurationBuilder.class).as(MyConfig.class);

Jak widać, jest to bardzo wygodne rozwiązanie, ponieważ cała logika związana z dostarczaniem właściwego kontekstu, wyszukiwaniem wartości pól, dziedziczeniem oraz mechanizmem fallback jest obsługiwana przez bibliotekę Context-Aware Configuration, bez konieczności pisania jakiegokolwiek własnego kodu. Warto również wspomnieć, że Sling CAC jest częścią standardowej instalacji OOTB AEM.

Co jednak, kiedy mamy specjalne wymagania, a mimo to chcielibyśmy nadal korzystać z CAC? Na szczęście, biblioteka pozwala dostosować ją do naszych potrzeb.

Dostosowanie i adaptacja do potrzeb projektu

Potencjalną wadą przechowywania danych konfiguracyjnych w CAC jest brak przyjaznego, out-of-the-box interfejsu użytkownika do edycji konfiguracji. Obecnie konfigurację można zmieniać jedynie poprzez dostarczanie jej wraz z paczką kodu albo bezpośrednio w narzędziu CRXDE.

Ale co w przypadku, kiedy chcemy, aby autorzy mogli edytować przynajmniej część konfiguracji i ją publikować? Na szczęście da się to zrealizować przy użyciu niewielkiej ilości własnego kodu do edycji i zapisu danych – co ważne, nadal korzystając ze wszystkich zalet CAC przy ich odczycie.

Przyjrzyjmy się scenariuszowi, w którym chcielibyśmy połączyć przechowywanie danych na zwykłych stronach, jako zwykłe komponenty, a jednocześnie odczytywać te wartości w ustandaryzowany sposób.

Załóżmy strukturę:

/content
   /tenant1
      /region1
	     /site1
	        /configuration – configuration page
	           /jcr:content
	              /myconfig – configuration component
	                 @prop1 = “value”

gdzie istnieje specjalna strona konfiguracyjna z instancjami komponentów przechowujących poszukiwane przez nas wartości pól.

W pierwszej kolejności musimy zaimplementować interfejs ContextPathStrategy:

@Component(
   service = ContextPathStrategy.class
   property = {
      “service.ranking.integer:1000”   
   }
}
public class CustomContextPathStrategy implments ContextPathStrategy {
   private static final String CONFIG_PAGE = "configuration";
   
   @Override
   public Iterator<ContextResource> findContextResource(Resource resource) {
      List<ContextResource> contextResources = new ArrayList();
      
      Resource current = resource; 
      while (current != null) {
         if (current.getPath().startsWith("/content/")) { 
            Resource configurationPage = current.getChild(CONFIG_PAGE); 
            if(configurationPage != null) {
               contextResources.add(new ContextResource(configurationPage, current.getPath()));
            }
         }
         current.getParent();
      }
      return contextResources.iterator();
   }
}

Ta usługa OSGI będzie używana podczas wyszukiwania ścieżki kontekstu. Wysoki ranking zapewnia, że zostanie użyta jako pierwsza. W metodzie findContextResource przeszukujemy hierarchię zasobów w górę do poziomu /content, szukając takiego, który posiada dziecko o nazwie configuration – czyli naszej strony konfiguracyjnej. Jeśli zostanie znaleziona, jest dodawana do wyniku.

Następnie przychodzi czas na użycie tej usługi we własnej implementacji ConfigurationResourceResolvingStrategy. W tym miejscu wprost wykorzystujemy CustomContextPathStrategy, która zwraca wyszukane ścieżki kontekstu.

Następnie iterujemy po nich, szukając komponentu, którego nazwa odpowiada nazwie interfejsu konfiguracji (MyConfig) oraz zgodna jest z zasadami tworzenia nazw komponentów w AEM (metoda getComponentName). Jeśli komponent zostanie znaleziony – metoda go zwraca, a jeśli nie – następuje fallback do kolejnej ścieżki kontekstu i cały scenariusz jest powtarzany.

@Component {
   serice = ConfigurationResourceResolvingStrategy.class,
   property = {
      "serivce.ranking:Integer=1000"
   }
}
public class CustomConfigurationResourceResolvingStrategy implements ConfigurationResourceResolvingStrategy {
   @Reference(target="(component.name=x.y.z.CustomContextPathStrategy)")
   private ContextPathStrategy contextPathStrategy;
   
   @Override
   public Resource getResource(Resource resourceResolver, Collection<String> bucketName) {
      String className = configName.substring(configName.lastIndex('.') + 1);
      return findContextResources(contentResource)
         .map(configurationPage -> findChildResource(configurationPage, getComponentName(className)))
         .filter(Object::nonNull)
         .findFirst()
         .orElse(null)
   }
   
   private String getComponentName(String className) {
      String componentName = className.toLowerCase();
      if(componentName.length() > 20) {
            return componentName.substring(0, 20);
      }
      return componentName;
   }
   
   private Resource findChildResource(Resource resource, String name) {
      if(resource.getName().equals(name)) {
         return resource;
      }
      for(Resource child : resource.getChildren()) {
         Resource result = findChildResource(child, name);
         if(resoult != null) {
            return result;
         }
      }
      return null;
   }

   ...
   implmenetation of the rest of the methods
   ...
   
}
oferty pracy

Podsumowanie

W podobny sposób można obsługiwać kolekcje. Trzeba jedynie pamiętać, że nazwa konfiguracji ma postać x.y.z.MyConfig/jcr:content/keyValues i trzeba to uwzględnić, aby była poprawnie zapisywana jak i odczytywana.

Część /jcr:content pochodzi z wewnętrznej implementacji Granite i w trakcie moich eksperymentów nie udało mi się jej zmienić.

Stworzenie implementacji tych dwóch interfejsów jest wystarczające, aby obsłużyć nasz niestandardowy scenariusz i stanowi świetny przykład elastyczności mechanizmu Sling Context-Aware Customization.

Dalsze rozważania

Istnieje również biblioteka 3rd party, która udostępnia UI oraz własne strategie konfiguracji: Context-Aware Configuration | wcm.io

Zawsze warto także zajrzeć do oficjalnej dokumentacji: Apache Sling :: Apache Sling Context-Aware Configuration

5/5
Ocena
5/5
Avatar

O autorze

Jakub Smoczyński

AEM Developer z kilkunastoletnią historią pracy przy projektach AEM i innych systemach Java/CMS. Wykorzystuje zdobyte doświadczenie, skupiając się na jak najpełniejszej realizacji wszystkich aspektów powierzonego projektu, nie ograniczając się do samego kodu. Po godzinach miłośnik dwóch kółek i podróży – najchętniej łącząc oba i w wyjazdy rowerowe

Wszystkie artykuły autora

Zostaw komentarz

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *

Może Cię również zainteresować

Dołącz do nas

Sprawdź oferty pracy

Pokaż wyniki
Dołącz do nas Kontakt

This content is available only in one language version.
You will be redirected to home page.

Are you sure you want to leave this page?