Expand my Community achievements bar.

Don’t miss the AEM Skill Exchange in SF on Nov 14—hear from industry leaders, learn best practices, and enhance your AEM strategy with practical tips.
SOLVED

Structuring Data in AEM JCR for a Shopping Mall Website

Avatar

Level 3

I am currently working on a project where I need to create a website using Adobe Experience Manager (AEM). The website is designed to represent multiple malls, with each mall housing numerous stores. Additionally, these stores will be providing various offers.

The client’s requirement is to store all the data related to the malls, stores, and offers in the AEM Java Content Repository (JCR). They have specifically requested not to use SQL, MySQL, or any other external data storage systems.

Given this scenario, I am seeking advice on the best way to structure this data in the JCR. Is it appropriate and efficient to store such hierarchical data in the JCR? If so, could you provide some guidance or best practices on how to achieve this?



Is the following way or organizing this data in JCR is proper way ? 

touseefk2181136_0-1715538224220.png

Any insights or suggestions would be greatly appreciated.

 

Topics

Topics help categorize Community content and increase your ability to discover relevant content.

1 Accepted Solution

Avatar

Correct answer by
Community Advisor

Hi @touseefk2181136 ,

JCR is designed to handle hierarchical data, and your structure aligns well with this model.

Here are some best practices you might consider:

  1. Use meaningful node names: Node names should be descriptive and meaningful. In your case, using the mall name, store name, and offer name as node names is a good approach.

  2. Leverage properties: Use properties to store data about nodes. For example, you can store the name, location, and type of a store as properties of the store node.

  3. Avoid deep hierarchies: While JCR can handle deep hierarchies, it’s generally a good practice to avoid overly deep structures as they can impact performance and complicate queries.

  4. Use node types: Node types allow you to define the structure and constraints of a node. You can define a node type for malls, stores, and offers, specifying what child nodes and properties they can have.

  5. Indexing: Make sure to set up proper indexing. This will greatly improve the performance of your queries.

Your structure would look something like this in terms of JCR nodes and properties:

 

/content
   /malls
      /mall1
         - jcr:title = "Mall 1"
         - location = "Location 1"
         /stores
            /store1
               - jcr:title = "Store 1"
               - type = "Electronic"
               /offers
                  /offer1
                     - jcr:title = "10% off"

 

In this structure, jcr:title is a commonly used property to store the display name of a node. Each mall, store, and offer is a node with properties. The stores and offers are intermediate nodes to group stores and offers respectively.

Thanks,
Madhur

View solution in original post

10 Replies

Avatar

Correct answer by
Community Advisor

Hi @touseefk2181136 ,

JCR is designed to handle hierarchical data, and your structure aligns well with this model.

Here are some best practices you might consider:

  1. Use meaningful node names: Node names should be descriptive and meaningful. In your case, using the mall name, store name, and offer name as node names is a good approach.

  2. Leverage properties: Use properties to store data about nodes. For example, you can store the name, location, and type of a store as properties of the store node.

  3. Avoid deep hierarchies: While JCR can handle deep hierarchies, it’s generally a good practice to avoid overly deep structures as they can impact performance and complicate queries.

  4. Use node types: Node types allow you to define the structure and constraints of a node. You can define a node type for malls, stores, and offers, specifying what child nodes and properties they can have.

  5. Indexing: Make sure to set up proper indexing. This will greatly improve the performance of your queries.

Your structure would look something like this in terms of JCR nodes and properties:

 

/content
   /malls
      /mall1
         - jcr:title = "Mall 1"
         - location = "Location 1"
         /stores
            /store1
               - jcr:title = "Store 1"
               - type = "Electronic"
               /offers
                  /offer1
                     - jcr:title = "10% off"

 

In this structure, jcr:title is a commonly used property to store the display name of a node. Each mall, store, and offer is a node with properties. The stores and offers are intermediate nodes to group stores and offers respectively.

Thanks,
Madhur

Avatar

Level 3

Hi @Madhur-Madan thanks for you reply. I have few question, can please take a look

 

  1. Will each hierarchical data will be with their page as show in the following pic ?
    touseefk2181136_0-1715588436641.png

     

  2. Or it can something like as shown in the following pic ?
    touseefk2181136_1-1715588604494.png

     





  3. I have malls, stores and offers data in CSV file. How can I import it in JCR and create corresponding nodes? Do I need to create any utility or I can do it through some groovy script or some other tool ?

    Thank you.

Avatar

Community Advisor

Hi @touseefk2181136 ,

In the structure I recommended, here’s how cq:Page and nt:unstructured could be used:

cq:Page: This node type is used for the mall1 node. This is because each mall could represent a page in your website.

/content
   /malls
      /mall1 (cq:Page) - jcr:title:"Mall 1"
                         sling:resourceType: "myapp/mall"
                         /jcr:content - location: "Location 1"

