View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.maven.cling.invoker.mvnenc;
20  
21  import java.io.InterruptedIOException;
22  import java.util.ArrayList;
23  import java.util.List;
24  import java.util.Map;
25  
26  import org.apache.maven.api.cli.mvnenc.EncryptInvoker;
27  import org.apache.maven.api.cli.mvnenc.EncryptInvokerRequest;
28  import org.apache.maven.api.cli.mvnenc.EncryptOptions;
29  import org.apache.maven.cli.CLIReportingUtils;
30  import org.apache.maven.cling.invoker.LookupInvoker;
31  import org.apache.maven.cling.invoker.ProtoLookup;
32  import org.jline.consoleui.prompt.ConsolePrompt;
33  import org.jline.reader.LineReader;
34  import org.jline.reader.LineReaderBuilder;
35  import org.jline.reader.UserInterruptException;
36  import org.jline.terminal.Terminal;
37  import org.jline.utils.AttributedString;
38  import org.jline.utils.AttributedStringBuilder;
39  import org.jline.utils.AttributedStyle;
40  import org.jline.utils.Colors;
41  import org.jline.utils.OSUtils;
42  
43  /**
44   * Encrypt invoker implementation, when Encrypt CLI is being run. System uses ClassWorld launcher, and class world
45   * instance is passed in via "enhanced" main method. Hence, this class expects fully setup ClassWorld via constructor.
46   */
47  public class DefaultEncryptInvoker
48          extends LookupInvoker<EncryptOptions, EncryptInvokerRequest, DefaultEncryptInvoker.LocalContext>
49          implements EncryptInvoker {
50  
51      @SuppressWarnings("VisibilityModifier")
52      public static class LocalContext
53              extends LookupInvokerContext<EncryptOptions, EncryptInvokerRequest, DefaultEncryptInvoker.LocalContext> {
54          protected LocalContext(DefaultEncryptInvoker invoker, EncryptInvokerRequest invokerRequest) {
55              super(invoker, invokerRequest);
56          }
57  
58          public Map<String, Goal> goals;
59  
60          public List<AttributedString> header;
61          public AttributedStyle style;
62          public LineReader reader;
63          public ConsolePrompt prompt;
64  
65          public void addInHeader(String text) {
66              addInHeader(AttributedStyle.DEFAULT, text);
67          }
68  
69          public void addInHeader(AttributedStyle style, String text) {
70              AttributedStringBuilder asb = new AttributedStringBuilder();
71              asb.style(style).append(text);
72              header.add(asb.toAttributedString());
73          }
74      }
75  
76      public DefaultEncryptInvoker(ProtoLookup protoLookup) {
77          super(protoLookup);
78      }
79  
80      @Override
81      protected int execute(LocalContext context) throws Exception {
82          return doExecute(context);
83      }
84  
85      @Override
86      protected LocalContext createContext(EncryptInvokerRequest invokerRequest) {
87          return new LocalContext(this, invokerRequest);
88      }
89  
90      @Override
91      protected void lookup(LocalContext context) {
92          context.goals = context.lookup.lookupMap(Goal.class);
93      }
94  
95      public static final int OK = 0; // OK
96      public static final int ERROR = 1; // "generic" error
97      public static final int BAD_OPERATION = 2; // bad user input or alike
98      public static final int CANCELED = 3; // user canceled
99  
100     protected int doExecute(LocalContext context) throws Exception {
101         try {
102             if (!context.interactive) {
103                 context.terminal.writer().println("This tool works only in interactive mode!");
104                 context.terminal
105                         .writer()
106                         .println("Tool purpose is to configure password management on developer workstations.");
107                 context.terminal
108                         .writer()
109                         .println(
110                                 "Note: Generated configuration can be moved/copied to headless environments, if configured as such.");
111                 return BAD_OPERATION;
112             }
113 
114             context.header = new ArrayList<>();
115             context.style = new AttributedStyle();
116             context.addInHeader(
117                     context.style.italic().bold().foreground(Colors.rgbColor("green")),
118                     "Maven Encryption " + CLIReportingUtils.showVersionMinimal());
119             context.addInHeader("Tool for secure password management on workstations.");
120             context.addInHeader("This tool is part of Apache Maven 4 distribution.");
121             context.addInHeader("");
122 
123             Thread executeThread = Thread.currentThread();
124             context.terminal.handle(Terminal.Signal.INT, signal -> executeThread.interrupt());
125             ConsolePrompt.UiConfig config;
126             if (context.terminal.getType().equals(Terminal.TYPE_DUMB)
127                     || context.terminal.getType().equals(Terminal.TYPE_DUMB_COLOR)) {
128                 context.terminal.writer().println(context.terminal.getName() + ": " + context.terminal.getType());
129                 throw new IllegalStateException("Dumb terminal detected.\nThis tool requires real terminal to work!\n"
130                         + "Note: On Windows Jansi or JNA library must be included in classpath.");
131             } else if (OSUtils.IS_WINDOWS) {
132                 config = new ConsolePrompt.UiConfig(">", "( )", "(x)", "( )");
133             } else {
134                 config = new ConsolePrompt.UiConfig("❯", "◯ ", "◉ ", "◯ ");
135             }
136             config.setCancellableFirstPrompt(true);
137 
138             context.reader =
139                     LineReaderBuilder.builder().terminal(context.terminal).build();
140             context.prompt = new ConsolePrompt(context.reader, context.terminal, config);
141 
142             if (context.invokerRequest.options().goals().isEmpty()
143                     || context.invokerRequest.options().goals().get().size() != 1) {
144                 return badGoalsErrorMessage("No goal or multiple goals specified, specify only one goal.", context);
145             }
146 
147             String goalName = context.invokerRequest.options().goals().get().get(0);
148             Goal goal = context.goals.get(goalName);
149 
150             if (goal == null) {
151                 return badGoalsErrorMessage("Unknown goal: " + goalName, context);
152             }
153 
154             return goal.execute(context);
155         } catch (InterruptedException | InterruptedIOException | UserInterruptException e) {
156             context.terminal.writer().println("Goal canceled by user.");
157             return CANCELED;
158         } catch (Exception e) {
159             if (context.invokerRequest.options().showErrors().orElse(false)) {
160                 context.terminal.writer().println(e.getMessage());
161                 e.printStackTrace(context.terminal.writer());
162             } else {
163                 context.terminal.writer().println(e.getMessage());
164             }
165             return ERROR;
166         } finally {
167             context.terminal.writer().flush();
168         }
169     }
170 
171     protected int badGoalsErrorMessage(String message, LocalContext context) {
172         context.terminal.writer().println(message);
173         context.terminal.writer().println("Supported goals are: " + String.join(", ", context.goals.keySet()));
174         context.terminal.writer().println("Use -h to display help.");
175         return BAD_OPERATION;
176     }
177 }