All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.geomajas.internal.service.pipeline.PipelineServiceImpl Maven / Gradle / Ivy

The newest version!
/*
 * This is part of Geomajas, a GIS framework, http://www.geomajas.org/.
 *
 * Copyright 2008-2016 Geosparc nv, http://www.geosparc.com/, Belgium.
 *
 * The program is available in open source according to the GNU Affero
 * General Public License. All contributions in this program are covered
 * by the Geomajas Contributors License Agreement. For full licensing
 * details, see LICENSE.txt in the project root.
 */

package org.geomajas.internal.service.pipeline;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.annotation.PostConstruct;

import org.geomajas.global.ExceptionCode;
import org.geomajas.global.GeomajasException;
import org.geomajas.service.pipeline.PipelineContext;
import org.geomajas.service.pipeline.PipelineHook;
import org.geomajas.service.pipeline.PipelineInfo;
import org.geomajas.service.pipeline.PipelineInterceptor;
import org.geomajas.service.pipeline.PipelineInterceptor.ExecutionMode;
import org.geomajas.service.pipeline.PipelineService;
import org.geomajas.service.pipeline.PipelineStep;
import org.geotools.util.Utilities;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

/**
 * Service which allows "executing" a pipeline.
 * 
 * @param 
 *            type of response object for the pipeline
 * 
 * @author Joachim Van der Auwera
 * @author Jan De Moerloose
 */
@Component
public class PipelineServiceImpl implements PipelineService {

	private final Logger log = LoggerFactory.getLogger(PipelineServiceImpl.class);
	
	private static final String INDENT = "   ";

	@Autowired
	private List> pipelineInfos;

	private Map> pipelineMap;

	/** @inheritDoc */
	public void execute(String key, String layerId, PipelineContext context, RESPONSE response)
			throws GeomajasException {
		execute(getPipeline(key, layerId), context, response);
	}

	/** @inheritDoc */
	public void execute(PipelineInfo pipeline, PipelineContext startContext, RESPONSE response)
			throws GeomajasException {
		PipelineContext context = startContext;
		if (null == context) {
			context = createContext();
		}
		long ps = 0;
		if (log.isDebugEnabled()) {
			ps = System.currentTimeMillis();
			log.debug("execute pipeline {}", pipeline.getPipelineName());
		}
		for (PipelineStep step : pipeline.getPipeline()) {
			if (context.isFinished()) {
				log.debug("context finished, pipeline {} execution done.", pipeline.getPipelineName());
				break;
			}
			long ts = 0;
			if (log.isDebugEnabled()) {
				log.debug("execute step {} for pipeline {}.", step.getId(), pipeline.getPipelineName());
				ts = System.currentTimeMillis();
			}
			step.execute(context, response);
			if (log.isDebugEnabled()) {
				log.debug("done step {}, time {}s", step.getId(), (System.currentTimeMillis() - ts) / 1000.0);
			}
		}
		if (log.isDebugEnabled()) {
			log.debug("pipeline done, {}, time {}s", pipeline.getPipelineName(),
					(System.currentTimeMillis() - ps) / 1000.0);
		}
	}

	/** @inheritDoc */
	@SuppressWarnings("unchecked")
	public PipelineInfo getPipeline(String pipelineName, String layerId) throws GeomajasException {
		PipelineStamp stamp = new PipelineStamp(pipelineName, layerId);
		PipelineInfo pipeline = pipelineMap.get(stamp);
		if (null == pipeline) {
			stamp = new PipelineStamp(pipelineName, null);
			pipeline = pipelineMap.get(stamp);
		}
		if (null == pipeline) {
			throw new GeomajasException(ExceptionCode.PIPELINE_UNKNOWN, pipelineName, layerId);
		}
		return pipeline;
	}

	/** @inheritDoc */
	public PipelineContext createContext() {
		return new PipelineContextImpl();
	}