nt:unstructured: This node type is used for the store1 and offer1 nodes. This is because these nodes do not necessarily represent pages in your website, but rather components or elements within a page.

/content
   /malls
      /mall1 (cq:Page)
         /jcr:content
            /store1 (nt:unstructured) - name : "Store1"
                                        type:"Electronic"
                                      /offer1 (nt:unstructured) : "10% off"

This structure allows you to leverage AEM’s page editing features for each mall, while also providing flexibility for the stores and offers. However, this is just one possible way to structure your data, and the actual structure may vary based on your specific requirements.

On the approach part basically all three can be used and it depends on your use case so as which to pick.

  1. Custom Servlet (Utility): This method is powerful and flexible. It allows you to create a user interface for uploading the CSV file, and you can handle errors and edge cases in a user-friendly way. However, it requires more development effort compared to the other methods, as you need to write the servlet, create the user interface, and handle the file upload.

  2. Groovy Scripts: This method is quick and easy, especially if you have the ACS AEM Commons package installed. It allows you to quickly write and execute scripts without needing to compile or deploy code. However, it’s less user-friendly, as you need to manually run the script each time you want to import data. Also, error handling and edge cases might be more difficult to manage.

  3. JCR API: This method is also powerful and flexible, and it doesn’t require any additional packages. However, it requires you to write a standalone Java application, which might be overkill for a simple data import. Also, like the Groovy script method, it’s less user-friendly and requires manual execution.

Thanks,
Madhur

Avatar

Level 3

@Madhur-Madan for explaining comprehensively. I have another requirement. I have a list(hyperlinks) of stores on a page (a store component on page). The requirement is when user clicks on any specific store, a page for that store should be dynamically created and it should redirect user to that page and fetch the information for that store and show its information on that dynamically created page (store detail). Is it possible or I will have to create a separate page for each store. 

Avatar

Community Advisor

Hi @touseefk2181136 ,
The dynamic page creation is very much possible but keep in mind the dispatcher configs if you want the dynamic pages to be cached.
I will list down two approaches which you can use to fulfill the requirement. You can either use vanilla Js or you can use Sling Resource resolution approach.

1. Vanilla Js

  •  Create a store component in AEM that represents each store on the page. This component should include fields for the store name, description, and a button or link to view more details.

 

<!-- Store Component (/apps/myproject/components/store/store.html) -->
<div class="store">
    <h2 class="store-name">${properties.storeName}</h2>
    <p class="store-description">${properties.storeDescription}</p>
    <a href="#" class="view-details">View Details</a>
</div>



 

  • Attach a client-side JavaScript event handler to the "View Details" link in the store component. This event handler will trigger the dynamic page creation and redirection process

 

// Store Component JavaScript (/apps/myproject/components/store/clientlibs/store.js)
$(document).ready(function() {
    $('.view-details').on('click', function(e) {
        e.preventDefault(); // Prevent default link behavior

        var storeName = $(this).closest('.store').find('.store-name').text();
        
        // Check if the page already exists for the selected store
        $.ajax({
            url: '/content/myproject/stores/' + encodeURIComponent(storeName) + '.html',
            type: 'HEAD',
            success: function() {
                // Page already exists, redirect to it
                window.location.href = '/content/myproject/stores/' + encodeURIComponent(storeName) + '.html';
            },
            error: function() {
                // Page doesn't exist, create it dynamically
                createStorePage(storeName);
            }
        });
    });
});

// Function to create store page dynamically
function createStorePage(storeName) {
    // Sample Java code for dynamic page creation
    Resource parentPage = resourceResolver.getResource("/content/myproject/stores");
    PageManager pageManager = resourceResolver.adaptTo(PageManager.class);

    // Create a new page for the store
    Page storePage = pageManager.create(parentPage, storeName, "/apps/myproject/templates/store-detail.html", storeName);
    
    // Redirect to the newly created store detail page
    window.location.href = '/content/myproject/stores/' + encodeURIComponent(storeName) + '.html';
}

 

  • Create a template in AEM for the store detail pages. This template will define the structure and layout of the store detail pages.

 

<!-- Store Detail Template (/apps/myproject/templates/store-detail.html) -->
<sly data-sly-use.page="com.adobe.cq.wcm.core.components.models.Page">
    <h1>${page.properties.storeName}</h1>
    <p>${page.properties.storeDescription}</p>
    <!-- Additional components or placeholders for displaying store information -->
</sly>

 

  • When a user clicks on a store in the store component, dynamically create a new page for the selected store using AEM's page creation API or workflow capabilities. This can be done server-side in response to the click event.

 

// Sample Java code for dynamic page creation
Resource parentPage = resourceResolver.getResource("/content/myproject/stores");
PageManager pageManager = resourceResolver.adaptTo(PageManager.class);

