/** * EasySOA Registry Copyright 2012 Open Wide * * This program is free software: you can redistribute it and/or modify it 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. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more * details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. * * Contact : easysoa-dev@googlegroups.com */ package org.easysoa.registry.indicators.rest; import au.com.bytecode.opencsv.CSVWriter; import java.io.File; import java.io.FileReader; import java.io.FileWriter; import java.io.Reader; import java.io.StringWriter; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.Produces; import javax.ws.rs.QueryParam; import javax.ws.rs.core.MediaType; import org.apache.commons.io.IOUtils; import org.apache.log4j.Logger; import org.easysoa.registry.SubprojectServiceImpl; import org.easysoa.registry.rest.EasysoaModuleRoot; import org.easysoa.registry.types.Actor; import org.easysoa.registry.types.BusinessService; import org.easysoa.registry.types.Component; import org.easysoa.registry.types.Deliverable; import org.easysoa.registry.types.DeployedDeliverable; import org.easysoa.registry.types.Endpoint; import org.easysoa.registry.types.EndpointConsumption; import org.easysoa.registry.types.InformationService; import org.easysoa.registry.types.RequirementsDocument; import org.easysoa.registry.types.ServiceConsumption; import org.easysoa.registry.types.ServiceImplementation; import org.easysoa.registry.types.SoaNode; import org.easysoa.registry.types.TaggingFolder; import org.easysoa.registry.utils.ContextData; import org.nuxeo.ecm.core.api.CoreSession; import org.nuxeo.ecm.webengine.jaxrs.session.SessionFactory; import org.nuxeo.ecm.webengine.model.WebObject; /** * Indicators * * TODO refactor init of providers & computing to dedicated IndicatorsService * * @author mdutoo * */ //XXX Slightly outdated after removal of Service doctype @WebObject(type = "EasySOA") @Path("easysoa/indicators") public class IndicatorsController extends EasysoaModuleRoot { // New category for indicators public static final String CATEGORY_STEERING = "steering"; // Or Governance ? public static final String CATEGORY_CARTOGRAPHY = "cartography"; public static final String CATEGORY_USAGE = "usage"; public static final String CATEGORY_MATCHING = "matching"; private static Logger logger = Logger.getLogger(IndicatorsController.class); private List<IndicatorProvider> indicatorProviders = new ArrayList<IndicatorProvider>(); private Map<String,IndicatorProvider> name2indicatorProviderMap = new HashMap<String,IndicatorProvider>(); /** * Init the indicator controller */ public IndicatorsController() { // CARTOGRAPHY // Document count by type addIndicatorProvider(new DoctypeCountProvider(SoaNode.ABSTRACT_DOCTYPE, CATEGORY_CARTOGRAPHY)); // TODO : replace all these providers by a unique provider addIndicatorProvider(new DoctypeCountProvider(BusinessService.DOCTYPE, CATEGORY_CARTOGRAPHY)); addIndicatorProvider(new DoctypeCountProvider(Actor.DOCTYPE, CATEGORY_CARTOGRAPHY)); addIndicatorProvider(new DoctypeCountProvider(RequirementsDocument.DOCTYPE, CATEGORY_CARTOGRAPHY)); //addIndicatorProvider(new DoctypeCountProvider(SoftwareComponent.DOCTYPE, CATEGORY_CARTOGRAPHY)); addIndicatorProvider(new DoctypeCountProvider(InformationService.DOCTYPE, CATEGORY_CARTOGRAPHY)); addIndicatorProvider(new DoctypeCountProvider(Component.DOCTYPE, CATEGORY_CARTOGRAPHY)); addIndicatorProvider(new DoctypeCountProvider(ServiceImplementation.DOCTYPE, CATEGORY_CARTOGRAPHY)); addIndicatorProvider(new DoctypeCountProvider(ServiceConsumption.DOCTYPE, CATEGORY_CARTOGRAPHY)); addIndicatorProvider(new DoctypeCountProvider(Deliverable.DOCTYPE, CATEGORY_CARTOGRAPHY)); addIndicatorProvider(new DoctypeCountProvider(DeployedDeliverable.DOCTYPE, CATEGORY_CARTOGRAPHY)); addIndicatorProvider(new DoctypeCountProvider(Endpoint.DOCTYPE, CATEGORY_CARTOGRAPHY)); addIndicatorProvider(new DoctypeCountProvider(EndpointConsumption.DOCTYPE, CATEGORY_CARTOGRAPHY)); addIndicatorProvider(new DoctypeCountProvider(TaggingFolder.DOCTYPE, CATEGORY_CARTOGRAPHY)); // Specific indicators addIndicatorProvider(new LastCodeDiscoveryIndicatorProvider(CATEGORY_CARTOGRAPHY)); addIndicatorProvider(new ServiceImplStateProvider(CATEGORY_CARTOGRAPHY)); // MATCHING //addIndicatorProvider(new PlaceholdersIndicatorProvider(InformationService.DOCTYPE, CATEGORY_MATCHING)); addIndicatorProvider(new PlaceholdersIndicatorProvider(ServiceImplementation.DOCTYPE, CATEGORY_MATCHING)); addIndicatorProvider(new PlaceholdersIndicatorProvider(Endpoint.DOCTYPE, CATEGORY_MATCHING)); //addIndicatorProvider(new PlaceholdersIndicatorProvider(SoaNode.DOCTYPE, CATEGORY_MATCHING)); // USAGE addIndicatorProvider(new ServiceDefaultCountIndicatorProvider(CATEGORY_USAGE)); // STEERING addIndicatorProvider(new ServiceConsumptionIndicatorProvider(CATEGORY_STEERING)); addIndicatorProvider(new ServiceStateProvider(CATEGORY_STEERING)); //addIndicatorProvider(new SoftwareComponentIndicatorProvider()); // Disabled, need to be updated addIndicatorProvider(new TagsIndicatorProvider(CATEGORY_STEERING)); addIndicatorProvider(new PhaseProgressIndicatorProvider(CATEGORY_STEERING)); } /** * Add an indicator provider in the indicator list * * @param indicator provider */ public void addIndicatorProvider(IndicatorProvider indicatorProvider) { if (name2indicatorProviderMap.containsKey(indicatorProvider.getName())) { throw new RuntimeException("Conflicting indicatorProvider names: " + indicatorProvider.getName()); } indicatorProviders.add(indicatorProvider); name2indicatorProviderMap.put(indicatorProvider.getName(), indicatorProvider); } public IndicatorProvider getIndicatorProvider(String name) { return name2indicatorProviderMap.get(name); } // TODO : add a method to compute indicators and to be callable from another controller // TODO : return a single map instead of 2 separed maps for percents and count values /** * * @param nbMap * @param percentMap * @param subprojectId The subproject (or version) ID * @param visibility Visibility used when computing the indicators * @return A map containing the indicators values * @throws Exception If a problem occurs */ public Map<String, IndicatorValue> computeIndicators(CoreSession session, HashMap<String, Long> nbMap, HashMap<String, Integer> percentMap, String subprojectId, String visibility) throws Exception { // using all subprojects or getting default one is let to indicator impls TODO better //subprojectId = SubprojectServiceImpl.getSubprojectIdOrCreateDefault(session, subprojectId); if ("".equals(subprojectId)) { subprojectId = null; } Map<String, IndicatorValue> indicators = computeIndicators(session, subprojectId, visibility); // Create and return view if (nbMap == null) { nbMap = new HashMap<String, Long>(); } if (percentMap == null) { percentMap = new HashMap<String, Integer>(); } for (Entry<String, IndicatorValue> indicator : indicators.entrySet()) { if (indicator.getValue().getCount() != -1) { nbMap.put(indicator.getKey(), indicator.getValue().getCount()); } if (indicator.getValue().getPercentage() != -1) { percentMap.put(indicator.getKey(), indicator.getValue().getPercentage()); } } return indicators; } /** * * @param subprojectId * @param visibility * @return The indicators view * @throws Exception */ @GET @Produces(MediaType.TEXT_HTML) public Object doGetHTML(@QueryParam("subprojectId") String subprojectId, @QueryParam("visibility") String visibility) throws Exception { CoreSession session = SessionFactory.getSession(request); // Init maps HashMap<String, Long> nbMap = new HashMap<String, Long>(); HashMap<String, Integer> percentMap = new HashMap<String, Integer>(); // Compute the indicators this.computeIndicators(session, nbMap, percentMap, subprojectId, visibility); // Set args for the template return getView("indicators") .arg("nbMap", nbMap) // TODO : only one map containing the indicatorValue .arg("percentMap", percentMap) // TODO : only one map .arg("subprojectId", subprojectId) .arg("visibility", visibility) .arg("contextInfo", ContextData.getVersionData(session, subprojectId)); } /** * * @param subprojectId * @param visibility * @return The indicators packaged in a json structure * @throws Exception */ @GET @Produces(MediaType.APPLICATION_JSON) public Object doGetJSON(@QueryParam("subproject") String subprojectId, @QueryParam("visibility") String visibility) throws Exception { CoreSession session = SessionFactory.getSession(request); subprojectId = SubprojectServiceImpl.getSubprojectIdOrCreateDefault(session, subprojectId); // TODO default or not ?? return computeIndicators(session, subprojectId, visibility); } //private Map<String, Map<String, IndicatorValue>> computeIndicators(String subprojectId, String visibility) throws Exception { private Map<String, IndicatorValue> computeIndicators(CoreSession session, String subprojectId, String visibility) throws Exception { //CoreSession session = SessionFactory.getSession(request); List<IndicatorProvider> computedProviders = new ArrayList<IndicatorProvider>(); List<IndicatorProvider> pendingProviders = new ArrayList<IndicatorProvider>(); Map<String, IndicatorValue> computedIndicators = new HashMap<String, IndicatorValue>(); //Map<String, Map<String, IndicatorValue>> indicatorsByCategory = new HashMap<String, Map<String, IndicatorValue>>(); HashSet<String> pendingRequiredIndicators = new HashSet<String>(indicatorProviders.size()); int previousComputedProvidersCount = -1; // Compute indicators in several passes, with respect to dependencies while (computedProviders.size() != previousComputedProvidersCount) { previousComputedProvidersCount = computedProviders.size(); /*for (Entry<String, List<IndicatorProvider>> indicatorProviderCategory : indicatorProviders.entrySet()) {*/ // Start or continue indicator category /*Map<String, IndicatorValue> categoryIndicators = indicatorsByCategory.get(indicatorProviderCategory.getKey()); if (categoryIndicators == null) { categoryIndicators = new HashMap<String, IndicatorValue>(); }*/ // Browse all providers //for (IndicatorProvider indicatorProvider : indicatorProviderCategory.getValue()) { for (IndicatorProvider indicatorProvider : indicatorProviders) { if (!computedProviders.contains(indicatorProvider)) { // Compute indicator only if the dependencies are already computed List<String> requiredIndicators = indicatorProvider.getRequiredIndicators(); boolean allRequirementsSatisfied = true; if (requiredIndicators != null) { for (String requiredIndicator : requiredIndicators) { if (!computedIndicators.containsKey(requiredIndicator)) { allRequirementsSatisfied = false; pendingRequiredIndicators.add(requiredIndicator); break; } } } // Actual indicator calculation if (allRequirementsSatisfied) { Map<String, IndicatorValue> indicators = null; try { indicators = indicatorProvider .computeIndicators(session, subprojectId, computedIndicators, visibility); } catch (Exception e) { logger.error("Failed to compute indicator '" + indicatorProvider.toString() + "': " + e.getMessage(), e); } if (indicators != null) { //categoryIndicators.putAll(indicators); computedIndicators.putAll(indicators); pendingRequiredIndicators.removeAll(indicators.entrySet()); // just in case there had been required } computedProviders.add(indicatorProvider); pendingProviders.remove(indicatorProvider); } else { pendingProviders.add(indicatorProvider); } } } //indicatorsByCategory.put(indicatorProviderCategory.getKey(), categoryIndicators); } /*}*/ // Warn if some indicators have been left pending for (IndicatorProvider pendingProvider : pendingProviders) { logger.warn(pendingProvider.getClass().getName() + " provider dependencies could not be satisfied (" + pendingProvider.getRequiredIndicators() + ")"); } if (!pendingRequiredIndicators.isEmpty()) { logger.warn("Pending required indicators : " + pendingRequiredIndicators); } //return indicatorsByCategory; return computedIndicators; } /** * Exports the indicators as a CSV file * @param subprojectId * @param visibility * @return * @throws Exception */ @GET @Path("export.csv") @Produces(MediaType.APPLICATION_OCTET_STREAM) public Object doGetExportIndicators(@QueryParam("subprojectId") String subprojectId, @QueryParam("visibility") String visibility, @QueryParam("category") String category) throws Exception { CoreSession session = SessionFactory.getSession(request); File exportFile = new File("indicators.csv"); CSVWriter writer = new CSVWriter(new FileWriter(exportFile), ';'); // Headers String[] entries = new String[4]; entries[0] = "Indicateur"; entries[1] = "Description"; entries[2] = "Valeur"; entries[3] = "Pourcentage"; writer.writeNext(entries); // values Map<String, IndicatorValue> indicators = computeIndicators(session, subprojectId, visibility); Set<String> indicatorNames = indicators.keySet(); for(String indicatorName : indicatorNames){ if(category != null && !"".equals(category)){ if(indicators.get(indicatorName).getCategories().contains(category)){ // return only the indicators with the correponding category writeEntry(writer, indicators.get(indicatorName)); } } else { // return all indicators writeEntry(writer, indicators.get(indicatorName)); } } writer.close(); Reader input = new FileReader(exportFile); StringWriter output = new StringWriter(); try { IOUtils.copy(input, output); } finally { input.close(); } return output.toString(); } /** * * @param writer * @param indicatorValue */ private void writeEntry(CSVWriter writer, IndicatorValue indicatorValue){ String[] entries = new String[4]; entries[0] = indicatorValue.getName(); entries[1] = indicatorValue.getDescription(); if(indicatorValue.getCount() == -1){ entries[2] = "N/A"; } else { entries[2] = String.valueOf(indicatorValue.getCount()); } if(indicatorValue.getPercentage() == -1){ entries[3] = "N/A"; } else { entries[3] = String.valueOf(indicatorValue.getPercentage()) + "%"; } writer.writeNext(entries); } }