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;
}
}