// Create a new page for the store
Page storePage = pageManager.create(parentPage, storeName, "/apps/myproject/templates/store-detail.html", storeName);

 

  • After dynamically creating the store detail page, redirect the user to the newly created page using AEM's redirection capabilities or client-side JavaScript
  • On the store detail page (/content/myproject/stores/store-name.html), fetch and display store-specific information using AEM's component logic or server-side scripts.

 

<!-- Store Detail Page (/content/myproject/stores/store-name.html) -->
<sly data-sly-use.page="com.adobe.cq.wcm.core.components.models.Page">
    <h1>${page.properties.storeName}</h1>
    <p>${page.properties.storeDescription}</p>
    <!-- Additional components or placeholders for displaying store information -->
</sly>

 

2. Sling Resource Resolution

  • In AEM, navigate to the Vanity URL configuration (e.g., /etc/map) and create a new mapping for the store detail page template or component.
  • Define a Vanity URL pattern that will be used to access store detail pages. For example, /stores/{storeName}.
  • Implement a request filter or servlet in AEM to intercept requests to the Vanity URL pattern for store detail pages.
  • Extract the store name or identifier from the request URL parameters or path (e.g., using SlingHttpServletRequest). Check if a store detail page already exists for the extracted store name.
  • If a store detail page doesn't exist for the extracted store name, use AEM's page creation API or workflow capabilities to dynamically create the page.
  • Set up the created page to use the store detail page template or component.
  • After dynamically creating the store detail page, redirect the user to the Vanity URL for the newly created page.
  • This ensures that subsequent requests to the Vanity URL are directed to the dynamically created store detail page.

 

import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.resource.ResourceResolver;
import org.apache.sling.api.servlets.HttpConstants;
import org.apache.sling.api.servlets.ServletResolverConstants;
import org.apache.sling.api.servlets.Servlet;
import org.osgi.service.component.annotations.Component;
import org.osgi.framework.Constants;
import org.osgi.service.component.annotations.Reference;
import org.apache.sling.servlets.annotations.ServletResourceTypes;
import org.apache.sling.servlets.annotations.SlingServletResourceTypes;

@Component(service = Servlet.class,
           property = {
               Constants.SERVICE_DESCRIPTION + "=Dynamic Store Page Creation Servlet",
               ServletResolverConstants.SLING_SERVLET_PATHS + "=/bin/createStorePage"
           })
@SlingServletResourceTypes(resourceTypes = "sling/servlet/default", methods = HttpConstants.METHOD_GET)
public class CreateStorePageServlet extends SlingAllMethodsServlet {
    
    @Reference
    private ResourceResolver resourceResolver;

    @Override
    protected void doGet(SlingHttpServletRequest request, SlingHttpServletResponse response) {
        String storeName = request.getParameter("storeName");
        if (storeName != null) {
            // Check if page already exists for the store name
            if (!pageExistsForStore(storeName)) {
                // Page doesn't exist, dynamically create it
                createPageForStore(storeName);
            }
        }
    }

    private boolean pageExistsForStore(String storeName) {
        // Logic to check if page exists for the given store name
        // Implement your own logic to check if a page exists
    }

    private void createPageForStore(String storeName) {
        // Logic to dynamically create the store detail page
        // Use AEM's page creation API or workflow capabilities
        Resource parentPage = resourceResolver.getResource("/content/myproject/stores");
        PageManager pageManager = resourceResolver.adaptTo(PageManager.class);
        Page storePage = pageManager.create(parentPage, storeName, "/apps/myproject/templates/store-detail.html", storeName);
    }
}

 

I hope it helps

Thanks,
Madhur

Avatar

Level 3

@Madhur-Madan Thank you for explaining. I come to conclusion is to use a single store detail page and use content fragment on page to change the content related to each store. In this scenario, only thing I am thinking about is URL of the store page which should contain the name of the store to make it SEO friendly. 

Avatar

Employee Advisor

At first sight I would agree to your structure. But there are several unknowns, which can influence this. A few thoughts:

 

  • ACLs?
  • Content reuse (same offer over stores of multiple brands), you might think about using CF/XFs for it. Also about asset reuse.
  • Do you plan to use the MSM or the language copy feature? 

Avatar

Level 3

@Jörg_Hoh Thank you. It will be a public website.  Yes we plan to use MSM or language copy feature.

Avatar

Employee Advisor

In that case you should think how you want to use the MSM relations, and how the MSM rollouts will be done. Also about local changes, which are overriding MSM inheritance.

(You should also do the math and think about if the MSM is the right tool here. Rollouts can be costly/performance-intense, and if you are just duplicating content without ever overriding it locally you should thinking about replacing duplicated content with a reference.)

Also think about how you want to structure your content processes. You might want to do a paper-and-pencil exercise with your future authoring users to see if that content creation workflow is okay.