/*
 * Copyright 2017 Adobe.
 *
 * 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.
 */
package com.adobe.acs.commons.mcp.impl;

import com.adobe.acs.commons.fam.ActionManagerFactory;
import com.adobe.acs.commons.mcp.ControlledProcessManager;
import com.adobe.acs.commons.mcp.ProcessDefinition;
import com.adobe.acs.commons.mcp.ProcessInstance;
import com.adobe.acs.commons.mcp.model.impl.ArchivedProcessInstance;
import com.adobe.acs.commons.util.visitors.TreeFilteringResourceVisitor;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.management.openmbean.OpenDataException;
import javax.management.openmbean.TabularDataSupport;
import org.apache.commons.lang3.reflect.FieldUtils;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Property;
import org.apache.felix.scr.annotations.Reference;
import org.apache.felix.scr.annotations.ReferenceCardinality;
import org.apache.felix.scr.annotations.ReferencePolicy;
import org.apache.felix.scr.annotations.Service;
import org.apache.sling.api.resource.LoginException;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.resource.ResourceResolver;
import org.apache.sling.api.resource.ResourceResolverFactory;
import org.slf4j.LoggerFactory;

/**
 * Implementation of ControlProcessManager service
 */
@Component
@Service(ControlledProcessManager.class)
@Property(name = "jmx.objectname", value = "com.adobe.acs.commons:type=Manage Controlled Processes")
public class ControlledProcessManagerImpl implements ControlledProcessManager {
    private static final org.slf4j.Logger LOG = LoggerFactory.getLogger(ControlledProcessManagerImpl.class);

    private static final String SERVICE_NAME = "manage-controlled-processes";
    private static final Map<String, Object> AUTH_INFO;
    @Reference(cardinality = ReferenceCardinality.MANDATORY_MULTIPLE, bind = "bindDefinition", unbind = "unbindDefinition", referenceInterface = ProcessDefinition.class, policy = ReferencePolicy.DYNAMIC)
    private final List<ProcessDefinition> processDefinitions = Collections.synchronizedList(new ArrayList<>());

    static {
        AUTH_INFO = Collections.singletonMap(ResourceResolverFactory.SUBSERVICE, (Object) SERVICE_NAME);
    }
    Map<String, ProcessInstance> activeProcesses = Collections.synchronizedMap(new LinkedHashMap<>());

    @Reference
    ResourceResolverFactory resourceResolverFactory;

    @Reference
    ActionManagerFactory amf;

    @Override
    public ActionManagerFactory getActionManagerFactory() {
        return amf;
    }

    protected void bindDefinition(ProcessDefinition def) {
        processDefinitions.add(def);
    }

    protected void unbindDefinition(ProcessDefinition def) {
        processDefinitions.remove(def);
    }

    @Override
    public ProcessInstance getManagedProcessInstanceByPath(String path) {
        throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
    }

    @Override
    public ProcessInstance getManagedProcessInstanceByIdentifier(String id) {
        ProcessInstance process = activeProcesses.get(id);
        if (process != null) {
            process.updateProgress();
        }
        return process;
    }

    @Override
    public ProcessInstance createManagedProcessInstance(ProcessDefinition definition, String description) {
        ProcessInstance instance = new ProcessInstanceImpl(this, definition, description);
        activeProcesses.put(instance.getId(), instance);
        return instance;
    }

    @Override
    public void haltActiveProcesses() {
        Set<ProcessInstance> instances = new HashSet<>(activeProcesses.values());
        activeProcesses.clear();
        instances.forEach(ProcessInstance::halt);
    }

    @Override
    public void purgeCompletedProcesses() {
        activeProcesses.values().removeIf(proc -> !proc.getInfo().isIsRunning());
    }

    @Override
    public ResourceResolver getServiceResourceResolver() throws LoginException {
        return resourceResolverFactory.getServiceResourceResolver(AUTH_INFO);
    }

    @Override
    public ProcessDefinition findDefinitionByNameOrPath(String nameOrPath) throws ReflectiveOperationException {
        if (nameOrPath.startsWith("/")) {
            return findDefinitionByPath(nameOrPath);
        } else {
            return findDefinitionByName(nameOrPath);
        }
    }

    private ProcessDefinition findDefinitionByName(String name) throws ReflectiveOperationException {
        Class defClass = Class.forName(name);
        ProcessDefinition definition = (ProcessDefinition) defClass.newInstance();
        processDefinitions.stream().filter(d -> d.getClass().equals(defClass)).findFirst().ifPresent(svc -> copyReferences(svc, definition));
        return definition;
    }

    private <T> void copyReferences(T src, T dest) {
        for (Field f : src.getClass().getDeclaredFields()) {
            try {
                Object srcValue = FieldUtils.readField(f, src, true);
                if (srcValue != null && FieldUtils.readField(f, dest, true) == null) {
                    FieldUtils.writeField(f, dest, srcValue);
                }
            } catch (IllegalAccessException ex) {
                Logger.getLogger(ControlledProcessManagerImpl.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
    }

    private ProcessDefinition findDefinitionByPath(String path) throws ReflectiveOperationException {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    @Override
    public TabularDataSupport getStatistics() throws OpenDataException {
        TabularDataSupport stats = new TabularDataSupport(ProcessInstanceImpl.getStaticsTableType());
        activeProcesses.values().stream().map(ProcessInstance::getStatistics).forEach(stats::put);
        return stats;
    }

    @Override
    public void haltProcessById(String id) {
        getManagedProcessInstanceByIdentifier(id).halt();
    }

    @Override
    public void haltProcessByPath(String path) {
        getManagedProcessInstanceByPath(path).halt();
    }

    @Override
    public Collection<ProcessInstance> getActiveProcesses() {
        activeProcesses.forEach((id, process) -> process.updateProgress());
        return activeProcesses.values();
    }
    
    @Override
    public Collection<ProcessInstance> getInactiveProcesses() {
        ArrayList<ProcessInstance> processes = new ArrayList();
        try (ResourceResolver rr = getServiceResourceResolver()) {
            Resource tree = rr.getResource(ProcessInstanceImpl.BASE_PATH);
            TreeFilteringResourceVisitor visitor = new TreeFilteringResourceVisitor();
            visitor.setLeafVisitor((r,l)->{
                if (!activeProcesses.containsKey(r.getName())) {
                    processes.add(r.adaptTo(ArchivedProcessInstance.class));
                }
            });
            visitor.accept(tree);
        } catch (LoginException ex) {
            LOG.error("Error getting inactive process list", ex);
        }
        return processes;
    }
}

@@ -5305,39 +5305,308 @@
 ass 
-defClass = 
+clazz;%0A        try %7B%0A            clazz = Class.forName(name);%0A        %7D catch (ClassNotFoundException ex) %7B%0A            clazz = processDefinitions.stream().map(Object::get
 Class
+)
 .f
-orName(name);
+ilter(c -%3E c.getName().equals(name)).findFirst().orElseThrow(() -%3E ex);%0A        %7D%0A%0A        final Class defClass = clazz;%0A
 %0A   

Fix reflection issue