AEM Template Updater – Update the exiting template with a new template

Problem Statement:

How can I update an existing template with a new template? For example, If I want to update the 2nd/3rd level page template with a new template without moving child pages or reactivation.

Requirement:

  1. Update the Adventures page template from landing page template to adventure page template.
  2. Avoid moving child pages from current adventure page to new staging location
  3. Avoid bulk activation of child pages due to movement
Create duplicate page

Introduction:

Let’s try to solve the above issue manually:

Create a new page called adventures (which will be created has adventures0) using the adventure page template.

Copy all the child pages or move page by page from the current adventures page to the new adventure page.

Copy the child pages from source to destination

Delete the current adventures page and move the adventures0 page and rename it by removing 0

Move the page and renmae

Finally, click on manage publication to publish new adventures page and select all child pages to reactivate.

Include child pages before publishing

Let’s try to solve the above issue programmatically:

Create a new MCP process called TemplateTypeUpdaterFactory service class as shown below:

package com.mysite.mcp.process;

import org.osgi.service.component.annotations.Component;
import com.adobe.acs.commons.mcp.ProcessDefinitionFactory;

@Component(service = ProcessDefinitionFactory.class, immediate = true)
public class TemplateTypeUpdaterFactory extends ProcessDefinitionFactory<TemplateTypeUpdator> {
	@Override
	public String getName() {
		return "Template Type Updater";
	}
	@Override
	protected TemplateTypeUpdator createProcessDefinitionInstance() {
		return new TemplateTypeUpdator();
	}
}

Create TemplateTypeUpdator Implementation class as shown below:

package com.mysite.mcp.process;

import java.io.IOException;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.jcr.Node;
import javax.jcr.RepositoryException;
import org.apache.sling.api.request.RequestParameter;
import org.apache.sling.api.resource.LoginException;
import org.apache.sling.api.resource.PersistenceException;
import org.apache.sling.api.resource.ResourceResolver;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.adobe.acs.commons.data.Spreadsheet;
import com.adobe.acs.commons.fam.ActionManager;
import com.adobe.acs.commons.mcp.ProcessDefinition;
import com.adobe.acs.commons.mcp.ProcessInstance;
import com.adobe.acs.commons.mcp.form.Description;
import com.adobe.acs.commons.mcp.form.FileUploadComponent;
import com.adobe.acs.commons.mcp.form.FormField;
import com.adobe.acs.commons.mcp.model.GenericReport;
import com.day.cq.commons.jcr.JcrConstants;
import com.day.cq.commons.jcr.JcrUtil;
import com.day.cq.wcm.api.Page;
import com.day.cq.wcm.api.PageManager;
import com.day.cq.wcm.api.WCMException;

public class TemplateTypeUpdator extends ProcessDefinition {

	private static final Logger LOGGER = LoggerFactory.getLogger(TemplateTypeUpdator.class);
	private static final String REPORT_NAME = "Page-Template-Updated";
	private static final String EXECUTING_KEYWORD = "Performs Page Template change";
	private static final String SLASH = "/";
	private static final String REPORT_SAVE_PATH = "/jcr:content/report";
	private static final String DESTINATION_COL = "duplicatepage";
	private static final String SOURCE_COL = "originalpage";
	final Map<String, String> channelPaths = Collections.synchronizedMap(new HashMap<>());
	private final List<EnumMap<ReportColumns, Object>> reportRows = new ArrayList<>();
	GenericReport genericReport = new GenericReport();

	@FormField(name = "Template Update", description = "Excel spreadsheet for performing channel type updator", component = FileUploadComponent.class, required = false)
	private RequestParameter sourceFile;

	@Override
	public void buildProcess(ProcessInstance instance, ResourceResolver rr) throws LoginException, RepositoryException {
		validateInputs();
		genericReport.setName(REPORT_NAME);
		String desc = EXECUTING_KEYWORD;
		instance.defineCriticalAction("Updating Page", rr, this::createOrUpdatePage);
		instance.getInfo().setDescription(desc);
	}

	protected void createOrUpdatePage(ActionManager manager) {
		manager.deferredWithResolver(resourceResolver -> {
			try {
				for (Map.Entry<String, String> entry : channelPaths.entrySet()) {
					final PageManager pageManager = resourceResolver.adaptTo(PageManager.class);
					Page destinationPage = resourceResolver.resolve(entry.getValue()).adaptTo(Page.class);
					Page sourcePage = resourceResolver.resolve(entry.getKey()).adaptTo(Page.class);
					pageManager.delete(sourcePage, true, false);
					JcrUtil.copy(resourceResolver.resolve(destinationPage.getPath() + SLASH + JcrConstants.JCR_CONTENT)
							.adaptTo(Node.class), sourcePage.adaptTo(Node.class), null);
					pageManager.delete(destinationPage, false, false);
					recordData(entry.getKey());
				}
				if (resourceResolver.hasChanges()) {
					resourceResolver.commit();
				}
			} catch (WCMException | RepositoryException e) {
				LOGGER.error("unable to create or update page {}", e.getMessage());
			}
		});
	}

	protected void recordData(String channelPagePath) {
		final EnumMap<ReportColumns, Object> row = new EnumMap<>(ReportColumns.class);
		row.put(ReportColumns.SERIAL, 1);
		row.put(ReportColumns.CONTENT_PATH, channelPagePath);
		reportRows.add(row);
	}

	private void validateInputs() throws RepositoryException {
		if (sourceFile != null && sourceFile.getSize() > 0) {
			Spreadsheet sheet;
			try {
				sheet = new Spreadsheet(sourceFile, SOURCE_COL, DESTINATION_COL).buildSpreadsheet();
			} catch (IOException ex) {
				throw new RepositoryException("Unable to parse spreadsheet", ex);
			}

			if (!sheet.getHeaderRow().contains(SOURCE_COL) || !sheet.getHeaderRow().contains(DESTINATION_COL)) {
				throw new RepositoryException(
						MessageFormat.format("Spreadsheet should have two columns, respectively named {0} and {1}",
								SOURCE_COL, DESTINATION_COL));
			}

			sheet.getDataRowsAsCompositeVariants().forEach(
					row -> channelPaths.put(row.get(SOURCE_COL).toString(), row.get(DESTINATION_COL).toString()));
		}
	}

	@Override
	public void storeReport(ProcessInstance instance, ResourceResolver rr)
			throws RepositoryException, PersistenceException {
		genericReport.setRows(reportRows, ReportColumns.class);
		genericReport.persist(rr, instance.getPath() + REPORT_SAVE_PATH);
	}

	@Override
	public void init() throws RepositoryException {
	}

	enum TemplateType {
		HOME_PAGE_TEMPLATE, EXPLORATORY_TOPIC_TEMPLATE, EXPLORATORY_TOOL_TEMPLATE, PROBLEM_SOLVING_TOPIC_TEMPLATE
	}

	public enum ReportColumns {
		SERIAL, CONTENT_PATH
	}

	public enum PublishMethod {
		@Description("Select this option to generate Generate Article Pages")
		CHANNEL_PAGES
	}
}

Create a new adventures page under any path using the adventure page template.

Create an excel sheet with the following columns:

  1. Duplicatepage column – new adventures path
  2. Originalpage column – exiting adventures page path
excel sheet columns

Upload this excel sheet as an input into the new process as shown below:

select the process
Upload the excel sheet

Once the process starts it will update the exiting page template with a new template and it will delete the duplicate page

Once the update is completed you can use quick publish the updated adventures page.

You can also use the same process to update single or multiple pages at once by adding multiple rows into the excel sheet.

Excelsheet:

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s