/**
* This file is part of PaxmlCore.
*
* PaxmlCore is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* PaxmlCore is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with PaxmlCore. If not, see <http://www.gnu.org/licenses/>.
*/
package org.paxml.launch;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.Vector;
import java.util.concurrent.atomic.AtomicLong;
import org.paxml.core.PaxmlResource;
import org.paxml.tag.plan.PlanEntityFactory.Plan;
import org.springframework.core.io.Resource;
/**
* Launch model impl.
*
* @author Xuetao Niu
*
*/
public class LaunchModel {
/**
* Per-jvm/classloader increasing process id generator, starting from 1.
*/
private static final AtomicLong PID = new AtomicLong(1);
private final StaticConfig config = new StaticConfig();
private volatile Resource resource;
private volatile String name;
private final Map<String, Group> groups = Collections.synchronizedMap(new LinkedHashMap<String, Group>());
private final Settings globalSettings = new Settings(null);
private volatile List<LaunchPoint> launchPoints;
private volatile long planProcessId;
private volatile int concurrency;
private Plan planEntity;
public long getPlanProcessId() {
return planProcessId;
}
public void setPlanProcessId(long planProcessId) {
this.planProcessId = planProcessId;
}
public Settings getGlobalSettings() {
return globalSettings;
}
public Map<String, Group> getGroups() {
return groups;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getConcurrency() {
return concurrency;
}
public void setConcurrency(int concurrency) {
this.concurrency = concurrency;
}
/**
* Get the selected groups to run.
*
* @return the selected
*/
private Set<Group> getSelectedGroups() {
Set<Group> set = new LinkedHashSet<Group>();
for (Matcher groupMatcher : globalSettings.getGroupMatchers()) {
for (Map.Entry<String, Group> groupEntry : groups.entrySet()) {
if (groupMatcher.match(groupEntry.getKey())) {
set.add(groupEntry.getValue());
}
}
}
return set;
}
/**
* Get launch points, where each point has a unique process id counting from
* 1. The process id reflects the order of the point to be submitted into
* the execution thread pool.
*
* @param forceRefresh
* false to use cached points if there are, true to always
* reparse the points which is expensive.
* @return the launch points, never null
*/
public synchronized List<LaunchPoint> getLaunchPoints(boolean forceRefresh, long executionId) {
if (forceRefresh || launchPoints == null) {
List<Map<PaxmlResource, List<Settings>>> points = findLaunchPoints();
launchPoints = new Vector<LaunchPoint>();
for (Map<PaxmlResource, List<Settings>> map : points) {
for (Map.Entry<PaxmlResource, List<Settings>> entry : map.entrySet()) {
for (Settings s : entry.getValue()) {
List<Properties> explodedFactors = explodeFactors(s);
if (explodedFactors == null || explodedFactors.size() <= 0) {
launchPoints.add(createLaunchPoint(entry.getKey(), s, null, generateNextPid(), executionId));
} else {
for (Properties factors : explodedFactors) {
launchPoints.add(createLaunchPoint(entry.getKey(), s, factors, generateNextPid(), executionId));
}
}
}
}
}
}
return launchPoints;
}
public static long generateNextPid() {
return PID.getAndIncrement();
}
private LaunchPoint createLaunchPoint(PaxmlResource res, Settings settings, Properties factors, long processId, long executionId) {
Properties props = new Properties();
if (settings != null) {
for (Map.Entry<Object, Object> entry : settings.getProperties().entrySet()) {
String key = entry.getKey().toString();
String value = entry.getValue().toString();
if (settings == globalSettings || !value.equals(globalSettings.getProperties().get(key))) {
props.put(key, value);
}
}
}
return new LaunchPoint(this, res, settings.getGroup(), getGlobalSettings().getProperties(), props, factors, processId, executionId);
}
/**
* Execute a launch point.
*
* @param point
* the launch point
* @return the resource execution result
*/
public Object execute(LaunchPoint point) {
Paxml paxml = new Paxml(point.getProcessId(), point.getExecutionId());
paxml.addStaticConfig(config);
return paxml.execute(point.getResource().getName(), System.getProperties(), point.getEffectiveProperties(false));
}
/**
* Execute a collection of launch points.
*
* @param points
* the points
* @return the list of results corresponding to each launch point.
*/
public List<Object> execute(Collection<LaunchPoint> points) {
List<Object> results = new ArrayList<Object>(points.size());
for (LaunchPoint point : points) {
results.add(execute(point));
}
return results;
}
private void populateResourceMap(Map<PaxmlResource, List<Settings>> map, PaxmlResource res, Group group) {
Settings settings = new Settings(group == null ? "" : group.getId());
Properties properties = settings.getProperties();
properties.putAll(getGlobalSettings().getProperties());
if (group != null) {
properties.putAll(group.getSettings().getProperties());
}
Map<String, Factor> factorMap = settings.getFactors();
// merge the global factors
for (Map.Entry<String, Factor> globalFactorEntry : getGlobalSettings().getFactors().entrySet()) {
final String key = globalFactorEntry.getKey();
Factor factor = factorMap.get(key);
if (factor == null) {
factor = new Factor();
factor.setName(key);
factorMap.put(key, factor);
}
factor.getValues().addAll(globalFactorEntry.getValue().getValues());
}
// copy my own factors
if (group != null) {
factorMap.putAll(group.getSettings().getFactors());
}
List<Settings> list = map.get(res);
if (list == null) {
list = new Vector<Settings>();
map.put(res, list);
}
list.add(settings);
}
private List<Map<PaxmlResource, List<Settings>>> findLaunchPoints() {
List<Map<PaxmlResource, List<Settings>>> result = new ArrayList<Map<PaxmlResource, List<Settings>>>();
// first check all scenarios
Map<PaxmlResource, List<Settings>> singleMap = new LinkedHashMap<PaxmlResource, List<Settings>>();
for (Matcher matcher : globalSettings.getSingleMatchers()) {
for (PaxmlResource selectedResource : config.getResources()) {
if ((matcher.isMatchPath() && matcher.match(selectedResource.getPath()) || (!matcher.isMatchPath() && matcher.match(selectedResource.getName())))) {
populateResourceMap(singleMap, selectedResource, null);
}
}
}
if (!singleMap.isEmpty()) {
result.add(singleMap);
}
// then check all groups
Set<Group> selectedGroups = getSelectedGroups();
for (Group group : selectedGroups) {
Map<PaxmlResource, List<Settings>> map = new LinkedHashMap<PaxmlResource, List<Settings>>();
for (PaxmlResource selectedResource : config.getResources()) {
if (group.matchPath(selectedResource.getPath()) || group.matchName(selectedResource.getName())) {
populateResourceMap(map, selectedResource, group);
}
}
if (!map.isEmpty()) {
result.add(map);
}
}
return result;
}
private static List<Properties> explodeFactors(Settings settings) {
List<String> factorNames = new ArrayList<String>();
List<List<Object>> factors = new ArrayList<List<Object>>();
for (Map.Entry<String, Factor> entry : settings.getFactors().entrySet()) {
factors.add(new ArrayList<Object>(entry.getValue().getValues()));
factorNames.add(entry.getKey());
}
if (factorNames.size() < 1) {
return null;
} else {
List<List<Object>> exploded = new ArrayList<List<Object>>();
for (Object factor : factors.get(0)) {
List<Object> item = new ArrayList<Object>();
item.add(factor);
exploded.add(item);
}
// make more combinations
for (int i = 1; i < factors.size(); i++) {
List<Object> more = factors.get(i);
exploded = combineMoreFactors(exploded, more);
}
List<Properties> result = new ArrayList<Properties>();
for (int i = 0; i < exploded.size(); i++) {
List<Object> combination = exploded.get(i);
Properties map = new Properties();
for (int j = 0; j < factorNames.size(); j++) {
map.put(factorNames.get(j), combination.get(j));
}
result.add(map);
}
return result;
}
}
private static List<List<Object>> combineMoreFactors(List<List<Object>> list, List<Object> more) {
if (more.size() <= 0) {
return list;
}
List<List<Object>> result = new ArrayList<List<Object>>(0);
for (int i = 0; i < more.size(); i++) {
Object m = more.get(i);
for (int j = 0; j < list.size(); j++) {
List<Object> base = list.get(j);
List<Object> newList = new ArrayList<Object>(base);
newList.add(m);
result.add(newList);
}
}
return result;
}
public Resource getResource() {
return resource;
}
public void setResource(Resource resource) {
this.resource = resource;
}
public StaticConfig getConfig() {
return config;
}
public Plan getPlanEntity() {
return planEntity;
}
public void setPlanEntity(Plan planEntity) {
this.planEntity = planEntity;
}
}