Package org.cishell.reference.service.conversion

Source Code of org.cishell.reference.service.conversion.DataConversionServiceImpl

/* ****************************************************************************
* CIShell: Cyberinfrastructure Shell, An Algorithm Integration Framework.
*
* All rights reserved. This program and the accompanying materials are made
* available under the terms of the Apache License v2.0 which accompanies
* this distribution, and is available at:
* http://www.apache.org/licenses/LICENSE-2.0.html
*
* Created on Jun 15, 2006 at Indiana University.
*
* Contributors:
*     Indiana University -
* ***************************************************************************/
package org.cishell.reference.service.conversion;

import java.io.File;
import java.util.AbstractList;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;

import org.cishell.framework.CIShellContext;
import org.cishell.framework.algorithm.AlgorithmFactory;
import org.cishell.framework.algorithm.AlgorithmProperty;
import org.cishell.framework.data.Data;
import org.cishell.service.conversion.ConversionException;
import org.cishell.service.conversion.Converter;
import org.cishell.service.conversion.DataConversionService;
import org.osgi.framework.BundleContext;
import org.osgi.framework.Constants;
import org.osgi.framework.InvalidSyntaxException;
import org.osgi.framework.ServiceEvent;
import org.osgi.framework.ServiceListener;
import org.osgi.framework.ServiceReference;
import org.osgi.service.log.LogService;

import com.google.common.collect.Lists;
import com.google.common.collect.Sets;

import edu.uci.ics.jung.algorithms.shortestpath.DijkstraShortestPath;
import edu.uci.ics.jung.graph.Edge;
import edu.uci.ics.jung.graph.Graph;
import edu.uci.ics.jung.graph.Vertex;
import edu.uci.ics.jung.graph.impl.DirectedSparseEdge;
import edu.uci.ics.jung.graph.impl.DirectedSparseGraph;
import edu.uci.ics.jung.graph.impl.SparseVertex;
import edu.uci.ics.jung.io.GraphMLFile;
import edu.uci.ics.jung.utils.UserDataContainer;

/**
* Builds converter chains from one data type to another
*
* @author Bruce Herr
* @author Ben Markines
*
*/
public class DataConversionServiceImpl implements DataConversionService, AlgorithmProperty, ServiceListener {
  public final static String SERVICE_LIST = "SERVICE_LIST";

  private BundleContext bContext;
  private CIShellContext ciContext;
  private Map<String,Vertex> dataTypeToVertex;
  // TODO: upgrade Jung, use new Graph<V,E>
  private Graph graph;

  /**
   * Set up to listen for service requests and initial set up of the graph
   *
   * @param bContext Current bundle context
   * @param ciContext Current CIShell context
   */
  public DataConversionServiceImpl(BundleContext bContext, CIShellContext ciContext) {
    this.bContext = bContext;
    this.ciContext = ciContext;

    this.graph = new DirectedSparseGraph();
    this.dataTypeToVertex = new HashMap<String,Vertex>();

    String filter = "(&("+ALGORITHM_TYPE+"="+TYPE_CONVERTER+")" +
        "("+IN_DATA+"=*) " +
        "("+OUT_DATA+"=*)" +
        "(!("+REMOTE+"=*))" +
        "(!("+IN_DATA+"=file-ext:*))" +
        "(!("+OUT_DATA+"=file-ext:*)))";

    try {
      this.bContext.addServiceListener(this, filter);
    } catch (InvalidSyntaxException e) {
      e.printStackTrace();
    }
    assembleGraph();
  }

