blob: 8f4c972fe994e2ec547cd7c2fc20c49625df5385 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.ofbiz.webtools.artifactinfo;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import org.apache.ofbiz.base.location.FlexibleLocation;
import org.apache.ofbiz.base.util.Debug;
import org.apache.ofbiz.base.util.FileUtil;
import org.apache.ofbiz.base.util.GeneralException;
import org.apache.ofbiz.base.util.UtilJavaParse;
import org.apache.ofbiz.base.util.UtilMisc;
import org.apache.ofbiz.base.util.UtilPlist;
import org.apache.ofbiz.base.util.UtilValidate;
import org.apache.ofbiz.minilang.MiniLangException;
import org.apache.ofbiz.minilang.SimpleMethod;
import org.apache.ofbiz.minilang.artifact.ArtifactInfoContext;
import org.apache.ofbiz.service.ModelParam;
import org.apache.ofbiz.service.ModelService;
import org.apache.ofbiz.service.eca.ServiceEcaRule;
import org.apache.ofbiz.service.eca.ServiceEcaUtil;
import org.apache.ofbiz.service.group.GroupModel;
import org.apache.ofbiz.service.group.GroupServiceModel;
import org.apache.ofbiz.service.group.ServiceGroupReader;
/**
*
*/
public class ServiceArtifactInfo extends ArtifactInfoBase {
public static final String module = ServiceArtifactInfo.class.getName();
protected ModelService modelService;
protected String displayPrefix = null;
Set<EntityArtifactInfo> entitiesUsedByThisService = new TreeSet<EntityArtifactInfo>();
Set<ServiceArtifactInfo> servicesCalledByThisService = new TreeSet<ServiceArtifactInfo>();
Set<ServiceEcaArtifactInfo> serviceEcasTriggeredByThisService = new TreeSet<ServiceEcaArtifactInfo>();
public ServiceArtifactInfo(String serviceName, ArtifactInfoFactory aif) throws GeneralException {
super(aif);
this.modelService = this.aif.getModelService(serviceName);
}
/**
* This must be called after creation from the ArtifactInfoFactory after this class has been put into the global Map in order to avoid recursive initialization
*
* @throws GeneralException
*/
public void populateAll() throws GeneralException {
this.populateUsedEntities();
this.populateCalledServices();
this.populateTriggeredServiceEcas();
}
protected void populateUsedEntities() throws GeneralException {
// populate entitiesUsedByThisService and for each the reverse-associate cache in the aif
if ("simple".equals(this.modelService.engineName)) {
// we can do something with this!
SimpleMethod simpleMethodToCall = null;
try {
simpleMethodToCall = SimpleMethod.getSimpleMethod(this.modelService.location, this.modelService.invoke,null);
} catch (MiniLangException e) {
Debug.logWarning("Error getting Simple-method [" + this.modelService.invoke + "] in [" + this.modelService.location + "] referenced in service [" + this.modelService.name + "]: " + e.toString(), module);
}
if (simpleMethodToCall == null) {
Debug.logWarning("Simple-method [" + this.modelService.invoke + "] in [" + this.modelService.location + "] referenced in service [" + this.modelService.name + "] not found", module);
return;
}
ArtifactInfoContext aic = new ArtifactInfoContext();
simpleMethodToCall.gatherArtifactInfo(aic);
populateEntitiesFromNameSet(aic.getEntityNames());
} else if ("java".equals(this.modelService.engineName)) {
String fullClassPathAndFile = UtilJavaParse.findRealPathAndFileForClass(this.modelService.location);
if (fullClassPathAndFile != null) {
String javaFile = null;
try {
javaFile = FileUtil.readTextFile(fullClassPathAndFile, true).toString();
} catch (FileNotFoundException e) {
Debug.logWarning("Error reading java file [" + fullClassPathAndFile + "] for service implementation: " + e.toString(), module);
return;
} catch (IOException e) {
Debug.logWarning("Error reading java file [" + fullClassPathAndFile + "] for service implementation: " + e.toString(), module);
return;
}
javaFile = UtilJavaParse.stripComments(javaFile);
int methodBlockStart = UtilJavaParse.findServiceMethodBlockStart(this.modelService.invoke, javaFile);
int methodBlockEnd = UtilJavaParse.findEndOfBlock(methodBlockStart, javaFile);
Set<String> allEntityNameSet = UtilJavaParse.findEntityUseInBlock(methodBlockStart, methodBlockEnd, javaFile);
populateEntitiesFromNameSet(allEntityNameSet);
}
} else if ("group".equals(this.modelService.engineName)) {
// nothing to do, there won't be entities referred to in these
}
}
protected void populateEntitiesFromNameSet(Set<String> allEntityNameSet) throws GeneralException {
for (String entityName: allEntityNameSet) {
if (UtilValidate.isEmpty(entityName) || entityName.contains("${")) {
continue;
}
// attempt to convert relation names to entity names
String validEntityName = aif.getEntityModelReader().validateEntityName(entityName);
if (validEntityName == null) {
Debug.logWarning("Entity [" + entityName + "] reference in service [" + this.modelService.name + "] does not exist!", module);
continue;
}
// the forward reference
this.entitiesUsedByThisService.add(aif.getEntityArtifactInfo(validEntityName));
// the reverse reference
UtilMisc.addToSortedSetInMap(this, aif.allServiceInfosReferringToEntityName, validEntityName);
}
}
protected void populateCalledServices() throws GeneralException {
// populate servicesCalledByThisService and for each the reverse-associate cache in the aif
if ("simple".equals(this.modelService.engineName)) {
// we can do something with this!
SimpleMethod simpleMethodToCall = null;
try {
simpleMethodToCall = SimpleMethod.getSimpleMethod(this.modelService.location, this.modelService.invoke,null);
} catch (MiniLangException e) {
Debug.logWarning("Error getting Simple-method [" + this.modelService.invoke + "] in [" + this.modelService.location + "] referenced in service [" + this.modelService.name + "]: " + e.toString(), module);
}
if (simpleMethodToCall == null) {
Debug.logWarning("Simple-method [" + this.modelService.invoke + "] in [" + this.modelService.location + "] referenced in service [" + this.modelService.name + "] not found", module);
return;
}
ArtifactInfoContext aic = new ArtifactInfoContext();
simpleMethodToCall.gatherArtifactInfo(aic);
populateServicesFromNameSet(aic.getServiceNames());
} else if ("java".equals(this.modelService.engineName)) {
String fullClassPathAndFile = UtilJavaParse.findRealPathAndFileForClass(this.modelService.location);
if (fullClassPathAndFile != null) {
String javaFile = null;
try {
javaFile = FileUtil.readTextFile(fullClassPathAndFile, true).toString();
} catch (FileNotFoundException e) {
Debug.logWarning("Error reading java file [" + fullClassPathAndFile + "] for service implementation: " + e.toString(), module);
return;
} catch (IOException e) {
Debug.logWarning("Error reading java file [" + fullClassPathAndFile + "] for service implementation: " + e.toString(), module);
return;
}
javaFile = UtilJavaParse.stripComments(javaFile);
int methodBlockStart = UtilJavaParse.findServiceMethodBlockStart(this.modelService.invoke, javaFile);
int methodBlockEnd = UtilJavaParse.findEndOfBlock(methodBlockStart, javaFile);
Set<String> allServiceNameSet = UtilJavaParse.findServiceCallsInBlock(methodBlockStart, methodBlockEnd, javaFile);
populateServicesFromNameSet(allServiceNameSet);
}
} else if ("group".equals(this.modelService.engineName)) {
Set<String> allServiceNameSet = new HashSet<String>();
GroupModel groupModel = modelService.internalGroup;
if (groupModel == null) {
groupModel = ServiceGroupReader.getGroupModel(this.modelService.location);
}
if (groupModel != null) {
List<GroupServiceModel> groupServiceModels = groupModel.getServices();
for (GroupServiceModel groupServiceModel: groupServiceModels) {
allServiceNameSet.add(groupServiceModel.getName());
}
}
populateServicesFromNameSet(allServiceNameSet);
}
}
protected void populateServicesFromNameSet(Set<String> allServiceNameSet) throws GeneralException {
for (String serviceName: allServiceNameSet) {
if (serviceName.contains("${")) {
continue;
}
if (!aif.getDispatchContext().getAllServiceNames().contains(serviceName)) {
Debug.logWarning("Service [" + serviceName + "] reference in service [" + this.modelService.name + "] does not exist!", module);
continue;
}
// the forward reference
this.servicesCalledByThisService.add(aif.getServiceArtifactInfo(serviceName));
// the reverse reference
UtilMisc.addToSortedSetInMap(this, aif.allServiceInfosReferringToServiceName, serviceName);
}
}
protected void populateTriggeredServiceEcas() throws GeneralException {
// populate serviceEcasTriggeredByThisService and for each the reverse-associate cache in the aif
Map<String, List<ServiceEcaRule>> serviceEventMap = ServiceEcaUtil.getServiceEventMap(this.modelService.name);
if (serviceEventMap == null) return;
for (List<ServiceEcaRule> ecaRuleList: serviceEventMap.values()) {
for (ServiceEcaRule ecaRule: ecaRuleList) {
this.serviceEcasTriggeredByThisService.add(aif.getServiceEcaArtifactInfo(ecaRule));
// the reverse reference
UtilMisc.addToSortedSetInMap(this, aif.allServiceInfosReferringToServiceEcaRule, ecaRule);
}
}
}
public ModelService getModelService() {
return this.modelService;
}
public void setDisplayPrefix(String displayPrefix) {
this.displayPrefix = displayPrefix;
}
@Override
public String getDisplayName() {
return this.getDisplayPrefixedName();
}
public String getDisplayPrefixedName() {
return (this.displayPrefix != null ? this.displayPrefix : "") + this.modelService.name;
}
@Override
public String getDisplayType() {
return "Service";
}
@Override
public String getType() {
return ArtifactInfoFactory.ServiceInfoTypeId;
}
@Override
public String getUniqueId() {
return this.modelService.name;
}
@Override
public URL getLocationURL() throws MalformedURLException {
return FlexibleLocation.resolveLocation(this.modelService.definitionLocation, null);
}
public URL getImplementationLocationURL() throws MalformedURLException {
return FlexibleLocation.resolveLocation(this.modelService.location, null);
}
public Set<EntityArtifactInfo> getEntitiesUsedByService() {
return this.entitiesUsedByThisService;
}
public Set<ServiceArtifactInfo> getServicesCallingService() {
return aif.allServiceInfosReferringToServiceName.get(this.modelService.name);
}
public Set<ServiceArtifactInfo> getServicesCalledByService() {
return this.servicesCalledByThisService;
}
public Set<ServiceArtifactInfo> getServicesCalledByServiceEcas() {
// TODO: implement this sometime, not really necessary
return new HashSet<ServiceArtifactInfo>();
}
public Set<ServiceEcaArtifactInfo> getServiceEcaRulesTriggeredByService() {
return this.serviceEcasTriggeredByThisService;
}
public Set<ServiceArtifactInfo> getServicesCallingServiceByEcas() {
// TODO: implement this sometime, not really necessary
return new HashSet<ServiceArtifactInfo>();
}
public Set<ServiceEcaArtifactInfo> getServiceEcaRulesCallingService() {
return this.aif.allServiceEcaInfosReferringToServiceName.get(this.modelService.name);
}
public Set<FormWidgetArtifactInfo> getFormsCallingService() {
return this.aif.allFormInfosReferringToServiceName.get(this.modelService.name);
}
public Set<FormWidgetArtifactInfo> getFormsBasedOnService() {
return this.aif.allFormInfosBasedOnServiceName.get(this.modelService.name);
}
public Set<ScreenWidgetArtifactInfo> getScreensCallingService() {
return this.aif.allScreenInfosReferringToServiceName.get(this.modelService.name);
}
public Set<ControllerRequestArtifactInfo> getRequestsWithEventCallingService() {
return this.aif.allRequestInfosReferringToServiceName.get(this.modelService.name);
}
public void writeServiceCallGraphEoModel(String eomodeldFullPath) throws GeneralException, FileNotFoundException, UnsupportedEncodingException {
boolean useMoreDetailedNames = true;
Debug.logInfo("Writing Service Call Graph EO Model for service [" + this.modelService.name + "] to [" + eomodeldFullPath + "]", module);
Set<String> allDiagramEntitiesWithPrefixes = new HashSet<String>();
List<ServiceArtifactInfo> allServiceList = new LinkedList<ServiceArtifactInfo>();
List<ServiceEcaArtifactInfo> allServiceEcaList = new LinkedList<ServiceEcaArtifactInfo>();
// make sure that any prefix that might have been set on this is cleared
this.setDisplayPrefix("");
// put this service in the master list
allDiagramEntitiesWithPrefixes.add(this.modelService.name);
// all services that call this service
Set<ServiceArtifactInfo> callingServiceSet = this.getServicesCallingService();
if (callingServiceSet != null) {
// set the prefix and add to the all list
for (ServiceArtifactInfo callingService: callingServiceSet) {
callingService.setDisplayPrefix("Calling_");
allDiagramEntitiesWithPrefixes.add(callingService.getDisplayPrefixedName());
allServiceList.add(callingService);
}
}
// all services this service calls
Set<ServiceArtifactInfo> calledServiceSet = this.getServicesCalledByService();
for (ServiceArtifactInfo calledService: calledServiceSet) {
calledService.setDisplayPrefix("Called_");
allDiagramEntitiesWithPrefixes.add(calledService.getDisplayPrefixedName());
allServiceList.add(calledService);
}
Map<String, Integer> displaySuffixNumByEcaName = new HashMap<String, Integer>();
// all SECAs and triggering services that call this service as an action
Set<ServiceEcaArtifactInfo> callingServiceEcaSet = this.getServiceEcaRulesCallingService();
if (callingServiceEcaSet != null) {
for (ServiceEcaArtifactInfo callingServiceEca: callingServiceEcaSet) {
callingServiceEca.setDisplayPrefix("Triggering_");
Integer displaySuffix = displaySuffixNumByEcaName.get(callingServiceEca.getDisplayPrefixedName());
if (displaySuffix == null) {
displaySuffix = 1;
} else {
displaySuffix++;
}
displaySuffixNumByEcaName.put(callingServiceEca.getDisplayPrefixedName(), displaySuffix);
callingServiceEca.setDisplaySuffixNum(displaySuffix);
allDiagramEntitiesWithPrefixes.add(callingServiceEca.getDisplayPrefixedName());
allServiceEcaList.add(callingServiceEca);
}
}
// all SECAs and corresponding services triggered by this service
Set<ServiceEcaArtifactInfo> calledServiceEcaSet = this.getServiceEcaRulesTriggeredByService();
for (ServiceEcaArtifactInfo calledServiceEca: calledServiceEcaSet) {
calledServiceEca.setDisplayPrefix("Triggered_");
Integer displaySuffix = displaySuffixNumByEcaName.get(calledServiceEca.getDisplayPrefixedName());
if (displaySuffix == null) {
displaySuffix = 1;
} else {
displaySuffix++;
}
displaySuffixNumByEcaName.put(calledServiceEca.getDisplayPrefixedName(), displaySuffix);
calledServiceEca.setDisplaySuffixNum(displaySuffix);
allDiagramEntitiesWithPrefixes.add(calledServiceEca.getDisplayPrefixedName());
allServiceEcaList.add(calledServiceEca);
}
// write index.eomodeld file
Map<String, Object> indexEoModelMap = new HashMap<String, Object>();
indexEoModelMap.put("EOModelVersion", "\"2.1\"");
List<Map<String, Object>> entitiesMapList = new LinkedList<Map<String,Object>>();
indexEoModelMap.put("entities", entitiesMapList);
for (String entityName: allDiagramEntitiesWithPrefixes) {
Map<String, Object> entitiesMap = new HashMap<String, Object>();
entitiesMapList.add(entitiesMap);
entitiesMap.put("className", "EOGenericRecord");
entitiesMap.put("name", entityName);
}
UtilPlist.writePlistFile(indexEoModelMap, eomodeldFullPath, "index.eomodeld", true);
// write this service description file
Map<String, Object> thisServiceEoModelMap = createEoModelMap(callingServiceSet, calledServiceSet, callingServiceEcaSet, calledServiceEcaSet, useMoreDetailedNames);
UtilPlist.writePlistFile(thisServiceEoModelMap, eomodeldFullPath, this.modelService.name + ".plist", true);
// write service description files
if (callingServiceSet != null) {
for (ServiceArtifactInfo callingService: callingServiceSet) {
Map<String, Object> serviceEoModelMap = callingService.createEoModelMap(null, UtilMisc.toSet(this), null, null, useMoreDetailedNames);
UtilPlist.writePlistFile(serviceEoModelMap, eomodeldFullPath, callingService.getDisplayPrefixedName() + ".plist", true);
}
}
if (calledServiceSet != null) {
for (ServiceArtifactInfo calledService: calledServiceSet) {
Map<String, Object> serviceEoModelMap = calledService.createEoModelMap(UtilMisc.toSet(this), null, null, null, useMoreDetailedNames);
UtilPlist.writePlistFile(serviceEoModelMap, eomodeldFullPath, calledService.getDisplayPrefixedName() + ".plist", true);
}
}
// write SECA description files
if (callingServiceEcaSet != null) {
for (ServiceEcaArtifactInfo callingServiceEca: callingServiceEcaSet) {
// add List<ServiceArtifactInfo> for services that trigger this eca rule
Set<ServiceArtifactInfo> ecaCallingServiceSet = callingServiceEca.getServicesTriggeringServiceEca();
for (ServiceArtifactInfo ecaCallingService: ecaCallingServiceSet) {
ecaCallingService.setDisplayPrefix("Triggering_");
}
ecaCallingServiceSet.add(this);
Map<String, Object> serviceEcaEoModelMap = callingServiceEca.createEoModelMap(ecaCallingServiceSet, null, useMoreDetailedNames);
UtilPlist.writePlistFile(serviceEcaEoModelMap, eomodeldFullPath, callingServiceEca.getDisplayPrefixedName() + ".plist", true);
}
}
if (calledServiceEcaSet != null) {
for (ServiceEcaArtifactInfo calledServiceEca: calledServiceEcaSet) {
// add List<ServiceArtifactInfo> for services this eca rule calls in action
Set<ServiceArtifactInfo> ecaCalledServiceSet = calledServiceEca.getServicesCalledByServiceEcaActions();
for (ServiceArtifactInfo ecaCalledService: ecaCalledServiceSet) {
ecaCalledService.setDisplayPrefix("Triggered_");
}
ecaCalledServiceSet.add(this);
Map<String, Object> serviceEcaEoModelMap = calledServiceEca.createEoModelMap(null, ecaCalledServiceSet, useMoreDetailedNames);
UtilPlist.writePlistFile(serviceEcaEoModelMap, eomodeldFullPath, calledServiceEca.getDisplayPrefixedName() + ".plist", true);
}
}
}
public Map<String, Object> createEoModelMap(Set<ServiceArtifactInfo> callingServiceSet, Set<ServiceArtifactInfo> calledServiceSet, Set<ServiceEcaArtifactInfo> callingServiceEcaSet, Set<ServiceEcaArtifactInfo> calledServiceEcaSet, boolean useMoreDetailedNames) {
if (callingServiceSet == null) callingServiceSet = new HashSet<ServiceArtifactInfo>();
if (calledServiceSet == null) calledServiceSet = new HashSet<ServiceArtifactInfo>();
if (callingServiceEcaSet == null) callingServiceEcaSet = new HashSet<ServiceEcaArtifactInfo>();
if (calledServiceEcaSet == null) calledServiceEcaSet = new HashSet<ServiceEcaArtifactInfo>();
Map<String, Object> topLevelMap = new HashMap<String, Object>();
topLevelMap.put("name", this.getDisplayPrefixedName());
topLevelMap.put("className", "EOGenericRecord");
// for classProperties add attribute names AND relationship names to get a nice, complete chart
List<String> classPropertiesList = new LinkedList<String>();
topLevelMap.put("classProperties", classPropertiesList);
for (ModelParam param: this.modelService.getModelParamList()) {
// skip the internal parameters, very redundant in the diagrams
if (param.internal) continue;
if (useMoreDetailedNames) {
classPropertiesList.add(param.getShortDisplayDescription());
} else {
classPropertiesList.add(param.name);
}
}
for (ServiceArtifactInfo sai: callingServiceSet) {
classPropertiesList.add(sai.getDisplayPrefixedName());
}
for (ServiceArtifactInfo sai: calledServiceSet) {
classPropertiesList.add(sai.getDisplayPrefixedName());
}
for (ServiceEcaArtifactInfo seai: callingServiceEcaSet) {
classPropertiesList.add(seai.getDisplayPrefixedName());
}
for (ServiceEcaArtifactInfo seai: calledServiceEcaSet) {
classPropertiesList.add(seai.getDisplayPrefixedName());
}
// attributes
List<Map<String, Object>> attributesList = new LinkedList<Map<String,Object>>();
topLevelMap.put("attributes", attributesList);
for (ModelParam param: this.modelService.getModelParamList()) {
Map<String, Object> attributeMap = new HashMap<String, Object>();
attributesList.add(attributeMap);
if (useMoreDetailedNames) {
attributeMap.put("name", param.getShortDisplayDescription());
} else {
attributeMap.put("name", param.name);
}
attributeMap.put("valueClassName", param.type);
attributeMap.put("externalType", param.type);
}
// relationships
List<Map<String, Object>> relationshipsMapList = new LinkedList<Map<String,Object>>();
for (ServiceArtifactInfo sai: callingServiceSet) {
Map<String, Object> relationshipMap = new HashMap<String, Object>();
relationshipsMapList.add(relationshipMap);
relationshipMap.put("name", sai.getDisplayPrefixedName());
relationshipMap.put("destination", sai.getDisplayPrefixedName());
relationshipMap.put("isToMany", "N");
relationshipMap.put("isMandatory", "Y");
// not sure if we can use these, or need them, for this type of diagram
//relationshipMap.put("joinSemantic", "EOInnerJoin");
//relationshipMap.put("joins", joinsMapList);
//joinsMap.put("sourceAttribute", keyMap.getFieldName());
//joinsMap.put("destinationAttribute", keyMap.getRelFieldName());
}
for (ServiceArtifactInfo sai: calledServiceSet) {
Map<String, Object> relationshipMap = new HashMap<String, Object>();
relationshipsMapList.add(relationshipMap);
relationshipMap.put("name", sai.getDisplayPrefixedName());
relationshipMap.put("destination", sai.getDisplayPrefixedName());
relationshipMap.put("isToMany", "Y");
relationshipMap.put("isMandatory", "Y");
// not sure if we can use these, or need them, for this type of diagram
//relationshipMap.put("joinSemantic", "EOInnerJoin");
//relationshipMap.put("joins", joinsMapList);
//joinsMap.put("sourceAttribute", keyMap.getFieldName());
//joinsMap.put("destinationAttribute", keyMap.getRelFieldName());
}
for (ServiceEcaArtifactInfo seai: callingServiceEcaSet) {
Map<String, Object> relationshipMap = new HashMap<String, Object>();
relationshipsMapList.add(relationshipMap);
relationshipMap.put("name", seai.getDisplayPrefixedName());
relationshipMap.put("destination", seai.getDisplayPrefixedName());
relationshipMap.put("isToMany", "N");
relationshipMap.put("isMandatory", "Y");
}
for (ServiceEcaArtifactInfo seai: calledServiceEcaSet) {
Map<String, Object> relationshipMap = new HashMap<String, Object>();
relationshipsMapList.add(relationshipMap);
relationshipMap.put("name", seai.getDisplayPrefixedName());
relationshipMap.put("destination", seai.getDisplayPrefixedName());
relationshipMap.put("isToMany", "Y");
relationshipMap.put("isMandatory", "Y");
}
if (relationshipsMapList.size() > 0) {
topLevelMap.put("relationships", relationshipsMapList);
}
return topLevelMap;
}
@Override
public boolean equals(Object obj) {
if (obj instanceof ServiceArtifactInfo) {
return this.modelService.name.equals(((ServiceArtifactInfo) obj).modelService.name);
} else {
return false;
}
}
}