Skip to content

Commit e1859aa

Browse files
authored
Enable model extensions (#359)
* Allow alternative ModelFactory implementations * Table name for ModelClasses are now defined explicitely instead of implicitely from class name * Enable copying of additional extension specific files when creating project file structure with "-wiz" option. * Describe steps to extend Solicitor data model in user guide. * clean up * Add new entries to solicitor.dict * Update release notes.
1 parent 11830e7 commit e1859aa

11 files changed

Lines changed: 329 additions & 31 deletions
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package com.devonfw.tools.solicitor;
2+
3+
import java.lang.reflect.InvocationTargetException;
4+
5+
import org.springframework.beans.factory.annotation.Value;
6+
import org.springframework.context.annotation.Bean;
7+
import org.springframework.context.annotation.Configuration;
8+
9+
import com.devonfw.tools.solicitor.common.SolicitorRuntimeException;
10+
import com.devonfw.tools.solicitor.model.ModelFactory;
11+
import com.devonfw.tools.solicitor.model.impl.ModelFactoryImpl;
12+
13+
/**
14+
* Configuration class to instantiate the ModelFactory implementation class configured via property
15+
* "solicitor.modelfactory-classname". The configured class must be a subclass of {@link ModelFactoryImpl} and must have
16+
* a default constructor. The instantiated ModelFactory is registered as a Spring bean and can thus be injected in other
17+
* classes.
18+
*
19+
*/
20+
@Configuration
21+
public class ModelFactoryConfiguration {
22+
23+
private String modelFactoryClassName;
24+
25+
@Value("${solicitor.modelfactory-classname}")
26+
public void setModelFactoryClassName(String modelFactoryClassName) {
27+
28+
this.modelFactoryClassName = modelFactoryClassName;
29+
}
30+
31+
@Bean
32+
public ModelFactory modelFactory() {
33+
34+
try {
35+
ModelFactoryImpl modelFactory = (ModelFactoryImpl) Class.forName(this.modelFactoryClassName).getConstructor()
36+
.newInstance();
37+
return modelFactory;
38+
} catch (InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException
39+
| NoSuchMethodException | SecurityException | ClassNotFoundException e) {
40+
throw new SolicitorRuntimeException("Could not instantiate ModelFactory implementation class", e);
41+
}
42+
}
43+
44+
}

core/src/main/java/com/devonfw/tools/solicitor/common/ResourceToFileCopier.java

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,12 @@ public class ResourceToFileCopier {
3636
@Value("${solicitor.extension-userguide-target}")
3737
String extensionUserguideTarget;
3838

39+
@Value("${solicitor.extension-additionalprojectfiles-sources}")
40+
String[] extensionAdditionalProjectFilesSources;
41+
42+
@Value("${solicitor.extension-additionalprojectfiles-targets}")
43+
String[] extensionAdditionalProjectFilesTargets;
44+
3945
/**
4046
* Represents a single copy operation to be performed
4147
*/
@@ -226,7 +232,8 @@ public String copyReourcesToFile(ResourceGroup resourceGroup, String targetDir)
226232
returnString = "solicitor.cdx.json";
227233
break;
228234
case PROJECT_FILES:
229-
new CopySequenceBuilder().withCopyOperation("classpath:starters/solicitor.cfg", "new_project/solicitor.cfg")
235+
CopySequenceBuilder csb2 = new CopySequenceBuilder()
236+
.withCopyOperation("classpath:starters/solicitor.cfg", "new_project/solicitor.cfg")
230237
.withCopyOperation("classpath:starters/input/licenses_starter.xml",
231238
"new_project/input/licenses_starter.xml")
232239
.withCopyOperation("classpath:starters/rules/LegalEvaluationProject.xls",
@@ -241,8 +248,9 @@ public String copyReourcesToFile(ResourceGroup resourceGroup, String targetDir)
241248
"new_project/rules/LicenseSelectionProject.xls")
242249
.withCopyOperation("classpath:starters/rules/MultiLicenseSelectionProject.xls",
243250
"new_project/rules/MultiLicenseSelectionProject.xls")
244-
.withCopyOperation("classpath:starters/readme.txt", "new_project/readme.txt")
245-
.replaceInTarget("new_project", targetDir).execute();
251+
.withCopyOperation("classpath:starters/readme.txt", "new_project/readme.txt");
252+
optionallyAddExtensionFiles(csb2);
253+
csb2.replaceInTarget("new_project", targetDir).execute();
246254
returnString = targetDir + "/readme.txt";
247255
break;
248256
case FULL_BASE_CONFIG:
@@ -282,6 +290,31 @@ private void optionallyAddExtensionUserguide(CopySequenceBuilder csb) {
282290
}
283291
}
284292

293+
/**
294+
* Optionally add specific files from an existing Extension.
295+
*
296+
* @param csb builder to add the files to
297+
*/
298+
private void optionallyAddExtensionFiles(CopySequenceBuilder csb) {
299+
300+
int sourcesCount = this.extensionAdditionalProjectFilesSources != null
301+
? this.extensionAdditionalProjectFilesSources.length
302+
: 0;
303+
int targetsCount = this.extensionAdditionalProjectFilesTargets != null
304+
? this.extensionAdditionalProjectFilesTargets.length
305+
: 0;
306+
307+
if (sourcesCount != targetsCount) {
308+
throw new SolicitorRuntimeException(
309+
"Properties solicitor.extension-additionalprojectfiles-sources and solicitor.extension-additionalprojectfiles-targets "
310+
+ "need to contain the same number of comma separated values");
311+
}
312+
for (int i = 0; i < sourcesCount; i++) {
313+
csb.withCopyOperation(this.extensionAdditionalProjectFilesSources[i],
314+
this.extensionAdditionalProjectFilesTargets[i]);
315+
}
316+
}
317+
285318
/**
286319
* Copies a single resource to the file system.
287320
*

core/src/main/java/com/devonfw/tools/solicitor/model/ModelFactory.java

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import java.util.Collection;
44

5+
import com.devonfw.tools.solicitor.model.impl.AbstractModelObject;
56
import com.devonfw.tools.solicitor.model.inventory.ApplicationComponent;
67
import com.devonfw.tools.solicitor.model.inventory.NormalizedLicense;
78
import com.devonfw.tools.solicitor.model.inventory.RawLicense;
@@ -11,7 +12,7 @@
1112
/**
1213
* An abstract factory for creating objects of the Solicitor data model.
1314
*/
14-
public abstract class ModelFactory {
15+
public interface ModelFactory {
1516

1617
/**
1718
* Returns the collection of all objects of the model tree starting from the given
@@ -21,56 +22,64 @@ public abstract class ModelFactory {
2122
* @return a collection of {@link Object}s representing all objects of the model
2223
* @param modelRoot a {@link ModelRoot} object.
2324
*/
24-
public abstract Collection<Object> getAllModelObjects(ModelRoot modelRoot);
25+
Collection<Object> getAllModelObjects(ModelRoot modelRoot);
26+
27+
/**
28+
* Determine the table name for the given {@link AbstractModelObject} subtype.
29+
*
30+
* @param modelClass a class name of the {@link AbstractModelObject} subtype
31+
* @return the table name for storing this to the reporting database
32+
*/
33+
String determineTableName(Class<? extends AbstractModelObject> modelClass);
2534

2635
/**
2736
* Creates a new {@link Application}
2837
*
2938
* @return the new instance
3039
*/
31-
public abstract Application newApplication();
40+
Application newApplication();
3241

3342
/**
3443
* Creates a new {@link ApplicationComponent}
3544
*
3645
* @return the new instance
3746
*/
38-
public abstract ApplicationComponent newApplicationComponent();
47+
ApplicationComponent newApplicationComponent();
3948

4049
/**
4150
* Creates a new {@link Engagement}
4251
*
4352
* @return the new instance
4453
*/
45-
public abstract Engagement newEngagement();
54+
Engagement newEngagement();
4655

4756
/**
4857
* Creates a {@link ModelRoot}.
4958
*
5059
* @return the new instance
5160
*/
52-
public abstract ModelRoot newModelRoot();
61+
ModelRoot newModelRoot();
5362

5463
/**
5564
* Creates a {@link NormalizedLicense}.
5665
*
5766
* @return the new instance
5867
*/
59-
public abstract NormalizedLicense newNormalizedLicense();
68+
NormalizedLicense newNormalizedLicense();
6069

6170
/**
6271
* Creates a {@link NormalizedLicense}.
6372
*
6473
* @param rawLicense a {@link RawLicense} object.
6574
* @return the new instance
6675
*/
67-
public abstract NormalizedLicense newNormalizedLicense(RawLicense rawLicense);
76+
NormalizedLicense newNormalizedLicense(RawLicense rawLicense);
6877

6978
/**
7079
* Creates a new {@link RawLicense}
7180
*
7281
* @return the new instance
7382
*/
74-
public abstract RawLicense newRawLicense();
83+
RawLicense newRawLicense();
7584

7685
}

core/src/main/java/com/devonfw/tools/solicitor/model/impl/ModelFactoryImpl.java

Lines changed: 66 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,16 @@
55

66
import java.util.Collection;
77
import java.util.Collections;
8+
import java.util.HashMap;
89
import java.util.Map;
910
import java.util.TreeMap;
1011

1112
import org.slf4j.Logger;
1213
import org.slf4j.LoggerFactory;
1314
import org.springframework.beans.factory.annotation.Autowired;
14-
import org.springframework.stereotype.Component;
1515

1616
import com.devonfw.tools.solicitor.SolicitorVersion;
17+
import com.devonfw.tools.solicitor.common.SolicitorRuntimeException;
1718
import com.devonfw.tools.solicitor.common.content.InMemoryMapContentProvider;
1819
import com.devonfw.tools.solicitor.common.content.web.WebContent;
1920
import com.devonfw.tools.solicitor.model.ModelFactory;
@@ -32,16 +33,64 @@
3233
* Implementation of the {@link ModelFactory} interface. All model object created by this factory will be extensions of
3334
* {@link AbstractModelObject}.
3435
*/
35-
@Component
36-
public class ModelFactoryImpl extends ModelFactory {
36+
public class ModelFactoryImpl implements ModelFactory {
3737
private static final Logger LOG = LoggerFactory.getLogger(ModelFactoryImpl.class);
3838

39-
@Autowired
4039
private InMemoryMapContentProvider<WebContent> licenseContentProvider;
4140

42-
@Autowired
4341
private SolicitorVersion solicitorVersion;
4442

43+
/** Map for looking up the table name for each model object class. */
44+
private Map<Class<? extends AbstractModelObject>, String> tableNameMap = new HashMap<>();
45+
46+
/**
47+
* The constructor.
48+
*/
49+
public ModelFactoryImpl() {
50+
51+
super();
52+
registerModelClass(ModelRootImpl.class, "MODELROOT");
53+
registerModelClass(EngagementImpl.class, "ENGAGEMENT");
54+
registerModelClass(ApplicationImpl.class, "APPLICATION");
55+
registerModelClass(ApplicationComponentImpl.class, "APPLICATIONCOMPONENT");
56+
registerModelClass(RawLicenseImpl.class, "RAWLICENSE");
57+
registerModelClass(NormalizedLicenseImpl.class, "NORMALIZEDLICENSE");
58+
}
59+
60+
/**
61+
* This method registers the given model class with the given table name. This allows to use custom model classes
62+
* (e.g. extensions of the default model classes) with custom (or preexisting) table names.
63+
*
64+
* @param modelClass the model class to register
65+
* @param tableName the table name to register for this model class
66+
*/
67+
public void registerModelClass(Class<? extends AbstractModelObject> modelClass, String tableName) {
68+
69+
this.tableNameMap.put(modelClass, tableName);
70+
}
71+
72+
@Autowired
73+
public void setLicenseContentProvider(InMemoryMapContentProvider<WebContent> licenseContentProvider) {
74+
75+
this.licenseContentProvider = licenseContentProvider;
76+
}
77+
78+
public InMemoryMapContentProvider<WebContent> getLicenseContentProvider() {
79+
80+
return this.licenseContentProvider;
81+
}
82+
83+
@Autowired
84+
public void setSolicitorVersion(SolicitorVersion solicitorVersion) {
85+
86+
this.solicitorVersion = solicitorVersion;
87+
}
88+
89+
public SolicitorVersion getSolicitorVersion() {
90+
91+
return this.solicitorVersion;
92+
}
93+
4594
/** {@inheritDoc} */
4695
@Override
4796
public Collection<Object> getAllModelObjects(ModelRoot modelRoot) {
@@ -71,6 +120,18 @@ public Collection<Object> getAllModelObjects(ModelRoot modelRoot) {
71120
return Collections.unmodifiableCollection(resultMap.values());
72121
}
73122

123+
/** {@inheritDoc} */
124+
@Override
125+
public String determineTableName(Class<? extends AbstractModelObject> modelClass) {
126+
127+
String tableName = this.tableNameMap.get(modelClass);
128+
if (tableName == null) {
129+
throw new SolicitorRuntimeException(
130+
"No table name defined in ModelFactory for model class '" + modelClass.getName() + "'");
131+
}
132+
return tableName;
133+
}
134+
74135
/** {@inheritDoc} */
75136
@Override
76137
public ApplicationImpl newApplication() {

core/src/main/java/com/devonfw/tools/solicitor/writer/ResultDatabaseFactory.java

Lines changed: 3 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ public class ResultDatabaseFactory {
6767
private void createTable(AbstractModelObject modelObject) {
6868

6969
StringBuilder sb = new StringBuilder();
70-
String name = determineTableName(modelObject.getClass());
70+
String name = modelFactory.determineTableName(modelObject.getClass());
7171
sb.append("create table ").append(name).append(" ( ");
7272
for (String fields : modelObject.getHeadElements()) {
7373
sb.append("\"").append(fields).append("\" ").append("LONGVARCHAR, ");
@@ -93,17 +93,6 @@ private void createTable(AbstractModelObject modelObject) {
9393

9494
}
9595

96-
/**
97-
* Determine the table name for the given {@link AbstractModelObject} subtype.
98-
*
99-
* @param tableClass a class name of the {@link AbstractModelObject} subtype
100-
* @return the table name for storing this to the reporting database
101-
*/
102-
public String determineTableName(Class<? extends AbstractModelObject> tableClass) {
103-
104-
return tableClass.getSimpleName().toUpperCase().replace("IMPL", "");
105-
}
106-
10796
/**
10897
* Drop the database table which corresponds to the given {@link AbstractModelObject}.
10998
*
@@ -112,7 +101,7 @@ public String determineTableName(Class<? extends AbstractModelObject> tableClass
112101
private void dropExistingTable(Class<? extends AbstractModelObject> oneTable) {
113102

114103
StringBuilder sb = new StringBuilder();
115-
String name = determineTableName(oneTable);
104+
String name = modelFactory.determineTableName(oneTable);
116105
sb.append("drop table ").append(name).append(";");
117106
LOG.debug("Dropping Reporting table '{}'", name);
118107
String sql = sb.toString();
@@ -270,7 +259,7 @@ public void saveToDatabase(AbstractModelObject modelObject) {
270259
createTable(modelObject);
271260
}
272261
StringBuilder sb = new StringBuilder();
273-
String name = determineTableName(modelObject.getClass());
262+
String name = modelFactory.determineTableName(modelObject.getClass());
274263
sb.append("insert into ").append(name).append(" values ( ");
275264
for (String fields : modelObject.getDataElements()) {
276265
sb.append("?").append(", ");

core/src/main/resources/application.properties

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,9 @@ packageurls.nuget.repobaseurl=https://www.nuget.org/api/v2/package/
3333
# Base URL for accessing cran packages
3434
packageurls.cran.repobaseurl=https://cran.r-project.org/
3535

36+
# The ModelFactory implementation class
37+
solicitor.modelfactory-classname=com.devonfw.tools.solicitor.model.impl.ModelFactoryImpl
38+
3639
# the URL of the base config file
3740
solicitor.base-config-url=classpath:com/devonfw/tools/solicitor/config/solicitor_base.cfg
3841

@@ -114,3 +117,8 @@ solicitor.extension-message-2=
114117
solicitor.extension-userguide-source=
115118
solicitor.extension-userguide-target=
116119

120+
# source and target location of additional files to be copied from the extension if a project file structure
121+
# is created using the "-wiz" option. (comma separated list of source and target paths)
122+
solicitor.extension-additionalprojectfiles-sources=
123+
solicitor.extension-additionalprojectfiles-targets=
124+

0 commit comments

Comments
 (0)