Skip to content

Instantly share code, notes, and snippets.

@ahmed-musallam
Created March 19, 2019 17:15
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ahmed-musallam/1d1e9eeec9ef6db647a59f2739041983 to your computer and use it in GitHub Desktop.
Save ahmed-musallam/1d1e9eeec9ef6db647a59f2739041983 to your computer and use it in GitHub Desktop.
An ACS MCP that helps in updating one component property in bulk
package com.mycom.core.mcp;
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.CheckboxComponent;
import com.adobe.acs.commons.mcp.form.FormField;
import com.adobe.acs.commons.mcp.form.PathfieldComponent;
import com.adobe.acs.commons.mcp.model.GenericReport;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.EnumMap;
import java.util.List;
import javax.jcr.RepositoryException;
import org.apache.commons.lang3.StringUtils;
import org.apache.sling.api.resource.LoginException;
import org.apache.sling.api.resource.ModifiableValueMap;
import org.apache.sling.api.resource.PersistenceException;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.resource.ResourceResolver;
public class ComponentPropertyUpdater extends ProcessDefinition implements Serializable {
private static final long serialVersionUID = 7526476295622773464L;
@FormField(
name = "Starting Path",
description = "Starting Path",
hint = "/var/eventing",
component = PathfieldComponent.FolderSelectComponent.class,
options = {"base=/content"})
public String startingPath;
@FormField(
name = "Component sling:ResourceType",
description = "The component sling:ResourceType you want to change as is in content",
hint = "myproject/path/to/component")
public String type;
@FormField(
name = "Property to change",
description = "The property you want to change",
hint = "someProperty")
public String property;
@FormField(
name = "nNew value",
description = "The new value to be set on the property",
hint = "some value")
public String propertyNewValue;
@FormField(
name = "Multi-valued property?",
description =
"If checked, the value will be saved as multivalued and will be treated as a CSV",
component = CheckboxComponent.class)
private boolean isMultiValued = true;
@Override
public void init() {}
@Override
public void buildProcess(ProcessInstance processInstance, ResourceResolver resourceResolver)
throws LoginException, RepositoryException {
processInstance.defineAction("Component Property Updater", resourceResolver, this::doSomething);
processInstance.getInfo().setDescription(startingPath);
}
private void doSomething(ActionManager manager) {
ComponentVisitor visitor =
new ComponentVisitor(
resource -> {
if (resource.getResourceType().equals(type)) {
manager.deferredWithResolver(
rr -> {
String path = resource.getPath();
updateComponent(rr, path);
});
}
});
manager.deferredWithResolver(
rr -> {
Resource res = rr.getResource(startingPath);
if (res != null) {
visitor.accept(res);
}
});
}
private void updateComponent(ResourceResolver rr, String componentPath) throws Exception {
Resource resource = rr.getResource(componentPath);
if (resource == null) {
throw new IllegalStateException("Could not get resource at path: " + componentPath);
}
ModifiableValueMap map = resource.adaptTo(ModifiableValueMap.class);
if (map == null) {
throw new PersistenceException("resource at " + resource.getPath() + " is not modifiable.");
} else {
String old = map.get(property, String.class);
if (isMultiValued) {
// we have to remove the property and commit the resolver
// else we get an error: "Can not assign multiple values to single valued property"
if (map.containsKey(property)) {
map.remove(property);
rr.commit();
rr.refresh();
}
map.put(property, StringUtils.split(propertyNewValue, ","));
} else {
map.put(property, propertyNewValue);
}
rr.commit();
trackActivity(resource.getPath(), old);
}
}
@SuppressWarnings("squid:S00115")
public enum ReportColumns {
component_path,
property_changed,
old_value,
new_value
}
private transient List<EnumMap<ComponentPropertyUpdater.ReportColumns, Object>> reportRows;
private synchronized EnumMap<ReportColumns, Object> trackActivity(String path, String oldValue) {
if (reportRows == null) {
reportRows = Collections.synchronizedList(new ArrayList<>());
}
EnumMap<ReportColumns, Object> reportRow = new EnumMap<>(ReportColumns.class);
reportRow.put(ReportColumns.component_path, path);
reportRow.put(ReportColumns.property_changed, property);
reportRow.put(ReportColumns.old_value, oldValue);
reportRow.put(ReportColumns.new_value, propertyNewValue);
reportRows.add(reportRow);
return reportRow;
}
@Override
public synchronized void storeReport(
ProcessInstance processInstance, ResourceResolver resourceResolver)
throws RepositoryException, PersistenceException {
GenericReport report = new GenericReport();
report.setRows(reportRows, ReportColumns.class);
report.persist(resourceResolver, processInstance.getPath() + "/jcr:content/report");
}
}
package com.mycom.core.mcp;
import com.adobe.acs.commons.mcp.AdminOnlyProcessDefinitionFactory;
import com.adobe.acs.commons.mcp.ProcessDefinitionFactory;
import org.osgi.service.component.annotations.Component;
@Component(
immediate = true,
property = {"label=Factory of Component Property Updater"},
service = ProcessDefinitionFactory.class)
public class ComponentPropertyUpdaterFactory extends
AdminOnlyProcessDefinitionFactory<ComponentPropertyUpdater> {
@Override
public String getName() {
return "Component Property Updater";
}
@Override
public ComponentPropertyUpdater createProcessDefinitionInstance() {
return new ComponentPropertyUpdater();
}
}
package com.mycom.core.mcp;
import com.adobe.acs.commons.util.visitors.ContentVisitor;
import com.adobe.acs.commons.util.visitors.ResourceRunnable;
import com.day.cq.wcm.api.NameConstants;
import org.apache.jackrabbit.JcrConstants;
import org.apache.sling.jcr.resource.api.JcrResourceConstants;
/** The same as ACS's ContentVisitor, but accepts nt:unstructured nodes to traverse components. */
public class ComponentVisitor extends ContentVisitor {
private static final String NT_PAGE_CONETNT = "cq:PageContent";
private static final String[] CONTAINER_TYPES = {
JcrResourceConstants.NT_SLING_FOLDER,
JcrResourceConstants.NT_SLING_ORDERED_FOLDER,
JcrConstants.NT_FOLDER
};
private static final String[] CONTENT_TYPES = {
NameConstants.NT_PAGE, NT_PAGE_CONETNT, JcrConstants.NT_UNSTRUCTURED
};
public ComponentVisitor(ResourceRunnable runnable) {
super(runnable, CONTAINER_TYPES, CONTENT_TYPES);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment