/* * Copyright (c) 2013 Data Harmonisation Panel * * All rights reserved. This program and the accompanying materials are made * available under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of the License, * or (at your option) any later version. * * You should have received a copy of the GNU Lesser General Public License * along with this distribution. If not, see <http://www.gnu.org/licenses/>. * * Contributors: * Data Harmonisation Panel <http://www.dhpanel.eu> */ package eu.esdihumboldt.hale.ui.service.values.internal; import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.atomic.AtomicReference; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; import org.eclipse.core.runtime.jobs.Job; import org.eclipse.jface.operation.IRunnableWithProgress; import org.eclipse.ui.PlatformUI; import com.google.common.collect.HashMultiset; import com.google.common.collect.Multiset; import com.google.common.collect.TreeMultiset; import de.fhg.igd.slf4jplus.ALogger; import de.fhg.igd.slf4jplus.ALoggerFactory; import eu.esdihumboldt.hale.common.align.model.AlignmentUtil; import eu.esdihumboldt.hale.common.align.model.impl.PropertyEntityDefinition; import eu.esdihumboldt.hale.common.core.io.Value; import eu.esdihumboldt.hale.common.core.io.project.model.IOConfiguration; import eu.esdihumboldt.hale.common.core.io.project.model.Resource; import eu.esdihumboldt.hale.common.instance.io.InstanceIO; import eu.esdihumboldt.hale.common.instance.model.DataSet; import eu.esdihumboldt.hale.common.instance.model.Instance; import eu.esdihumboldt.hale.common.instance.model.InstanceCollection; import eu.esdihumboldt.hale.common.instance.model.ResourceIterator; import eu.esdihumboldt.hale.common.instance.model.TypeFilter; import eu.esdihumboldt.hale.common.instance.model.impl.MultiInstanceCollection; import eu.esdihumboldt.hale.common.schema.SchemaSpaceID; import eu.esdihumboldt.hale.common.schema.model.constraint.type.Binding; import eu.esdihumboldt.hale.ui.service.instance.InstanceService; import eu.esdihumboldt.hale.ui.service.instance.InstanceServiceAdapter; import eu.esdihumboldt.hale.ui.service.instance.sample.internal.InstanceViewPreferences; import eu.esdihumboldt.hale.ui.service.project.ProjectResourcesUtil; import eu.esdihumboldt.hale.ui.service.project.ProjectService; import eu.esdihumboldt.hale.ui.service.project.ProjectServiceAdapter; import eu.esdihumboldt.hale.ui.service.values.OccurringValues; import eu.esdihumboldt.hale.ui.service.values.OccurringValuesUtil; import eu.esdihumboldt.hale.ui.transformation.TransformDataImportAdvisor; import eu.esdihumboldt.hale.ui.util.io.ThreadProgressMonitor; /** * Service that determines what different values occur for specific * {@link PropertyEntityDefinition}s. * * @author Simon Templer */ public class OccurringValuesServiceImpl extends AbstractOccurringValuesService { private static final ALogger log = ALoggerFactory.getLogger(OccurringValuesServiceImpl.class); /** * Job determining the occurring values for a specific property entity. */ public class OccurringValuesJob extends Job { private final PropertyEntityDefinition property; private final Map<PropertyEntityDefinition, OccurringValuesImpl> values; private final InstanceCollection instances; /** * Create a Job to update the occurring values for the given property * entity. * * @param property the property entity definition * @param values the map to store the updated information in * @param instances the instances to test */ public OccurringValuesJob(PropertyEntityDefinition property, Map<PropertyEntityDefinition, OccurringValuesImpl> values, InstanceCollection instances) { super("Determine occurring values"); this.property = property; this.values = values; this.instances = instances; setUser(true); } @SuppressWarnings("unchecked") @Override protected IStatus run(IProgressMonitor monitor) { // only select instances of the correct type InstanceCollection instances = this.instances .select(new TypeFilter(property.getType())); // and apply an eventual filter if (property.getFilter() != null) { instances = instances.select(property.getFilter()); } boolean instanceProgress = instances.hasSize(); String taskName = "Check instances for occuring values"; if (instanceProgress) { monitor.beginTask(taskName, instances.size()); } else { monitor.beginTask(taskName, IProgressMonitor.UNKNOWN); } // create set to store values @SuppressWarnings("rawtypes") Multiset collectedValues; Class<?> binding = property.getDefinition().getPropertyType() .getConstraint(Binding.class).getBinding(); if (Comparable.class.isAssignableFrom(binding)) { // tree set for sorted values collectedValues = TreeMultiset.create(); } else { // unsorted values collectedValues = HashMultiset.create(); } ResourceIterator<Instance> it = instances.iterator(); try { while (it.hasNext()) { Instance instance = it.next(); AlignmentUtil.addValues(instance, property.getPropertyPath(), collectedValues, true); if (instanceProgress) { // TODO improved monitor update?! monitor.worked(1); } } } finally { it.close(); } synchronized (values) { OccurringValuesImpl ov = new OccurringValuesImpl(collectedValues, property); values.put(property, ov); } notifyOccurringValuesUpdated(property); monitor.done(); return Status.OK_STATUS; } } /** * Values that occur in the source data. */ private final Map<PropertyEntityDefinition, OccurringValuesImpl> sourceValues = new HashMap<PropertyEntityDefinition, OccurringValuesImpl>(); /** * Values that occur in the transformed data. */ private final Map<PropertyEntityDefinition, OccurringValuesImpl> transformedValues = new HashMap<PropertyEntityDefinition, OccurringValuesImpl>(); /** * The service for accessing instances. */ private final InstanceService instances; /** * Create a service instance. * * @param instances the instance service * @param projectService the project service */ public OccurringValuesServiceImpl(InstanceService instances, ProjectService projectService) { super(); // add instance service listener instances.addListener(new InstanceServiceAdapter() { @Override public void datasetChanged(DataSet type) { SchemaSpaceID schemaSpace; switch (type) { case TRANSFORMED: schemaSpace = SchemaSpaceID.TARGET; break; default: schemaSpace = SchemaSpaceID.SOURCE; } invalidateValues(schemaSpace, type); } }); // add project service listener projectService.addListener(new ProjectServiceAdapter() { @Override public void projectSettingChanged(String name, Value value) { if (InstanceViewPreferences.KEY_OCCURRING_VALUES_USE_EXTERNAL.equals(name)) { // invalidate values on setting change invalidateValues(SchemaSpaceID.SOURCE, DataSet.SOURCE); } } }); this.instances = instances; } /** * Invalidate occurring values in the given schema space. * * @param schemaSpace the schema space * @param dataSet the data set */ protected void invalidateValues(SchemaSpaceID schemaSpace, DataSet dataSet) { Map<PropertyEntityDefinition, OccurringValuesImpl> values = selectValues(schemaSpace); boolean empty = instances.getInstances(dataSet).isEmpty(); synchronized (values) { if (empty) { // remove all values values.clear(); } else { // invalidate all values for (OccurringValuesImpl ov : values.values()) { ov.invalidate(); } } } notifyOccurringValuesInvalidated(schemaSpace); } @Override public OccurringValues getOccurringValues(PropertyEntityDefinition property) { return getOccurringValues(property, selectValues(property.getSchemaSpace())); } private Map<PropertyEntityDefinition, OccurringValuesImpl> selectValues( SchemaSpaceID schemaSpace) { switch (schemaSpace) { case SOURCE: return sourceValues; case TARGET: return transformedValues; default: throw new IllegalArgumentException("Illegal schema space specified"); } } /** * Get the values occurring in the data for the given property entity. * * @param property the property entity definition * @param values the map containing the current occurring values * @return the occurring values for the property or <code>null</code> */ private OccurringValues getOccurringValues(PropertyEntityDefinition property, Map<PropertyEntityDefinition, ? extends OccurringValues> values) { synchronized (values) { OccurringValues ov = values.get(property); return ov; } } @Override public boolean updateOccurringValues(PropertyEntityDefinition property) { // sanity check on property if (!OccurringValuesUtil.supportsOccurringValues(property)) { throw new IllegalArgumentException( "Determinining occurring values not supported for given property"); } return updateOccuringValues(property, selectValues(property.getSchemaSpace())); } /** * Update the occurring values for the given property entity. * * @param property the property entity definition * @param values the map containing the current occurring values * @return <code>true</code> if the task to update the information has been * started, <code>false</code> if the information was up-to-date */ private boolean updateOccuringValues(PropertyEntityDefinition property, Map<PropertyEntityDefinition, OccurringValuesImpl> values) { synchronized (values) { OccurringValues ov = values.get(property); if (ov != null && ov.isUpToDate()) { return false; } } // determine occurring values // determine data set DataSet dataSet; switch (property.getSchemaSpace()) { case TARGET: dataSet = DataSet.TRANSFORMED; break; default: dataSet = DataSet.SOURCE; } // determine if external data should be used boolean useExternalData = false; if (dataSet.equals(DataSet.SOURCE)) { ProjectService ps = PlatformUI.getWorkbench().getService(ProjectService.class); useExternalData = InstanceViewPreferences .occurringValuesUseExternalData(ps.getConfigurationService()); } InstanceCollection collection; if (!useExternalData) { collection = instances.getInstances(dataSet); } else { // use complete project data sources final AtomicReference<InstanceCollection> source = new AtomicReference<>(); IRunnableWithProgress op = new IRunnableWithProgress() { @Override public void run(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException { ProjectService ps = PlatformUI.getWorkbench().getService(ProjectService.class); List<InstanceCollection> sources = new ArrayList<>(); for (Resource resource : ps.getResources()) { if (InstanceIO.ACTION_LOAD_SOURCE_DATA.equals(resource.getActionId())) { // resource is source data IOConfiguration conf = resource.copyConfiguration(true); TransformDataImportAdvisor advisor = new TransformDataImportAdvisor(); ProjectResourcesUtil.executeConfiguration(conf, advisor, false, null); if (advisor.getInstances() != null) { sources.add(advisor.getInstances()); } } } source.set(new MultiInstanceCollection(sources)); } }; try { ThreadProgressMonitor.runWithProgressDialog(op, false); collection = source.get(); } catch (Exception e) { log.error("Error initializing data sources", e); return true; } } // go through instances to determine occurring values Job job = new OccurringValuesJob(property, values, collection); job.schedule(); return true; } }