	@SuppressWarnings("unchecked")
	@PostConstruct
	protected void postConstruct() throws GeomajasException {
		// sort pipelines so a pipeline is always before it's delegate (to make sure delegates are not handled before
		// delegating), and some sanity checks
		List> delegateLast = new ArrayList>();
		for (PipelineInfo pipeline : pipelineInfos) {
			if (null != pipeline.getPipeline() && null != pipeline.getDelegatePipeline()) {
				throw new GeomajasException(ExceptionCode.PIPELINE_DEFINED_AND_DELEGATE, pipeline.getPipelineName());
			}

			List> missing = new ArrayList>();
			PipelineInfo delegate = pipeline;
			while (delegate != null && !delegateLast.contains(delegate)) {
				missing.add(delegate);
				delegate = delegate.getDelegatePipeline();
			}
			// prepend missing delegate last
			delegateLast.addAll(0, missing);
		}

		// extend the delegating pipelines
		for (PipelineInfo pipeline : delegateLast) {
			if (pipeline.getPipeline() == null) {
				log.debug("extending pipeline {} with delegate {}", pipeline.getPipelineName(), pipeline
						.getDelegatePipeline() == null ? "none" : pipeline.getDelegatePipeline().getPipelineName());
				extendPipeline(pipeline);
				for (PipelineStep pipelineStep : pipeline.getPipeline()) {
					log.debug("added step {} to pipeline {}", pipelineStep.getId(), pipeline.getPipelineName());
				}
				log.debug("pipeline {} finished", pipeline.getPipelineName());
			} else {
				log.debug("pipeline {} with delegate {} has no extensions", pipeline.getPipelineName(), pipeline
						.getDelegatePipeline() == null ? "none" : pipeline.getDelegatePipeline().getPipelineName());
			}
		}
		// add the interceptors
		for (PipelineInfo pipeline : delegateLast) {
			interceptPipeline(pipeline);
		}
		// remove double names
		pipelineMap = new HashMap>();
		for (PipelineInfo pipeline : pipelineInfos) {
			// equal stamps will be overwritten here, last one wins !
			pipelineMap.put(new PipelineStamp(pipeline), pipeline);
		}
		if (log.isDebugEnabled()) {
			log.debug("listing pipeline structures");
			for (PipelineInfo pipeline : pipelineMap.values()) {
				log.debug("");
				print(pipeline);
			}
		}
	}

	private void print(PipelineInfo pipeline) {
		log.debug("", pipeline.getPipelineName(), pipeline.getLayerId());
		for (PipelineStep step : pipeline.getPipeline()) {
			printStep(INDENT, step);
		}
		log.debug("");
	}

	private void printStep(String indent, PipelineStep step) {
		if (step instanceof PipelineInterceptorStep) {
			PipelineInterceptorStep pis = (PipelineInterceptorStep) step;
			log.debug(indent + "", pis.getId());
			for (PipelineStep st : pis.getSteps()) {
				printStep(indent + INDENT, st);
			}
			log.debug(indent + "");
		} else {
			log.debug(indent + "", step.getId());
		}
	}

	@SuppressWarnings("unchecked")
	private void extendPipeline(PipelineInfo pipeline) throws GeomajasException {
		log.debug("extending pipeline {}", pipeline.getPipelineName());
		// collect extensions and steps
		List> steps = new ArrayList>();
		Map>> extensions = new HashMap>>();
		PipelineInfo delegate = pipeline;
		int total = 0;
		while (delegate != null) {
			if (delegate.getExtensions() != null) {
				for (Map.Entry> extensionEntry : delegate.getExtensions().entrySet()) {
					if (!extensions.containsKey(extensionEntry.getKey())) {
						extensions.put(extensionEntry.getKey(), new ArrayList>());
					}
					extensions.get(extensionEntry.getKey()).add(extensionEntry.getValue());
					total++;
				}
			}
			if (delegate.getPipeline() != null) {
				steps.addAll(delegate.getPipeline());
			}
			delegate = delegate.getDelegatePipeline();
		}

		// we have everything, lets extend...
		int count = 0;
		for (int i = steps.size() - 1; i >= 0; i--) {
			PipelineStep step = steps.get(i);
			if (step instanceof PipelineHook) {
				List> extList = extensions.get(step.getId());
				if (null != extList) {
					steps.addAll(i + 1, extList);
					for (PipelineStep ext : extList) {
						log.debug("extending pipeline {} with step {}", pipeline.getPipelineName(), ext.getId());
					}
					count += extList.size();
				}
			}
		}
		if (count != total) {
			throw new GeomajasException(ExceptionCode.PIPELINE_UNSATISFIED_EXTENSION, pipeline.getPipelineName(),
					pipeline.getLayerId());
		}
		pipeline.setPipeline(steps);
	}

