1313import java .io .InputStream ;
1414import java .nio .charset .StandardCharsets ;
1515import java .text .SimpleDateFormat ;
16+ import java .util .ArrayList ;
1617import java .util .Date ;
1718import java .util .LinkedHashMap ;
19+ import java .util .LinkedHashSet ;
20+ import java .util .List ;
1821import java .util .Locale ;
1922import java .util .Map ;
23+ import java .util .Set ;
2024import javax .mail .MessagingException ;
2125
2226import org .apache .commons .cli .Option ;
2933import org .dspace .core .Context ;
3034import org .dspace .core .Email ;
3135import org .dspace .core .I18nUtil ;
36+ import org .dspace .core .factory .CoreServiceFactory ;
37+ import org .dspace .core .service .PluginService ;
3238import org .dspace .eperson .factory .EPersonServiceFactory ;
3339import org .dspace .eperson .service .EPersonService ;
3440import org .dspace .health .Check ;
35- import org .dspace .health .Report ;
3641import org .dspace .health .ReportInfo ;
3742import org .dspace .scripts .DSpaceRunnable ;
3843import org .dspace .services .ConfigurationService ;
@@ -56,22 +61,23 @@ public class HealthReport extends DSpaceRunnable<HealthReportScriptConfiguration
5661 /**
5762 * Checks to be performed.
5863 */
59- private static final LinkedHashMap <String , Check > checks = Report . checks ();
64+ private static final LinkedHashMap <String , Check > checks = getChecks ();
6065
6166 /**
62- * `-i `: Info , show help information.
67+ * `-h `: Help , show help information.
6368 */
64- private boolean info = false ;
69+ private boolean help = false ;
6570
6671 /**
6772 * `-e`: Email, send report to specified email address.
6873 */
6974 private String [] emails ;
7075
7176 /**
72- * `-c`: Check, perform only specific check by index (0-`getNumberOfChecks()`).
77+ * `-c`: Check, perform only specific checks by index (0-`getNumberOfChecks()`).
78+ * Supports multiple values.
7379 */
74- private int specificCheck = - 1 ;
80+ private List < Integer > specificChecks = new ArrayList <>() ;
7581
7682 /**
7783 * `-f`: For, specify the last N days to consider.
@@ -80,9 +86,9 @@ public class HealthReport extends DSpaceRunnable<HealthReportScriptConfiguration
8086 private int forLastNDays = configurationService .getIntProperty ("healthcheck.last_n_days" );
8187
8288 /**
83- * `-o `: Output , specify a file to save the report.
89+ * `-r `: Report , specify a file to save the report.
8490 */
85- private String fileName ;
91+ private String reportFile ;
8692
8793 @ Override
8894 public HealthReportScriptConfiguration getScriptConfiguration () {
@@ -93,52 +99,64 @@ public HealthReportScriptConfiguration getScriptConfiguration() {
9399 @ Override
94100 public void setup () throws ParseException {
95101 ePersonService = EPersonServiceFactory .getInstance ().getEPersonService ();
96- // `-i `: Info , show help information.
97- if (commandLine .hasOption ('i ' )) {
98- info = true ;
102+ // `-h `: Help , show help information.
103+ if (commandLine .hasOption ('h ' )) {
104+ help = true ;
99105 return ;
100106 }
101107
102108 // `-e`: Email, send report to specified email address.
103109 if (commandLine .hasOption ('e' )) {
104110 emails = commandLine .getOptionValues ('e' );
105- handler .logInfo ("\n Report sent to this email address: " + String .join (", " , emails ));
106111 }
107112
108- // `-c`: Check, perform only specific check by index (0-`getNumberOfChecks()`).
113+ // `-c`: Check, perform only specific checks by index (0-`getNumberOfChecks()`).
114+ // Supports multiple values e.g. -c 0 -c 3 -c 4
109115 if (commandLine .hasOption ('c' )) {
110- String checkOption = commandLine .getOptionValue ('c' );
111- try {
112- specificCheck = Integer .parseInt (checkOption );
113- if (specificCheck < 0 || specificCheck >= getNumberOfChecks ()) {
114- specificCheck = -1 ;
116+ String [] checkOptions = commandLine .getOptionValues ('c' );
117+ for (String checkOption : checkOptions ) {
118+ try {
119+ int checkIndex = Integer .parseInt (checkOption );
120+ if (checkIndex < 0 || checkIndex >= getNumberOfChecks ()) {
121+ handler .logError ("Invalid value for check: " + checkOption +
122+ ". Must be an integer from 0 to " + (getNumberOfChecks () - 1 ) + "." );
123+ throw new ParseException ("Invalid check index: " + checkOption );
124+ }
125+ specificChecks .add (checkIndex );
126+ } catch (NumberFormatException e ) {
127+ handler .logError ("Invalid value for check: '" + checkOption +
128+ "'. It has to be an integer number from 0 to " + (getNumberOfChecks () - 1 ) + "." );
129+ throw new ParseException ("Invalid check value: " + checkOption );
115130 }
116- } catch (NumberFormatException e ) {
117- log .info ("Invalid value for check. It has to be a number from the displayed range." );
118- return ;
119131 }
120132 }
121133
122- // `-f`: For, specify the last N days to consider.
134+ // `-f`: For, specify the last N days to consider. Must be a positive integer.
123135 if (commandLine .hasOption ('f' )) {
124136 String daysOption = commandLine .getOptionValue ('f' );
125137 try {
126138 forLastNDays = Integer .parseInt (daysOption );
139+ if (forLastNDays <= 0 ) {
140+ handler .logError ("Invalid value for -f: " + daysOption +
141+ ". Must be a positive integer (greater than 0)." );
142+ throw new ParseException ("Invalid -f value: " + daysOption );
143+ }
127144 } catch (NumberFormatException e ) {
128- log .info ("Invalid value for last N days. Argument f has to be a number." );
129- return ;
145+ handler .logError ("Invalid value for -f: '" + daysOption +
146+ "'. Must be a positive integer." );
147+ throw new ParseException ("Invalid -f value: " + daysOption );
130148 }
131149 }
132150
133- // `-o `: Output , specify a file to save the report.
134- if (commandLine .hasOption ('o ' )) {
135- fileName = commandLine .getOptionValue ('o ' );
151+ // `-r `: Report , specify a file to save the report.
152+ if (commandLine .hasOption ('r ' )) {
153+ reportFile = commandLine .getOptionValue ('r ' );
136154 }
137155 }
138156
139157 @ Override
140158 public void internalRun () throws Exception {
141- if (info ) {
159+ if (help ) {
142160 printHelp ();
143161 return ;
144162 }
@@ -149,15 +167,14 @@ public void internalRun() throws Exception {
149167 ReportInfo ri = new ReportInfo (this .forLastNDays );
150168
151169 StringBuilder sbReport = new StringBuilder ();
152- sbReport .append ("\n \n HEALTH REPORT:\n " );
153170
154171 int position = -1 ;
155172 JSONObject root = new JSONObject ();
156173 // Create the array
157174 JSONArray checksArray = new JSONArray ();
158- for (Map .Entry <String , Check > check_entry : Report . checks () .entrySet ()) {
175+ for (Map .Entry <String , Check > check_entry : checks .entrySet ()) {
159176 ++position ;
160- if (specificCheck != - 1 && specificCheck != position ) {
177+ if (! specificChecks . isEmpty () && ! specificChecks . contains ( position ) ) {
161178 continue ;
162179 }
163180
@@ -197,10 +214,14 @@ public void internalRun() throws Exception {
197214 reportResultService .update (context , reportResult );
198215 context .commit ();
199216
217+ // Prepend the header with the persisted report ID so users can refer to it later
218+ String finalReport = "\n \n HEALTH REPORT " + reportResult .getID () + ":\n " + sbReport .toString ();
219+
200220 // save output to file
201- if (fileName != null ) {
202- InputStream inputStream = toInputStream (sbReport .toString (), StandardCharsets .UTF_8 );
203- handler .writeFilestream (context , fileName , inputStream , "export" );
221+ if (reportFile != null ) {
222+ InputStream inputStream = toInputStream (finalReport , StandardCharsets .UTF_8 );
223+ handler .writeFilestream (context , reportFile , inputStream , "export" );
224+ context .commit ();
204225
205226 context .restoreAuthSystemState ();
206227
@@ -213,27 +234,34 @@ public void internalRun() throws Exception {
213234 for (String recipient : emails ) {
214235 e .addRecipient (recipient );
215236 }
216- e .addArgument (sbReport . toString () );
237+ e .addArgument (finalReport );
217238 e .send ();
239+ handler .logInfo ("Report sent to: " + String .join (", " , emails ));
218240 } catch (IOException | MessagingException e ) {
219241 log .error ("Error sending email:" , e );
242+ handler .logError ("Error sending email to " + String .join (", " , emails )
243+ + ": " + e .getMessage ());
220244 }
221245 }
222246
223- handler .logInfo (sbReport . toString () );
247+ handler .logInfo (finalReport );
224248 }
225249 }
226250
227251 @ Override
228252 public void printHelp () {
229- handler .logInfo ("\n \n INFORMATION\n This process creates a health report of your DSpace.\n " +
253+ int configuredForLastNDays = configurationService .getIntProperty ("healthcheck.last_n_days" );
254+ handler .logInfo ("\n \n HELP\n This process creates a health report of your DSpace.\n " +
230255 "You can choose from these available options:\n " +
231- " -i , --info Show help information\n " +
256+ " -h , --help Show help information\n " +
232257 " -e, --email Send report to specified email address\n " +
233- " -c, --check Perform only specific check by index (0-" + (getNumberOfChecks () - 1 ) + ")\n " +
234- " -f, --for Specify the last N days to consider\n " +
235- " -o, --output Specify a file to save the report\n \n " +
236- "If you want to execute only one check using -c, use check index:\n " + checksNamesToString () + "\n "
258+ " -c, --check Perform specific check(s) by index (0-" + (getNumberOfChecks () - 1 ) +
259+ "). Repeat the flag (e.g. -c 1 -c 3) to run multiple checks. " +
260+ "Default: All checks\n " +
261+ " -f, --for Specify the last N days to consider (positive integer). " +
262+ "Default: " + configuredForLastNDays + "\n " +
263+ " -r, --report Specify a file to save the report\n \n " +
264+ "Available checks:\n " + checksNamesToString () + "\n "
237265 );
238266 }
239267
@@ -242,13 +270,21 @@ public void printHelp() {
242270 * This method is used to print the options used for the report.
243271 */
244272 private String printCommandlineOptions () {
245- // Return key-value pairs of options
246273 StringBuilder options = new StringBuilder ();
274+ Set <String > processedOptions = new LinkedHashSet <>();
275+
247276 for (Option option : commandLine .getOptions ()) {
248277 String key = option .getOpt ();
249- String value = commandLine .getOptionValue (key );
250- if (value != null ) {
251- options .append (String .format (" -%s: %s\n " , key , value ));
278+ if (key == null || processedOptions .contains (key )) {
279+ continue ;
280+ }
281+ processedOptions .add (key );
282+
283+ String [] values = commandLine .getOptionValues (key );
284+ if (values != null && values .length > 0 ) {
285+ for (String value : values ) {
286+ options .append (String .format (" -%s: %s\n " , key , value ));
287+ }
252288 } else {
253289 options .append (String .format (" -%s\n " , key ));
254290 }
@@ -295,4 +331,25 @@ public static String getCheckName(int specificCheck) {
295331 }
296332 return null ; // should not happen
297333 }
334+
335+ /**
336+ * Create check list from configured healthcheck plugins.
337+ */
338+ private static LinkedHashMap <String , Check > getChecks () {
339+ LinkedHashMap <String , Check > loadedChecks = new LinkedHashMap <>();
340+ String [] checkNames = DSpaceServicesFactory .getInstance ().getConfigurationService ()
341+ .getArrayProperty ("healthcheck.checks" );
342+ PluginService pluginService = CoreServiceFactory .getInstance ().getPluginService ();
343+
344+ for (String checkName : checkNames ) {
345+ Check check = (Check ) pluginService .getNamedPlugin (Check .class , checkName );
346+ if (check != null ) {
347+ loadedChecks .put (checkName , check );
348+ } else {
349+ log .warn ("Could not find implementation for [{}]" , checkName );
350+ }
351+ }
352+
353+ return loadedChecks ;
354+ }
298355}
0 commit comments