/******************************************************************************* * Copyright 2016 * Ubiquitous Knowledge Processing (UKP) Lab * Technische Universität Darmstadt * * 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 org.dkpro.lab.task.impl; import static org.dkpro.lab.storage.StorageService.CONTEXT_ID_SCHEME; import static org.dkpro.lab.storage.StorageService.LATEST_CONTEXT_SCHEME; import static org.dkpro.lab.storage.filesystem.FileSystemStorageService.isStaticImport; import java.io.File; import java.io.IOException; import java.lang.annotation.Annotation; import java.lang.reflect.Field; import java.net.URI; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import javax.ws.rs.core.UriBuilder; import org.apache.commons.lang.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.dkpro.lab.Util; import org.dkpro.lab.conversion.ConversionService; import org.dkpro.lab.engine.TaskContext; import org.dkpro.lab.reporting.Report; import org.dkpro.lab.storage.StorageService; import org.dkpro.lab.storage.impl.PropertiesAdapter; import org.dkpro.lab.task.Discriminator; import org.dkpro.lab.task.Property; import org.dkpro.lab.task.Task; import org.dkpro.lab.task.TaskContextMetadata; public class TaskBase implements Task { private final Log log = LogFactory.getLog(getClass()); private String type; private Map<String, String> imports; private Map<String, String> attributes; private Map<String, String> discriminators; private Map<String, String> analyzedAttributes; private Map<String, String> analyzedDiscriminators; private List<Class<? extends Report>> reports; private boolean initialized = false; private boolean didRun = false; private TaskContext aContext; { attributes = new HashMap<String, String>(); analyzedAttributes = new HashMap<String, String>(); discriminators = new HashMap<String, String>(); analyzedDiscriminators = new HashMap<String, String>(); reports = new ArrayList<Class<? extends Report>>(); imports = new HashMap<String, String>(); } /** * Create a new task with a default type name. The type is the simple name of the class. If * it is an inner class, only the name of the inner class is used. Be careful that type names * must uniquely identify a task class. */ public TaskBase() { String className = getClass().getName(); int innerClassSep = className.lastIndexOf('$'); if (innerClassSep != 1) { String innerName = className.substring(innerClassSep+1); if (!StringUtils.isNumeric(innerName)) { className = innerName; } } setType(className); } public TaskBase(String aType) { setType(aType); } @Override public void initialize(TaskContext aContext) { this.aContext = aContext; initialized = true; } @Override public boolean isInitialized() { return initialized; } @Override public final void analyze() { analyzedAttributes = new HashMap<String, String>(); analyzedDiscriminators = new HashMap<String, String>(); analyze(getClass(), Property.class, analyzedAttributes); analyze(getClass(), Discriminator.class, analyzedDiscriminators); } @Override public void destroy(TaskContext aContext) { initialized = false; } public void setType(String aType) { if (aType == null) { throw new IllegalArgumentException("Must specify a type"); } type = aType; } @Override public String getType() { return type; } @Override public void setAttribute(String aKey, String aValue) { if(didTaskRun()){ throw new IllegalStateException("[An already executed Task cannot be modified]"); } if (aKey == null) { throw new IllegalArgumentException("Must specify a key"); } if (aValue == null) { attributes.remove(aKey); } else { attributes.put(aKey, aValue); } } @Override public String getAttribute(String aKey) { String value = analyzedAttributes.get(aKey); if(value != null){ return value; } return attributes.get(aKey); } @Override public Map<String, String> getAttributes() { Map<String, String> allAttributes = new HashMap<>(); allAttributes.putAll(attributes); allAttributes.putAll(analyzedAttributes); return allAttributes; } @Override public void setDescriminator(String aKey, String aValue) { if(didTaskRun()){ throw new IllegalStateException("[An already executed Task cannot be modified]"); } if (aKey == null) { throw new IllegalArgumentException("Must specify a key"); } if (aValue == null) { discriminators.remove(aKey); } else { discriminators.put(aKey, aValue); } } @Override public String getDescriminator(String aKey) { String value = analyzedDiscriminators.get(aKey); if(value != null){ return value; } return discriminators.get(aKey); } @Override public Map<String, String> getDescriminators() { Map<String,String> allDiscriminators = new HashMap<>(); allDiscriminators.putAll(discriminators); allDiscriminators.putAll(analyzedDiscriminators); return allDiscriminators; } @Override public Map<String, String> getResolvedDescriminators(TaskContext aContext) { StorageService storageService = aContext.getStorageService(); Map<String, String> descs = new HashMap<String, String>(); descs.putAll(getDescriminators()); // Load previous discriminators and check that the do not conflict with discriminators // defined in this task for (String rawUri : aContext.getMetadata().getImports().values()) { URI uri = URI.create(rawUri); if (isStaticImport(uri)) { continue; } final TaskContextMetadata meta = aContext.resolve(uri); Map<String, String> prerequisiteDiscriminators = storageService.retrieveBinary( meta.getId(), DISCRIMINATORS_KEY, new PropertiesAdapter()).getMap(); for (Entry<String, String> e : prerequisiteDiscriminators.entrySet()) { if (descs.containsKey(e.getKey()) && !descs.get(e.getKey()).equals(e.getValue())) { throw new IllegalStateException("Discriminator [" + e.getKey() + "] in task [" + getType() + "] conflicts with dependency [" + meta.getType() + "]"); } descs.put(e.getKey(), e.getValue()); } } return descs; } @Deprecated @Override public void addImport(String aKey, String aUri) { if (aKey == null) { throw new IllegalArgumentException("Must specify a key"); } if (aUri == null) { throw new IllegalArgumentException("Must specify a URI"); } imports.put(aKey, aUri); } @Deprecated @Override public void addImportById(String aKey, String aUuid, String aSourceKey) { if (aKey == null) { throw new IllegalArgumentException("Must specify a key"); } if (aSourceKey == null) { throw new IllegalArgumentException("Must specify a source key"); } if (aUuid == null) { throw new IllegalArgumentException("Must specify a task id"); } imports.put(aKey, CONTEXT_ID_SCHEME+"://"+aUuid+"/"+aSourceKey); } @Deprecated @Override public void addImportLatest(String aKey, String aSourceKey, String aType) { if (aKey == null) { throw new IllegalArgumentException("Must specify a key"); } if (aSourceKey == null) { throw new IllegalArgumentException("Must specify a source key"); } if (aType == null) { throw new IllegalArgumentException("Must specify a type"); } imports.put(aKey, LATEST_CONTEXT_SCHEME+"://"+aType+"/"+aSourceKey); } @Deprecated @Override public void addImportLatest(String aKey, String aSourceKey, String aType, String... aConstraints) { if (aKey == null) { throw new IllegalArgumentException("Must specify a key"); } if (aSourceKey == null) { throw new IllegalArgumentException("Must specify a source key"); } if (aType == null) { throw new IllegalArgumentException("Must specify a type"); } if ((aConstraints.length % 2) != 0) { throw new IllegalArgumentException("Restrictions must be key/value pairs and " + "therefore have be represented by an even number of parameters"); } UriBuilder ub = UriBuilder.fromUri(LATEST_CONTEXT_SCHEME+"://"+aType+"/"+aSourceKey); for (int i = 0; i < aConstraints.length; i += 2) { String key = aConstraints[i]; String value = aConstraints[i+1]; ub.queryParam(key, value); } imports.put(aKey, ub.build().toString()); } @Deprecated @Override public void addImportLatest(String aKey, String aSourceKey, String aType, Map<String, String> aRestrictions) { int i = 0; String[] constraints = new String[aRestrictions.size()*2]; for (Entry<String, String> e : aRestrictions.entrySet()) { constraints[i++] = e.getKey(); constraints[i++] = e.getValue(); } addImportLatest(aKey, aSourceKey, aType, constraints); } @Override public void addImport(File aFile, String aKey) { addImport(aFile.toURI(), aKey); } @Override public void addImport(URI aUri, String aKey) { addImport(aKey, aUri.toString()); } @Override public void addImport(Task aTask, String aKey) { addImport(aTask, aKey, aKey); } @Override public void addImport(Task aTask, String aKey, String aAlias) { addImportLatest(aAlias, aKey, aTask.getType()); } @Override public void addImport(TaskContext aTaskContext, String aKey, String aAlias) { addImportById(aAlias, aTaskContext.getId(), aKey); } @Override public Map<String, String> getImports() { return imports; } @Override public void addReport(Class<? extends Report> aReport) { if (aReport == null) { throw new IllegalArgumentException("Report class cannot be null."); } reports.add(aReport); } @Override public void removeReport(Class<? extends Report> aReport) { reports.remove(aReport); } public void setReports(List<Class<? extends Report>> aReports) { reports = new ArrayList<Class<? extends Report>>(aReports); } @Override public List<Class<? extends Report>> getReports() { return reports; } @Override public void persist(final TaskContext aContext) throws IOException { if (!initialized) { throw new IllegalStateException( "Task not initialized. Maybe forgot to call super.initialize(ctx) in [" + getClass().getName() + "]?"); } aContext.storeBinary(ATTRIBUTES_KEY, new PropertiesAdapter(getAttributes(), "Task properties")); aContext.storeBinary(DISCRIMINATORS_KEY, new PropertiesAdapter(getResolvedDescriminators(aContext))); } protected void analyze(Class<?> aClazz, Class<? extends Annotation> aAnnotation, Map<String, String> props) { if (aClazz.getSuperclass() != null) { analyze(aClazz.getSuperclass(), aAnnotation, props); } for (Field field : aClazz.getDeclaredFields()) { field.setAccessible(true); try { if (field.isAnnotationPresent(aAnnotation)) { String name; Annotation annotation = field.getAnnotation(aAnnotation); if (StringUtils.isNotBlank(ParameterUtil.getName(annotation))) { name = getClass().getName() + "|" + ParameterUtil.getName(annotation); } else { name = getClass().getName() + "|" + field.getName(); } String value = Util.toString(field.get(this)); String oldValue = props.put(name, value); if (oldValue != null) { throw new IllegalStateException( "Discriminator/property name must be unique and cannot be used " + "on multiple fields in the same class [" + name + "]"); } //Override with conversion service information if available Object object = field.get(this); ConversionService cs = aContext.getConversionService(); if(cs.isRegistered(object)){ props.put(name, cs.getDiscriminableValue(object)); } log.debug("Found "+aAnnotation.getSimpleName()+" ["+name+"]: "+value); } } catch (IllegalAccessException e) { throw new IllegalStateException(e); } finally { field.setAccessible(false); } } } @Override public void markExecuted() { didRun = true; } @Override public boolean didTaskRun() { return didRun; } }