	@SuppressWarnings("unchecked")
	private void interceptPipeline(PipelineInfo pipeline) throws GeomajasException {
		// collect interceptors
		List> interceptors = new ArrayList>();
		PipelineInfo delegate = pipeline;
		while (delegate != null) {
			if (delegate.getInterceptors() != null) {
				// prepend to preserve order
				interceptors.addAll(0, delegate.getInterceptors());
			}
			delegate = delegate.getDelegatePipeline();
		}

		// create a step for each interceptor
		List> interceptorSteps = new ArrayList>();
		List> steps = new ArrayList>(pipeline.getPipeline());
		for (PipelineInterceptor interceptor : interceptors) {
			interceptorSteps.add(new PipelineInterceptorStep(interceptor, steps));
		}
		// sort by width, smallest first
		Collections.sort(interceptorSteps, new WidthComparator());

		// construct the nested hierarchy of normal steps and interceptor steps
		for (PipelineInterceptorStep interceptorStep : interceptorSteps) {
			log.debug("adding interceptor {} to pipeline {}", interceptorStep.getId(), pipeline.getPipelineName());
			int fromIndex = -1;
			int toIndex = -1;
			// find the from and to step indices (takes nesting into account)
			for (int i = 0 ; i < steps.size() ; i++) {
				PipelineStep pipelineStep = steps.get(i);
				if (pipelineStep instanceof PipelineInterceptorStep) {
					PipelineInterceptorStep pis = (PipelineInterceptorStep) pipelineStep;
					PipelineStep fromStep = pis.getFromStep();
					if (fromStep.equals(interceptorStep.getFromStep())) {
						fromIndex = i;
					}
					PipelineStep toStep = pis.getToStep();
					if (toStep.equals(interceptorStep.getToStep())) {
						toIndex = i;
					}
				} else {
					if (pipelineStep.equals(interceptorStep.getFromStep())) {
						fromIndex = i;
					}
					if (pipelineStep.equals(interceptorStep.getToStep())) {
						toIndex = i;
					}
				}
			}
			if (fromIndex > toIndex) {
				throw new GeomajasException(ExceptionCode.PIPELINE_INTERCEPTOR_INVALID_NESTING,
						interceptorStep.getId());
			}
			// nest the steps
			interceptorStep.setSteps(new ArrayList>(steps.subList(fromIndex, toIndex + 1)));
			for (int i = toIndex; i >= fromIndex; i--) {
				steps.remove(i);
			}
			steps.add(fromIndex, interceptorStep);
		}
		pipeline.setPipeline(steps);
	}

	/**
	 * Pipeline step that executes an interceptor.
	 * 
	 * @author Jan De Moerloose
	 * 
	 * @param 
	 */
	protected class PipelineInterceptorStep implements PipelineStep {

		private PipelineInterceptor interceptor;

		private List> steps;

		private PipelineStep fromStep;

		private PipelineStep toStep;

		private final int width;

		public PipelineInterceptorStep(PipelineInterceptor interceptor, List> steps)
				throws GeomajasException {
			this.interceptor = interceptor;
			int fromIndex = -1;
			int toIndex = -1;
			for (int i = 0 ; i < steps.size() ; i++) {
				PipelineStep pipelineStep = steps.get(i);
				if (pipelineStep.getId().equals(interceptor.getFromStepId())) {
					fromIndex = i;
					fromStep = pipelineStep;
				}
				if (pipelineStep.getId().equals(interceptor.getToStepId())) {
					toIndex = i;
					toStep = pipelineStep;
				}
			}
			if (fromIndex == -1) {
				if (null != interceptor.getFromStepId()) {
					throw new GeomajasException(ExceptionCode.PIPELINE_INTERCEPTOR_INVALID_STEP, interceptor.getId(),
							interceptor.getFromStepId());
				} else {
					fromIndex = 0;
					fromStep = steps.get(0);
				}
			}
			if (toIndex == -1) {
				if (null != interceptor.getToStepId()) {
					throw new GeomajasException(ExceptionCode.PIPELINE_INTERCEPTOR_INVALID_STEP, interceptor.getId(),
							interceptor.getToStepId());
				} else {
					int pos = steps.size() - 1;
					toIndex = pos;
					toStep = steps.get(pos);
				}
			}
			if (fromIndex > toIndex) {
				throw new GeomajasException(ExceptionCode.PIPELINE_INTERCEPTOR_STEPS_ORDER, interceptor.getId());
			}
			width = toIndex - fromIndex + 1;
		}