  /**
   * Assemble the directed graph of converters.  Currently unweighted.
   */
  private void assembleGraph() {
    try {
      String filter = "(&("+ALGORITHM_TYPE+"="+TYPE_CONVERTER+")" +
          "("+IN_DATA+"=*) " +
          "("+OUT_DATA+"=*)" +
          "(!("+REMOTE+"=*))" +
          "(!("+IN_DATA+"=file-ext:*))" +
          "(!("+OUT_DATA+"=file-ext:*)))";

      Collection<ServiceReference<AlgorithmFactory>> refs = getAFServiceReferences(filter);

      if (refs != null) {
        for (ServiceReference<AlgorithmFactory> ref : refs) {
          String inData = (String) ref
              .getProperty(AlgorithmProperty.IN_DATA);
          String outData = (String) ref
              .getProperty(AlgorithmProperty.OUT_DATA);

          addServiceReference(inData, outData, ref);
        }
      }
    } catch (InvalidSyntaxException e) {
      throw new RuntimeException(e);
    }
  }

  /**
   * Get the converter chains for incoming and outgoing format
   *
   * @param inFormat The format to convert from
   * @param outFormat The format to convert to
   */
  public Converter[] findConverters(String inFormat, String outFormat) {
    //      saveGraph();
    if (inFormat != null && inFormat.length() > 0
        && outFormat != null && outFormat.length() > 0) {

      ConverterImpl[] converters = null;

      if (outFormat.startsWith("file-ext:")) {
        converters = getConvertersByWildcard(inFormat, "file:*");
        converters = connectValidator(converters, outFormat);
      } else {
        converters = getConvertersByWildcard(inFormat, outFormat);
      }

      return converters;
    }
    return new Converter[0];
  }

  /**
   * If the final format is of type file-ext, then append the final converter
   * to the converter list
   *
   * @param converters Current converter chains
   * @param outFormat Final data type
   * @return The edited converter chains
   */
  private ConverterImpl[] connectValidator(ConverterImpl[] converters,
      String outFormat) {
    Collection<ConverterImpl> newConverters = Sets.newHashSet();

    Set<String> formats = Sets.newHashSet();
    for (int i = 0; i < converters.length; i++) {
      String format = (String) converters[i].getProperties().get(OUT_DATA);

      if (!formats.contains(format)) {
        String filter = "(&("+ALGORITHM_TYPE+"="+TYPE_VALIDATOR+")" +
            "(!("+REMOTE+"=*))" +
            "("+IN_DATA+"="+format+")" +
            "("+OUT_DATA+"="+outFormat+"))";

        try {
          Collection<ServiceReference<AlgorithmFactory>> refs =
              getAFServiceReferences(filter);

          if (refs != null && refs.size() > 0) {
            for (ServiceReference<AlgorithmFactory> ref : refs) {
              List<ServiceReference<AlgorithmFactory>> chain =
                  Lists.newArrayList(converters[i].getConverterList());
              chain.add(ref);

              newConverters.add(ConverterImpl.createConverter(bContext, ciContext,
                  chain));
            }

            formats.add(format);
          }
        } catch (InvalidSyntaxException e) {
          e.printStackTrace();
        }
      }
    }

    return newConverters.toArray(new ConverterImpl[0]);
  }

  private Collection<ServiceReference<AlgorithmFactory>> getAFServiceReferences(String filter) throws InvalidSyntaxException {
    List<ServiceReference<AlgorithmFactory>> refList = Lists.newArrayList();

    // We're guaranteed to get a ServiceReference<AlgorithmFactory>[] back, because
    // we pass the AlgorithmFactory class name into the method.
    // When we switch to a new version of OSGi, this ugliness can be replaced with the
    // a call to bContext.getServiceReferences(Class, String).
    @SuppressWarnings("unchecked")
    ServiceReference<AlgorithmFactory>[] refArray =
        (ServiceReference<AlgorithmFactory>[]) bContext.getServiceReferences(
            AlgorithmFactory.class.getName(),
            filter);

    if (refArray != null && refArray.length > 0) {
      refList.addAll((Collection<? extends ServiceReference<AlgorithmFactory>>) Arrays.asList(refArray));
    }

    return refList;
  }

  /**
   * Finds data types that match a given wildcard, using a particular algorithm property.
   *
   * @param formatString
   * @param algorithmProperty - one of AlgorithmProperties.IN_DATA or AlgorithmProperties.OUT_DATA
   * @return
   */
  private Set<String> resolveDataWildcard(String formatString, String algorithmProperty) {
    Set<String> expansions = new HashSet<String>();
    if (! formatString.contains("*")) {
      expansions.add(formatString);
      return expansions;
    }
   
    String algorithmFilter;
    if (algorithmProperty.equals(AlgorithmProperty.IN_DATA)) {
      algorithmFilter = createConverterFilterForInFormat(formatString);
    } else if (algorithmProperty.equals(AlgorithmProperty.OUT_DATA)) {
      algorithmFilter = createConverterFilterForOutFormat(formatString);
    } else {
      throw new IllegalArgumentException(String.format(
          "Got algorithm property %s, expected one of AlgorithmProperty.{IN,OUT}_DATA",
          algorithmProperty));
    }
   
   
    try {
      Collection<ServiceReference<AlgorithmFactory>> matches
        = getAFServiceReferences(algorithmFilter);
      for (ServiceReference<AlgorithmFactory> match : matches) {
        expansions.add((String) match.getProperty(algorithmProperty));
      }
    } catch (InvalidSyntaxException e) {
      getLogger().log(LogService.LOG_WARNING, String.format("Error while looking for %s: %s", formatString, e.getMessage()));
    }
   
   
    return expansions;
  }
 
  private LogService getLogger() {
    return (LogService) ciContext.getService(LogService.class.getName());
  }

  /**
   * Build the converter chains.  The data formats may include wildcards.
   * @param inFormat The original data type
   * @param outFormat The target data type
   * @return Converter chains
   * @see org.cishell.service.conversion.DataConversionService#findConverters(java.lang.String, java.lang.String)
   */
  private ConverterImpl[] getConvertersByWildcard(String inFormat, String outFormat) {
    Set<String>  matchingInFileTypes = resolveDataWildcard(inFormat,  AlgorithmProperty.IN_DATA),
          matchingOutFileTypes = resolveDataWildcard(outFormat, AlgorithmProperty.OUT_DATA);
    Set<ConverterImpl> possibleConverters = new HashSet<ConverterImpl>();

    //Check to see if inFormat matches the outFormat (for example:
    //in=file:text/graphml out=file:* If so, then add a null converter
    //to the converterList.
    if (outFormat.contains("*")) {
      String outFormatRegex = outFormat.replaceAll("[*]", ".*");
      if (Pattern.matches(outFormatRegex, inFormat)) {
        possibleConverters.add(ConverterImpl.createNoOpConverter(bContext, ciContext, inFormat));
      }
    }
   
    for (String srcDataType : matchingInFileTypes) {
      for (String destDataType : matchingOutFileTypes) {
        ConverterImpl converter =
            getConverter(srcDataType, destDataType);
        if (converter != null) {
          possibleConverters.add(converter);
        }
      }
    }
    return possibleConverters.toArray(new ConverterImpl[0]);
  }

  private String createConverterFilterForOutFormat(String outFormat) {
    String outFilter = "(&(" + ALGORITHM_TYPE + "=" + TYPE_CONVERTER + ")"
        + "("+IN_DATA+"=*) " + "("+OUT_DATA+"="+outFormat+")"
        + "(!(" + REMOTE + "=*)))";
    return outFilter;
  }

  private String createConverterFilterForInFormat(String inFormat) {
    String inFilter = "(&(" + ALGORITHM_TYPE + "=" + TYPE_CONVERTER + ")"
        + "("+IN_DATA+"="+inFormat+") " + "("+OUT_DATA+"=*)" +
        "(!("+IN_DATA+"=file-ext:*))" + "(!(" + REMOTE + "=*)))";
    return inFilter;
  }

  /**
   * Get the shortest converter path.  This returns a single converter path
   * @param inType The source data type
   * @param outType The target data type
   * @return Single converter path, or null if no path can be found
   */
  private ConverterImpl getConverter(String inType, String outType) {
    Vertex sourceVertex = dataTypeToVertex.get(inType);
    Vertex targetVertex = dataTypeToVertex.get(outType);

    if (sourceVertex != null && targetVertex != null) {
      DijkstraShortestPath shortestPathAlg =
          new DijkstraShortestPath(graph);
     
      // Documentation of DijkstraShortestPath indicates that this returns a list of Edge.
      @SuppressWarnings("unchecked")
      List<Edge> edgeList = shortestPathAlg.getPath(sourceVertex, targetVertex);

      if (edgeList.size() > 0) {
        List<ServiceReference<AlgorithmFactory>> serviceReferences = Lists.newArrayList();
        for (Edge edge : edgeList) {
          AbstractList converterList =
              (AbstractList) edge.getUserDatum(SERVICE_LIST);
          serviceReferences.add((ServiceReference) converterList.get(0));
        }

        return ConverterImpl.createConverter(bContext, ciContext, serviceReferences);
      }
    }

    return null;
  }

  /**
   * Builds the converter chains using data as the source
   * @param data The source data to convert from
   * @param outFormat the final data type
   * @return Converter chains
   * @see org.cishell.service.conversion.DataConversionService#findConverters(org.cishell.framework.data.Data, java.lang.String)
   */
  public Converter[] findConverters(Data data, String outFormat) {
    if (data == null) {
      if (NULL_DATA.equalsIgnoreCase(""+outFormat)) {
        return new Converter[]{ConverterImpl.createNoOpConverter(bContext, ciContext, outFormat)};
      } else {
        return new Converter[0];
      }
    }

    String format = data.getFormat();

    Set set = new HashSet();
    Converter[] converters = new Converter[0];
    if (format != null) {
      converters = findConverters(format, outFormat);
      set.addAll(new HashSet(Arrays.asList(converters)));
    }
    if (!(data.getData() instanceof File) && data.getData() != null) {
      Class dataClass = data.getData().getClass();
      for (Iterator it = getClassesFor(dataClass).iterator();
          it.hasNext();) {
        Class c = (Class) it.next();
        converters = findConverters(c.getName(), outFormat);
        set.addAll(new HashSet(Arrays.asList(converters)));
      }
    }

    return (Converter[]) set.toArray(new Converter[0]);
  }

  /**
   * Get all the classes implemented and extended
   * @param clazz The class to query
   * @return Interfaces and base classes
   */
  protected Collection getClassesFor(Class clazz) {
    Set classes = new HashSet();

    Class[] c = clazz.getInterfaces();
    for (int i=0; i < c.length; i++) {
      classes.addAll(getClassesFor(c[i]));
    }

    Class superC = clazz.getSuperclass();

    if (superC != Object.class) {
      if (superC != null)
        classes.addAll(getClassesFor(superC));
    } else {
      classes.add(superC);
    }

    classes.add(clazz);

    return classes;
  }

  /**
   * Convert the Data to a format
   * @param inDM Data type to convert
   * @param outFormat The data type to convert
   * @return The final data type
   * @see org.cishell.service.conversion.DataConversionService#convert(org.cishell.framework.data.Data, java.lang.String)
   */
  public Data convert(Data inDM, String outFormat) throws ConversionException {
    String inFormat = inDM.getFormat();

    if (inFormat != null && inFormat.equals(outFormat)) {
      return inDM;
    }

    Converter[] converters = findConverters(inDM, outFormat);
    if (converters.length > 0) {
      inDM = converters[0].convert(inDM);
    }

    return inDM;
  }

  /**
   * Change service reference in the graph
   * @param event The service that changed
   */
  public void serviceChanged(ServiceEvent event) {
    ServiceReference<?> inServiceRef = event.getServiceReference();

    String inDataType =
        (String) inServiceRef.getProperty(AlgorithmProperty.IN_DATA);
    String outDataType =
        (String) inServiceRef.getProperty(AlgorithmProperty.OUT_DATA);

    if (event.getType() ==  ServiceEvent.MODIFIED) {
      removeServiceReference(inDataType, outDataType, inServiceRef);
      addServiceReference(inDataType, outDataType, inServiceRef);
    }
    else if(event.getType() == ServiceEvent.REGISTERED) {
      addServiceReference(inDataType, outDataType, inServiceRef);
    }
    else if(event.getType() == ServiceEvent.UNREGISTERING) {
      removeServiceReference(inDataType, outDataType, inServiceRef);
    }
  }

  /**
   * Remove a service reference in the graph
   * @param sourceDataType The source data type of the serviceReference to remove
   * @param targetDataType The target data type of the serviceReference to remove
   * @param serviceReference The serviceReference to remove
   */
  private void removeServiceReference(String sourceDataType,
      String targetDataType,
      ServiceReference<?> serviceReference) {
    if (sourceDataType != null && targetDataType != null) {
      Vertex sourceVertex = (Vertex) dataTypeToVertex.get(sourceDataType);
      Vertex targetVertex = (Vertex) dataTypeToVertex.get(targetDataType);
      String pid =
          (String) serviceReference.getProperty(Constants.SERVICE_PID);

      if (sourceVertex != null && targetVertex != null) {
        Edge edge = sourceVertex.findEdge(targetVertex);
        if (edge != null) {
          AbstractList serviceList =
              (AbstractList) edge.getUserDatum(SERVICE_LIST);
          for (Iterator refs = serviceList.iterator(); refs.hasNext();) {
            ServiceReference currentServiceReference =
                (ServiceReference) refs.next();
            String currentPid =
                (String) currentServiceReference
                .getProperty(Constants.SERVICE_PID);

            if (pid.equals(currentPid)) {
              refs.remove();
            }
          }
          if (serviceList.isEmpty()) {
            graph.removeEdge(edge);
          }
        }
      }
    }
  }

  /**
   * Add service reference to the graph
   * @param sourceDataType The source data type
   * @param targetDataType The target data type
   * @param serviceReference The service reference to add
   */
  private void addServiceReference(String sourceDataType,
      String targetDataType,
      ServiceReference<?> serviceReference) {
    if (sourceDataType != null && sourceDataType.length() > 0
        && targetDataType != null && targetDataType.length() > 0) {
      Vertex sourceVertex = getVertex(sourceDataType);
      Vertex targetVertex = getVertex(targetDataType);

      removeServiceReference(
          sourceDataType,  targetDataType, serviceReference);

      Edge directedEdge = sourceVertex.findEdge(targetVertex);
      if (directedEdge == null) {
        directedEdge =
            new DirectedSparseEdge(sourceVertex, targetVertex);
        graph.addEdge(directedEdge);
      }

      AbstractList serviceList =
          (AbstractList) directedEdge.getUserDatum(SERVICE_LIST);
      if (serviceList == null) {
        serviceList = new ArrayList();
        serviceList.add(serviceReference);
      }
      directedEdge.setUserDatum(SERVICE_LIST, serviceList,
          new UserDataContainer.CopyAction.Shared());
    }
  }

  /**
   * Get the vertex in the graph given a data type.  Creates a new vertex
   * if one does not exist
   * @param dataType Datatype representing the node
   * @return The vertex
   */
  private Vertex getVertex(String dataType) {
    Vertex vertex = (SparseVertex) dataTypeToVertex.get(dataType);
    if (vertex== null) {
      vertex = new SparseVertex();
      vertex.addUserDatum("label",
          dataType,
          new UserDataContainer.CopyAction.Shared());
      graph.addVertex(vertex);
      dataTypeToVertex.put(dataType, vertex);
    }
    return vertex;
  }

  /**
   * Save the current converter graph to the user's home directory

   */
  private void saveGraph() {
    GraphMLFile writer = new GraphMLFile();
    Graph g = (Graph) graph.copy();
    for (Iterator edges = g.getEdges().iterator(); edges.hasNext();) {
      Edge e = (Edge) edges.next();
      e.removeUserDatum(SERVICE_LIST);
    }

    writer.save(g, System.getProperty("user.home")
        + File.separator
        + "convertGraph.xml");
  }
}
TOP

Related Classes of org.cishell.reference.service.conversion.DataConversionServiceImpl

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.