package org.jboss.windup.rules.apps.javaee.model.stats; import com.tinkerpop.blueprints.Vertex; import org.apache.commons.lang3.StringUtils; import org.jboss.windup.graph.GraphContext; import org.jboss.windup.graph.model.BelongsToProject; import org.jboss.windup.graph.model.ProjectModel; import org.jboss.windup.graph.model.resource.FileModel; import org.jboss.windup.graph.service.GraphService; import com.tinkerpop.frames.FramedGraphQuery; import com.tinkerpop.pipes.filter.BackFilterPipe; import com.tinkerpop.pipes.transform.OutPipe; import com.tinkerpop.pipes.util.Pipeline; import com.tinkerpop.pipes.util.StartPipe; import java.io.Serializable; import java.util.Date; import java.util.HashMap; import java.util.Map; import java.util.logging.Logger; import java.util.stream.StreamSupport; import org.apache.commons.collections4.CollectionUtils; import org.jboss.windup.graph.frames.TypeAwareFramedGraphQuery; import org.jboss.windup.graph.model.WindupVertexFrame; import org.jboss.windup.rules.apps.java.archives.model.IdentifiedArchiveModel; import org.jboss.windup.rules.apps.java.model.JarArchiveModel; import org.jboss.windup.rules.apps.java.model.JavaClassModel; import org.jboss.windup.rules.apps.javaee.model.DataSourceModel; import org.jboss.windup.rules.apps.javaee.model.EjbBeanBaseModel; import org.jboss.windup.rules.apps.javaee.model.EjbEntityBeanModel; import org.jboss.windup.rules.apps.javaee.model.EjbMessageDrivenModel; import org.jboss.windup.rules.apps.javaee.model.EjbSessionBeanModel; import org.jboss.windup.rules.apps.javaee.model.JNDIResourceModel; import org.jboss.windup.rules.apps.javaee.model.JPAEntityModel; import org.jboss.windup.rules.apps.javaee.model.JPANamedQueryModel; import org.jboss.windup.rules.apps.javaee.model.JPAPersistenceUnitModel; import org.jboss.windup.rules.apps.javaee.model.JaxRSWebServiceModel; import org.jboss.windup.rules.apps.javaee.model.JaxWSWebServiceModel; import org.jboss.windup.rules.apps.javaee.model.JmsConnectionFactoryModel; import org.jboss.windup.rules.apps.javaee.model.JmsDestinationModel; import org.jboss.windup.rules.apps.javaee.model.JmsDestinationType; import org.jboss.windup.rules.apps.javaee.model.RMIServiceModel; import org.jboss.windup.rules.apps.javaee.model.HibernateConfigurationFileModel; import org.jboss.windup.rules.apps.javaee.model.HibernateEntityModel; import org.jboss.windup.rules.apps.javaee.model.HibernateMappingFileModel; import org.jboss.windup.rules.apps.javaee.model.HibernateSessionFactoryModel; import org.jboss.windup.rules.apps.javaee.service.TechnologyKeyValuePairModelService; import org.jboss.windup.util.Logging; /** * Functionality for the Technologies Report. * * @author <a href="mailto:zizka@seznam.cz">Ondrej Zizka</a> * @author <a href="mailto:dklingenberg@gmail.com">David Klingenberg</a> */ public class TechnologiesStatsService extends GraphService<TechnologiesStatsModel> { private final static Logger LOG = Logging.get(TechnologiesStatsService.class); private TechnologyKeyValuePairModelService technologyKeyValuePairModelService; public TechnologiesStatsService(GraphContext context) { super(context, TechnologiesStatsModel.class); this.technologyKeyValuePairModelService = new TechnologyKeyValuePairModelService(context); } protected void setCountFilesByType(TechnologiesStatsModel stats, Map<String, Integer> suffixToCount) { if (suffixToCount == null || suffixToCount.isEmpty()) { return; } // TODO: This will need to filter out archives. suffixToCount.entrySet().forEach(entry -> { TechnologyKeyValuePairModel suffixUsage = this.technologyKeyValuePairModelService.create() .setName(entry.getKey()) .setValue(entry.getValue()); stats.addFileType(suffixUsage); }); } protected void setTechnologiesUsage(TechnologiesStatsModel stats, Map<String, Integer> technologyUsage) { if (technologyUsage == null || technologyUsage.isEmpty()) { return; } technologyUsage.entrySet().forEach(entry -> { TechnologyKeyValuePairModel currentTechnology = this.technologyKeyValuePairModelService.create() .setName(entry.getKey()) .setValue(entry.getValue()); stats.addTechnology(currentTechnology); }); } /** * Compute the stats for this execution. */ public TechnologiesStatsModel computeStats(Map<String, Integer> suffixToCount, Map<String, Integer> technologyUsage) { TechnologiesStatsModel stats = this.create(); stats.setComputed(new Date()); this.setCountFilesByType(stats, suffixToCount); this.setTechnologiesUsage(stats, technologyUsage); this.commit(); return stats; } public Map<ProjectModel, Map<String, Integer>> countTechnologiesUsage() { Map<String, Map<ProjectModel, Integer>> technologyUsage = new HashMap<>(); technologyUsage.put(TechnologiesStatsModel.STATS_SERVICES_EJB_STATELESS, this.countByType(EjbSessionBeanModel.class, EjbBeanBaseModel.SESSION_TYPE, "stateless")); technologyUsage.put(TechnologiesStatsModel.STATS_SERVICES_EJB_STATEFUL, this.countByType(EjbSessionBeanModel.class, EjbBeanBaseModel.SESSION_TYPE, "stateful")); technologyUsage.put(TechnologiesStatsModel.STATS_SERVICES_EJB_MESSAGEDRIVEN, this.countByType(EjbMessageDrivenModel.class)); // TODO: stats.setStatsServicesEjb___(item(countByType(EjbDeploymentDescriptorModel.class))); // Amounts // For the commented, we don't have a graph representation. Map<ProjectModel, Integer> count = this.sum( this.countByType(EjbEntityBeanModel.class), this.countByType(JPAEntityModel.class) ); // PersistenceEntityModel covers also HibernateEntityModel. technologyUsage.put(TechnologiesStatsModel.STATS_SERVICES_JPA_ENTITITES, count); technologyUsage.put(TechnologiesStatsModel.STATS_SERVICES_JPA_NAMEDQUERIES, countByType(JPANamedQueryModel.class)); technologyUsage.put(TechnologiesStatsModel.STATS_SERVICES_JPA_PERSISTENCEUNITS, countByType(JPAPersistenceUnitModel.class)); technologyUsage.put(TechnologiesStatsModel.STATS_SERVICES_RMI_SERVICES, countByType(RMIServiceModel.class)); technologyUsage.put(TechnologiesStatsModel.STATS_SERVICES_HIBERNATE_CONFIGURATIONFILES, countByType(HibernateConfigurationFileModel.class)); technologyUsage.put(TechnologiesStatsModel.STATS_SERVICES_HIBERNATE_ENTITIES, countByType(HibernateEntityModel.class)); technologyUsage.put(TechnologiesStatsModel.STATS_SERVICES_HIBERNATE_MAPPINGFILES, countByType(HibernateMappingFileModel.class)); technologyUsage.put(TechnologiesStatsModel.STATS_SERVICES_HIBERNATE_SESSIONFACTORIES, countByType(HibernateSessionFactoryModel.class)); technologyUsage.put(TechnologiesStatsModel.STATS_SERVERRESOURCES_DB_JDBCDATASOURCES, this.countByType(DataSourceModel.class, new HashMap<String, Serializable>(){{ put(DataSourceModel.IS_XA, false); }})); technologyUsage.put(TechnologiesStatsModel.STATS_SERVERRESOURCES_DB_XAJDBCDATASOURCES, this.countByType(DataSourceModel.class, new HashMap<String, Serializable>(){{ put(DataSourceModel.IS_XA, true); }})); technologyUsage.put(TechnologiesStatsModel.STATS_SERVICES_HTTP_JAX_RS, this.countByType(JaxRSWebServiceModel.class)); technologyUsage.put(TechnologiesStatsModel.STATS_SERVICES_HTTP_JAX_WS, this.countByType(JaxWSWebServiceModel.class)); technologyUsage.put(TechnologiesStatsModel.STATS_SERVERRESOURCES_MSG_JMS_QUEUES, this.countByType(JmsDestinationModel.class, JmsDestinationModel.DESTINATION_TYPE, JmsDestinationType.QUEUE.name())); technologyUsage.put(TechnologiesStatsModel.STATS_SERVERRESOURCES_MSG_JMS_TOPICS, this.countByType(JmsDestinationModel.class, JmsDestinationModel.DESTINATION_TYPE, JmsDestinationType.TOPIC.name())); technologyUsage.put(TechnologiesStatsModel.STATS_SERVERRESOURCES_MSG_JMS_CONNECTIONFACTORIES, this.countByType(JmsConnectionFactoryModel.class)); //stats.setStatsServerResourcesSecurityRealms(item(countByType(.class))); technologyUsage.put(TechnologiesStatsModel.STATS_SERVERRESOURCES_JNDI_TOTALENTRIES, this.countByType(JNDIResourceModel.class)); // Not sure how to get this number. Maybe JavaClassFileModel.getJavaClass() ? technologyUsage.put(TechnologiesStatsModel.STATS_JAVA_CLASSES_ORIGINAL, this.countJavaClassesOriginal()); technologyUsage.put(TechnologiesStatsModel.STATS_JAVA_CLASSES_TOTAL, this.countByType(JavaClassModel.class)); // We are not able to tell which of the jars are original. We can substract known opensource libs. technologyUsage.put(TechnologiesStatsModel.STATS_JAVA_JARS_ORIGINAL, this.diff( this.countByType(JarArchiveModel.class), this.countByType(IdentifiedArchiveModel.class)) ); technologyUsage.put(TechnologiesStatsModel.STATS_JAVA_JARS_TOTAL, this.countByType(JarArchiveModel.class)); return this.groupByProjectModel(technologyUsage); } protected Map<ProjectModel, Map<String, Integer>> groupByProjectModel(Map<String, Map<ProjectModel, Integer>> groupedByTechnology) { Map<ProjectModel, Map<String, Integer>> projectBasedResult = new HashMap<>(); groupedByTechnology.entrySet().forEach(technologyMap -> { technologyMap.getValue().entrySet().forEach(projectTechCount -> { ProjectModel project = projectTechCount.getKey(); if (!projectBasedResult.containsKey(project)) { projectBasedResult.put(project, new HashMap<>()); } projectBasedResult.get(project).put(technologyMap.getKey(), projectTechCount.getValue()); }); }); return projectBasedResult; } protected Map<ProjectModel, Integer> sum(Map<ProjectModel, Integer> a, Map<ProjectModel, Integer> b) { Map<ProjectModel, Integer> result = new HashMap<>(); a.entrySet().forEach(keyValuePair -> result.put(keyValuePair.getKey(), keyValuePair.getValue())); b.entrySet().forEach(keyValuePair -> result.put( keyValuePair.getKey(), result.getOrDefault(keyValuePair.getKey(), 0) + keyValuePair.getValue() )); return result; } protected Map<ProjectModel, Integer> diff(Map<ProjectModel, Integer> a, Map<ProjectModel, Integer> b) { Map<ProjectModel, Integer> result = new HashMap<>(); a.entrySet().forEach(keyValuePair -> result.put(keyValuePair.getKey(), keyValuePair.getValue())); b.entrySet().forEach(keyValuePair -> result.put( keyValuePair.getKey(), result.getOrDefault(keyValuePair.getKey(), keyValuePair.getValue()) - keyValuePair.getValue() )); return result; } private <T extends WindupVertexFrame> Map<ProjectModel, Integer> countByType(Class<T> clazz) { return countByType(clazz, null); } private <T extends WindupVertexFrame> Map<ProjectModel, Integer> countByType(Class<T> clazz, String propName, Serializable value) { ///LOG.info("Counting: Frame class == " + clazz.getSimpleName() + " && " + propName + " == " + value); return countByType(clazz, propName == null ? null : new HashMap<String, Serializable>(){{put(propName, value);}}); } private <T extends WindupVertexFrame> Map<ProjectModel, Integer> countByType(Class<T> clazz, Map<String, Serializable> props) { FramedGraphQuery query = this.getGraphContext().getQuery().type(clazz); if (props != null) { for (Map.Entry<String, Serializable> prop : props.entrySet()) { String propName = prop.getKey(); Serializable value = prop.getValue(); if (value == null) query = query.has(propName); else query = query.has(propName, value); } } Map<ProjectModel, Integer> projectCount = new HashMap<>(); Iterable<T> vertices = query.vertices(clazz); for (T vertex : vertices) { if (vertex instanceof BelongsToProject) { for (ProjectModel projectModel : ((BelongsToProject) vertex).getRootProjectModels()) { projectCount.put(projectModel, projectCount.getOrDefault(projectModel, 0) + 1); } } else { String errorMessage = "Not instance of " + BelongsToProject.class.getName() + "\n" + clazz.getName(); LOG.warning(errorMessage); } } LOG.info("Counted: Frame class == " + clazz.getSimpleName() + " && " + (props == null ? "no" : props.size())); return projectCount; } public Map<ProjectModel, Map<String, Integer>> countFilesBySuffix() { Map<ProjectModel, Map<String, Integer>> result = new HashMap<>(); Iterable<FileModel> files = this.getGraphContext().getQuery() .type(FileModel.class) .hasNot(FileModel.IS_DIRECTORY, true) .vertices(FileModel.class); StreamSupport.stream(files.spliterator(), false) .forEach((FileModel file) -> { String suffix = StringUtils.substringAfterLast(file.getFileName(), "."); if (suffix.isEmpty() || file.isWindupGenerated()) { return; } ProjectModel projectModel = file.getProjectModel(); if (projectModel == null) { return; } ProjectModel rootProjectModel = projectModel.getRootProjectModel(); Map<String, Integer> suffixToCount; if (rootProjectModel == null) { throw new RuntimeException("RootProjectModel null"); } else { if (!result.containsKey(rootProjectModel)) { result.put(rootProjectModel, new HashMap<>()); } suffixToCount = result.get(rootProjectModel); } Integer val = suffixToCount.get(suffix); if (val == null) { suffixToCount.put(suffix, 1); } else { suffixToCount.put(suffix, val + 1); } }); return result; } /* * Shortcut methods when only the qty is needed. */ private GeneralStatsItemModel item(int i) { return this.getGraphContext().create(GeneralStatsItemModel.class).setQuantity(i); } private GeneralStatsItemModel item( String key, String label, Class<? extends WindupVertexFrame> type, Map<String, String> props ) { GeneralStatsItemModel item = this.getGraphContext().create(GeneralStatsItemModel.class).setKey(key).setLabel(label); long qty = countGraphVertices(type, props); item.setQuantity((int) qty); return item; } private long countGraphVertices(Class<? extends WindupVertexFrame> clazz, Map<String, String> props) { if (clazz == null) throw new IllegalArgumentException("Frame type must be set (was null)."); LOG.info("Counting: Frame class == " + clazz.getSimpleName() + " && " + CollectionUtils.size(props) + " props."); FramedGraphQuery query = this.getGraphContext().getQuery().type(clazz); //props.entrySet().forEach( e -> { if (props != null) for (Map.Entry<String, String> e : props.entrySet()) { String key = e.getKey(); if (key != null && !key.isEmpty()) if (e.getValue() == null) query = query.has(key); else query = query.has(key, e.getValue()); } long count = this.count(query.vertices()); LOG.info(" ==> " + count); return count; } // Methods for individual statistic items private Map<ProjectModel, Integer> countJavaClassesOriginal() { // TODO: Fix this // new Pipeline<Vertex, Vertex>(). Iterable<Vertex> startVertices = new TypeAwareFramedGraphQuery(this.getGraphContext().getFramed()).type(JavaClassModel.class).vertices(); Pipeline<Vertex, Vertex> pipeline = new Pipeline<>(); pipeline.addPipe(new StartPipe(startVertices)); final OutPipe outPipe = new OutPipe(JavaClassModel.DECOMPILED_SOURCE); // The BackFilterPipe needs to wrap all pipes which it "go back before". // This means ...out(...).back(1); pipeline.addPipe(new BackFilterPipe(outPipe)); Map<ProjectModel, Integer> map = new HashMap<>(); Iterable<JavaClassModel> javaClassModels = this.getGraphContext().getFramed().frameVertices(pipeline, JavaClassModel.class); javaClassModels.forEach(item -> { FileModel fileModel = item.getDecompiledSource(); if (fileModel == null) { LOG.warning("Unexpected fileModel null"); return; } ProjectModel projectModel = fileModel.getProjectModel(); if (projectModel == null) { LOG.warning("Unexpected projectModel null"); return; } ProjectModel rootProjectModel = projectModel.getRootProjectModel(); map.put(rootProjectModel, map.getOrDefault(rootProjectModel, 0) + 1); }); return map; } }