		public void setSteps(List> steps) {
			this.steps = steps;
		}

		public List> getSteps() {
			return steps;
		}

		public String getId() {
			return interceptor.getId();
		}

		public int getWidth() {
			return width;
		}

		public PipelineStep getFromStep() {
			return fromStep;
		}

		public PipelineStep getToStep() {
			return toStep;
		}

		public void execute(PipelineContext context, T response) throws GeomajasException {
			log.debug("execute beforeSteps for interceptor {}", interceptor.getId());
			long its = 0;
			if (log.isDebugEnabled()) {
				its  = System.currentTimeMillis();
			}
			ExecutionMode mode = interceptor.beforeSteps(context, response);
			if (mode == null) {
				mode = ExecutionMode.EXECUTE_ALL;
			}
			switch (mode) {
				case EXECUTE_ALL:
				case EXECUTE_STEPS_NOT_AFTER:
					if (!context.isFinished()) {
						for (PipelineStep step : getSteps()) {
							if (context.isFinished()) {
								log.debug("context finished, interceptor {} execution done", interceptor.getId());
								break;
							}
							long ts = 0;
							if (log.isDebugEnabled()) {
								ts = System.currentTimeMillis();
								log.debug("execute step {} for interceptor {}", step.getId(), interceptor.getId());
							}
							step.execute(context, response);
							if (log.isDebugEnabled()) {
								log.debug("done step {}, time {}s", step.getId(),
										(System.currentTimeMillis() - ts) / 1000.0);
							}
						}
					}
					break;
				default:
					log.debug("skipping steps for interceptor {}", interceptor.getId());
			}
			switch (mode) {
				case EXECUTE_ALL:
				case EXECUTE_SKIP_STEPS:
					interceptor.afterSteps(context, response);
					break;
				default:
					log.debug("skipping afterSteps for interceptor {}", interceptor.getId());
			}
			if (log.isDebugEnabled()) {
				log.debug("beforeSteps done for {}, time {}s", interceptor.getId(),
						(System.currentTimeMillis() - its) / 1000.0);
			}
		}
	}

	/**
	 * Comparator for sorting interceptors by width.
	 * 
	 * @author Jan De Moerloose
	 */
	protected class WidthComparator implements Comparator> {

		public int compare(PipelineInterceptorStep o1, PipelineInterceptorStep o2) {
			return o1.getWidth() - o2.getWidth();
		}

	}

	/**
	 * Stamp for detection of duplicate pipelines.
	 * 
	 * @author Jan De Moerloose
	 */
	private static final class PipelineStamp {

		private final String name;

		private final String layerId;

		public PipelineStamp(PipelineInfo info) {
			this.name = info.getPipelineName();
			this.layerId = info.getLayerId();
		}

		public PipelineStamp(String name, String layerId) {
			this.name = name;
			this.layerId = layerId;
		}

		public boolean equals(Object otherObject) {
			if (this == otherObject) {
				return true;
			}
			if (otherObject == null || !(otherObject instanceof PipelineStamp)) {
				return false;
			}
			PipelineStamp otherStamp = (PipelineStamp) otherObject;
			return Utilities.equals(name, otherStamp.name) && Utilities.equals(layerId, otherStamp.layerId);
		}

		public int hashCode() {
			int result = 13;
			result = Utilities.hash(name, result);
			result = Utilities.hash(layerId, result);
			return result;
		}
	}

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy