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.tools.plugin.generator;
20  
21  import java.io.File;
22  import java.io.IOException;
23  import java.io.OutputStreamWriter;
24  import java.io.Writer;
25  import java.net.URI;
26  import java.util.LinkedHashMap;
27  import java.util.LinkedHashSet;
28  import java.util.List;
29  import java.util.Map;
30  import java.util.Set;
31  
32  import org.apache.maven.plugin.descriptor.MojoDescriptor;
33  import org.apache.maven.plugin.descriptor.Parameter;
34  import org.apache.maven.plugin.descriptor.PluginDescriptor;
35  import org.apache.maven.plugin.descriptor.Requirement;
36  import org.apache.maven.project.MavenProject;
37  import org.apache.maven.tools.plugin.ExtendedMojoDescriptor;
38  import org.apache.maven.tools.plugin.PluginDescriptorHelper;
39  import org.apache.maven.tools.plugin.PluginToolsRequest;
40  import org.apache.maven.tools.plugin.javadoc.JavadocLinkGenerator;
41  import org.apache.maven.tools.plugin.util.PluginUtils;
42  import org.codehaus.plexus.util.StringUtils;
43  import org.codehaus.plexus.util.io.CachingOutputStream;
44  import org.codehaus.plexus.util.xml.PrettyPrintXMLWriter;
45  import org.codehaus.plexus.util.xml.XMLWriter;
46  import org.slf4j.Logger;
47  import org.slf4j.LoggerFactory;
48  
49  import static java.nio.charset.StandardCharsets.UTF_8;
50  
51  /**
52   * Serializes
53   * <ol>
54   * <li>a standard <a href="/ref/current/maven-plugin-api/plugin.html">Maven Plugin Descriptor XML file</a></li>
55   * <li>a descriptor containing a limited set of elements for {@link PluginHelpGenerator}</li>
56   * <li>an enhanced descriptor containing HTML values for some elements (instead of plain text as for the other two)
57   * for {@code org.apache.maven.plugin.plugin.report.GoalRenderer}</li>
58   * </ol>
59   * from a given in-memory descriptor. The in-memory descriptor acting as source is supposed to contain XHTML values
60   * for description elements.
61   */
62  public class PluginDescriptorFilesGenerator implements Generator {
63      private static final Logger LOG = LoggerFactory.getLogger(PluginDescriptorFilesGenerator.class);
64  
65      /**
66       * The type of the plugin descriptor file
67       */
68      enum DescriptorType {
69          STANDARD,
70          LIMITED_FOR_HELP_MOJO,
71          XHTML
72      }
73  
74      @Override
75      public void execute(File destinationDirectory, PluginToolsRequest request) throws GeneratorException {
76          try {
77              // write standard plugin.xml descriptor
78              File f = new File(destinationDirectory, "plugin.xml");
79              writeDescriptor(f, request, DescriptorType.STANDARD);
80  
81              // write plugin-help.xml help-descriptor (containing only a limited set of attributes)
82              MavenProject mavenProject = request.getProject();
83              f = new File(destinationDirectory, PluginHelpGenerator.getPluginHelpPath(mavenProject));
84              writeDescriptor(f, request, DescriptorType.LIMITED_FOR_HELP_MOJO);
85  
86              // write enhanced plugin-enhanced.xml descriptor (containing some XHTML values)
87              f = getEnhancedDescriptorFilePath(mavenProject);
88              writeDescriptor(f, request, DescriptorType.XHTML);
89          } catch (IOException e) {
90              throw new GeneratorException(e.getMessage(), e);
91          }
92      }
93  
94      public static File getEnhancedDescriptorFilePath(MavenProject project) {
95          return new File(project.getBuild().getDirectory(), "plugin-enhanced.xml");
96      }
97  
98      private String getVersion() {
99          Package p = this.getClass().getPackage();
100         String version = (p == null) ? null : p.getSpecificationVersion();
101         return (version == null) ? "SNAPSHOT" : version;
102     }
103 
104     public void writeDescriptor(File destinationFile, PluginToolsRequest request, DescriptorType type)
105             throws IOException {
106         PluginDescriptor pluginDescriptor = request.getPluginDescriptor();
107 
108         if (!destinationFile.getParentFile().exists()) {
109             destinationFile.getParentFile().mkdirs();
110         }
111 
112         try (Writer writer = new OutputStreamWriter(new CachingOutputStream(destinationFile), UTF_8)) {
113             XMLWriter w = new PrettyPrintXMLWriter(writer, UTF_8.name(), null);
114 
115             final String additionalInfo;
116             switch (type) {
117                 case LIMITED_FOR_HELP_MOJO:
118                     additionalInfo = " (for help mojo with limited elements)";
119                     break;
120                 case XHTML:
121                     additionalInfo = " (enhanced XHTML version (used for plugin:report))";
122                     break;
123                 default:
124                     additionalInfo = "";
125                     break;
126             }
127             w.writeMarkup("\n<!-- Generated by maven-plugin-tools " + getVersion() + additionalInfo + "-->\n\n");
128 
129             w.startElement("plugin");
130 
131             GeneratorUtils.element(w, "name", pluginDescriptor.getName());
132 
133             GeneratorUtils.element(w, "description", pluginDescriptor.getDescription());
134 
135             GeneratorUtils.element(w, "groupId", pluginDescriptor.getGroupId());
136 
137             GeneratorUtils.element(w, "artifactId", pluginDescriptor.getArtifactId());
138 
139             GeneratorUtils.element(w, "version", pluginDescriptor.getVersion());
140 
141             GeneratorUtils.element(w, "goalPrefix", pluginDescriptor.getGoalPrefix());
142 
143             if (type != DescriptorType.LIMITED_FOR_HELP_MOJO) {
144                 GeneratorUtils.element(w, "isolatedRealm", String.valueOf(pluginDescriptor.isIsolatedRealm()));
145 
146                 GeneratorUtils.element(
147                         w, "inheritedByDefault", String.valueOf(pluginDescriptor.isInheritedByDefault()));
148 
149                 if (StringUtils.isNotBlank(PluginDescriptorHelper.getRequiredJavaVersion(pluginDescriptor))) {
150                     GeneratorUtils.element(
151                             w, "requiredJavaVersion", PluginDescriptorHelper.getRequiredJavaVersion(pluginDescriptor));
152                 }
153                 if (StringUtils.isNotBlank(pluginDescriptor.getRequiredMavenVersion())) {
154                     GeneratorUtils.element(w, "requiredMavenVersion", pluginDescriptor.getRequiredMavenVersion());
155                 }
156             }
157 
158             w.startElement("mojos");
159 
160             final JavadocLinkGenerator javadocLinkGenerator;
161             if (request.getInternalJavadocBaseUrl() != null
162                     || (request.getExternalJavadocBaseUrls() != null
163                             && !request.getExternalJavadocBaseUrls().isEmpty())) {
164                 javadocLinkGenerator = new JavadocLinkGenerator(
165                         request.getInternalJavadocBaseUrl(),
166                         request.getInternalJavadocVersion(),
167                         request.getExternalJavadocBaseUrls(),
168                         request.getSettings());
169             } else {
170                 javadocLinkGenerator = null;
171             }
172             if (pluginDescriptor.getMojos() != null) {
173                 List<MojoDescriptor> descriptors = pluginDescriptor.getMojos();
174 
175                 PluginUtils.sortMojos(descriptors);
176 
177                 for (MojoDescriptor descriptor : descriptors) {
178                     processMojoDescriptor(descriptor, w, type, javadocLinkGenerator);
179                 }
180             }
181 
182             w.endElement();
183 
184             if (type != DescriptorType.LIMITED_FOR_HELP_MOJO) {
185                 GeneratorUtils.writeDependencies(w, pluginDescriptor);
186             }
187 
188             w.endElement();
189 
190             writer.flush();
191         }
192     }
193 
194     /**
195      * @param type
196      * @param containsXhtmlValue
197      * @param text
198      * @return the normalized text value (i.e. potentially converted to XHTML)
199      */
200     private static String getTextValue(DescriptorType type, boolean containsXhtmlValue, String text) {
201         final String xhtmlText;
202         if (!containsXhtmlValue) // text comes from legacy extractor
203         {
204             xhtmlText = GeneratorUtils.makeHtmlValid(text);
205         } else {
206             xhtmlText = text;
207         }
208         if (type != DescriptorType.XHTML) {
209             return new HtmlToPlainTextConverter().convert(text);
210         } else {
211             return xhtmlText;
212         }
213     }
214 
215     @SuppressWarnings("deprecation")
216     protected void processMojoDescriptor(
217             MojoDescriptor mojoDescriptor,
218             XMLWriter w,
219             DescriptorType type,
220             JavadocLinkGenerator javadocLinkGenerator) {
221         boolean containsXhtmlTextValues = mojoDescriptor instanceof ExtendedMojoDescriptor
222                 && ((ExtendedMojoDescriptor) mojoDescriptor).containsXhtmlTextValues();
223 
224         w.startElement("mojo");
225 
226         // ----------------------------------------------------------------------
227         //
228         // ----------------------------------------------------------------------
229 
230         w.startElement("goal");
231         w.writeText(mojoDescriptor.getGoal());
232         w.endElement();
233 
234         // ----------------------------------------------------------------------
235         //
236         // ----------------------------------------------------------------------
237 
238         String description = mojoDescriptor.getDescription();
239 
240         if (description != null && !description.isEmpty()) {
241             w.startElement("description");
242             w.writeText(getTextValue(type, containsXhtmlTextValues, mojoDescriptor.getDescription()));
243             w.endElement();
244         }
245 
246         // ----------------------------------------------------------------------
247         //
248         // ----------------------------------------------------------------------
249 
250         if (StringUtils.isNotEmpty(mojoDescriptor.isDependencyResolutionRequired())) {
251             GeneratorUtils.element(w, "requiresDependencyResolution", mojoDescriptor.isDependencyResolutionRequired());
252         }
253 
254         // ----------------------------------------------------------------------
255         //
256         // ----------------------------------------------------------------------
257 
258         GeneratorUtils.element(w, "requiresDirectInvocation", String.valueOf(mojoDescriptor.isDirectInvocationOnly()));
259 
260         // ----------------------------------------------------------------------
261         //
262         // ----------------------------------------------------------------------
263 
264         GeneratorUtils.element(w, "requiresProject", String.valueOf(mojoDescriptor.isProjectRequired()));
265 
266         // ----------------------------------------------------------------------
267         //
268         // ----------------------------------------------------------------------
269 
270         GeneratorUtils.element(w, "requiresReports", String.valueOf(mojoDescriptor.isRequiresReports()));
271 
272         // ----------------------------------------------------------------------
273         //
274         // ----------------------------------------------------------------------
275 
276         GeneratorUtils.element(w, "aggregator", String.valueOf(mojoDescriptor.isAggregator()));
277 
278         // ----------------------------------------------------------------------
279         //
280         // ----------------------------------------------------------------------
281 
282         GeneratorUtils.element(w, "requiresOnline", String.valueOf(mojoDescriptor.isOnlineRequired()));
283 
284         // ----------------------------------------------------------------------
285         //
286         // ----------------------------------------------------------------------
287 
288         GeneratorUtils.element(w, "inheritedByDefault", String.valueOf(mojoDescriptor.isInheritedByDefault()));
289 
290         // ----------------------------------------------------------------------
291         //
292         // ----------------------------------------------------------------------
293 
294         if (StringUtils.isNotEmpty(mojoDescriptor.getPhase())) {
295             GeneratorUtils.element(w, "phase", mojoDescriptor.getPhase());
296         }
297 
298         // ----------------------------------------------------------------------
299         //
300         // ----------------------------------------------------------------------
301 
302         if (StringUtils.isNotEmpty(mojoDescriptor.getExecutePhase())) {
303             GeneratorUtils.element(w, "executePhase", mojoDescriptor.getExecutePhase());
304         }
305 
306         if (StringUtils.isNotEmpty(mojoDescriptor.getExecuteGoal())) {
307             GeneratorUtils.element(w, "executeGoal", mojoDescriptor.getExecuteGoal());
308         }
309 
310         if (StringUtils.isNotEmpty(mojoDescriptor.getExecuteLifecycle())) {
311             GeneratorUtils.element(w, "executeLifecycle", mojoDescriptor.getExecuteLifecycle());
312         }
313 
314         // ----------------------------------------------------------------------
315         //
316         // ----------------------------------------------------------------------
317 
318         w.startElement("implementation");
319         w.writeText(mojoDescriptor.getImplementation());
320         w.endElement();
321 
322         // ----------------------------------------------------------------------
323         //
324         // ----------------------------------------------------------------------
325 
326         w.startElement("language");
327         w.writeText(mojoDescriptor.getLanguage());
328         w.endElement();
329 
330         // ----------------------------------------------------------------------
331         //
332         // ----------------------------------------------------------------------
333 
334         if (StringUtils.isNotEmpty(mojoDescriptor.getComponentConfigurator())) {
335             w.startElement("configurator");
336             w.writeText(mojoDescriptor.getComponentConfigurator());
337             w.endElement();
338         }
339 
340         // ----------------------------------------------------------------------
341         //
342         // ----------------------------------------------------------------------
343 
344         if (StringUtils.isNotEmpty(mojoDescriptor.getComponentComposer())) {
345             w.startElement("composer");
346             w.writeText(mojoDescriptor.getComponentComposer());
347             w.endElement();
348         }
349 
350         // ----------------------------------------------------------------------
351         //
352         // ----------------------------------------------------------------------
353 
354         w.startElement("instantiationStrategy");
355         w.writeText(mojoDescriptor.getInstantiationStrategy());
356         w.endElement();
357 
358         // ----------------------------------------------------------------------
359         // Strategy for handling repeated reference to mojo in
360         // the calculated (decorated, resolved) execution stack
361         // ----------------------------------------------------------------------
362         w.startElement("executionStrategy");
363         w.writeText(mojoDescriptor.getExecutionStrategy());
364         w.endElement();
365 
366         // ----------------------------------------------------------------------
367         //
368         // ----------------------------------------------------------------------
369 
370         if (mojoDescriptor.getSince() != null) {
371             w.startElement("since");
372 
373             if (StringUtils.isEmpty(mojoDescriptor.getSince())) {
374                 w.writeText("No version given");
375             } else {
376                 w.writeText(mojoDescriptor.getSince());
377             }
378 
379             w.endElement();
380         }
381 
382         // ----------------------------------------------------------------------
383         //
384         // ----------------------------------------------------------------------
385 
386         if (mojoDescriptor.getDeprecated() != null) {
387             w.startElement("deprecated");
388 
389             if (StringUtils.isEmpty(mojoDescriptor.getDeprecated())) {
390                 w.writeText("No reason given");
391             } else {
392                 w.writeText(getTextValue(type, containsXhtmlTextValues, mojoDescriptor.getDeprecated()));
393             }
394 
395             w.endElement();
396         }
397 
398         // ----------------------------------------------------------------------
399         // Extended (3.0) descriptor
400         // ----------------------------------------------------------------------
401 
402         if (mojoDescriptor instanceof ExtendedMojoDescriptor) {
403             ExtendedMojoDescriptor extendedMojoDescriptor = (ExtendedMojoDescriptor) mojoDescriptor;
404             if (extendedMojoDescriptor.getDependencyCollectionRequired() != null) {
405                 GeneratorUtils.element(
406                         w, "requiresDependencyCollection", extendedMojoDescriptor.getDependencyCollectionRequired());
407             }
408 
409             GeneratorUtils.element(w, "threadSafe", String.valueOf(extendedMojoDescriptor.isThreadSafe()));
410 
411             boolean v4Api = extendedMojoDescriptor.isV4Api();
412             if (v4Api) {
413                 GeneratorUtils.element(w, "v4Api", String.valueOf(v4Api));
414             }
415         }
416 
417         // ----------------------------------------------------------------------
418         // Parameters
419         // ----------------------------------------------------------------------
420 
421         List<Parameter> parameters = mojoDescriptor.getParameters();
422 
423         w.startElement("parameters");
424 
425         Map<String, Requirement> requirements = new LinkedHashMap<>();
426 
427         Set<Parameter> configuration = new LinkedHashSet<>();
428 
429         if (parameters != null) {
430             if (type == DescriptorType.LIMITED_FOR_HELP_MOJO) {
431                 PluginUtils.sortMojoParameters(parameters);
432             }
433 
434             for (Parameter parameter : parameters) {
435                 String expression = getExpression(parameter);
436 
437                 if ((expression != null && !expression.isEmpty()) && expression.startsWith("${component.")) {
438                     // treat it as a component...a requirement, in other words.
439 
440                     // remove "component." plus expression delimiters
441                     String role = expression.substring("${component.".length(), expression.length() - 1);
442 
443                     String roleHint = null;
444 
445                     int posRoleHintSeparator = role.indexOf('#');
446                     if (posRoleHintSeparator > 0) {
447                         roleHint = role.substring(posRoleHintSeparator + 1);
448 
449                         role = role.substring(0, posRoleHintSeparator);
450                     }
451 
452                     // TODO: remove deprecated expression
453                     requirements.put(parameter.getName(), new Requirement(role, roleHint));
454                 } else if (parameter.getRequirement() != null) {
455                     requirements.put(parameter.getName(), parameter.getRequirement());
456                 }
457                 // don't show readonly parameters in help
458                 else if (type != DescriptorType.LIMITED_FOR_HELP_MOJO || parameter.isEditable()) {
459                     // treat it as a normal parameter.
460 
461                     w.startElement("parameter");
462 
463                     GeneratorUtils.element(w, "name", parameter.getName());
464 
465                     if (parameter.getAlias() != null) {
466                         GeneratorUtils.element(w, "alias", parameter.getAlias());
467                     }
468 
469                     writeParameterType(w, type, javadocLinkGenerator, parameter, mojoDescriptor.getGoal());
470 
471                     if (parameter.getSince() != null) {
472                         w.startElement("since");
473 
474                         if (StringUtils.isEmpty(parameter.getSince())) {
475                             w.writeText("No version given");
476                         } else {
477                             w.writeText(parameter.getSince());
478                         }
479 
480                         w.endElement();
481                     }
482 
483                     if (parameter.getDeprecated() != null) {
484                         if (StringUtils.isEmpty(parameter.getDeprecated())) {
485                             GeneratorUtils.element(w, "deprecated", "No reason given");
486                         } else {
487                             GeneratorUtils.element(
488                                     w,
489                                     "deprecated",
490                                     getTextValue(type, containsXhtmlTextValues, parameter.getDeprecated()));
491                         }
492                     }
493 
494                     if (parameter.getImplementation() != null) {
495                         GeneratorUtils.element(w, "implementation", parameter.getImplementation());
496                     }
497 
498                     GeneratorUtils.element(w, "required", Boolean.toString(parameter.isRequired()));
499 
500                     GeneratorUtils.element(w, "editable", Boolean.toString(parameter.isEditable()));
501 
502                     GeneratorUtils.element(
503                             w, "description", getTextValue(type, containsXhtmlTextValues, parameter.getDescription()));
504 
505                     if (StringUtils.isNotEmpty(parameter.getDefaultValue())
506                             || StringUtils.isNotEmpty(parameter.getExpression())) {
507                         configuration.add(parameter);
508                     }
509 
510                     w.endElement();
511                 }
512             }
513         }
514 
515         w.endElement();
516 
517         // ----------------------------------------------------------------------
518         // Configuration
519         // ----------------------------------------------------------------------
520 
521         if (!configuration.isEmpty()) {
522             w.startElement("configuration");
523 
524             for (Parameter parameter : configuration) {
525                 if (type == DescriptorType.LIMITED_FOR_HELP_MOJO && !parameter.isEditable()) {
526                     // don't show readonly parameters in help
527                     continue;
528                 }
529 
530                 w.startElement(parameter.getName());
531 
532                 // strip type by parameter type (generics) information
533                 String parameterType = StringUtils.chomp(parameter.getType(), "<");
534                 if (parameterType != null && !parameterType.isEmpty()) {
535                     w.addAttribute("implementation", parameterType);
536                 }
537 
538                 if (parameter.getDefaultValue() != null) {
539                     w.addAttribute("default-value", parameter.getDefaultValue());
540                 }
541 
542                 if (StringUtils.isNotEmpty(parameter.getExpression())) {
543                     w.writeText(parameter.getExpression());
544                 }
545 
546                 w.endElement();
547             }
548 
549             w.endElement();
550         }
551 
552         // ----------------------------------------------------------------------
553         // Requirements
554         // ----------------------------------------------------------------------
555 
556         if (!requirements.isEmpty() && type != DescriptorType.LIMITED_FOR_HELP_MOJO) {
557             w.startElement("requirements");
558 
559             for (Map.Entry<String, Requirement> entry : requirements.entrySet()) {
560                 String key = entry.getKey();
561                 Requirement requirement = entry.getValue();
562 
563                 w.startElement("requirement");
564 
565                 GeneratorUtils.element(w, "role", requirement.getRole());
566 
567                 if (StringUtils.isNotEmpty(requirement.getRoleHint())) {
568                     GeneratorUtils.element(w, "role-hint", requirement.getRoleHint());
569                 }
570 
571                 GeneratorUtils.element(w, "field-name", key);
572 
573                 w.endElement();
574             }
575 
576             w.endElement();
577         }
578 
579         w.endElement();
580     }
581 
582     /**
583      * Writes parameter type information and potentially also the related javadoc URL.
584      *
585      * @param w
586      * @param type
587      * @param javadocLinkGenerator
588      * @param parameter
589      * @param goal
590      */
591     protected void writeParameterType(
592             XMLWriter w,
593             DescriptorType type,
594             JavadocLinkGenerator javadocLinkGenerator,
595             Parameter parameter,
596             String goal) {
597         String parameterType = parameter.getType();
598 
599         if (type == DescriptorType.STANDARD) {
600             // strip type by parameter type (generics) information for standard plugin descriptor
601             parameterType = StringUtils.chomp(parameterType, "<");
602         }
603         GeneratorUtils.element(w, "type", parameterType);
604 
605         if (type == DescriptorType.XHTML && javadocLinkGenerator != null) {
606             // skip primitives which never has javadoc
607             if (parameter.getType().indexOf('.') == -1) {
608                 LOG.debug("Javadoc URLs are not available for primitive types like {}", parameter.getType());
609             } else {
610                 try {
611                     URI javadocUrl = getJavadocUrlForType(javadocLinkGenerator, parameterType);
612                     GeneratorUtils.element(w, "typeJavadocUrl", javadocUrl.toString());
613                 } catch (IllegalArgumentException e) {
614                     LOG.warn(
615                             "Could not get javadoc URL for type {} of parameter {} from goal {}: {}",
616                             parameter.getType(),
617                             parameter.getName(),
618                             goal,
619                             e.getMessage());
620                 }
621             }
622         }
623     }
624 
625     private static String extractBinaryNameForJavadoc(String type) {
626         final String binaryName;
627         int startOfParameterType = type.indexOf("<");
628         if (startOfParameterType != -1) {
629             // parse parameter type
630             String mainType = type.substring(0, startOfParameterType);
631 
632             // some heuristics here
633             String[] parameterTypes = type.substring(startOfParameterType + 1, type.lastIndexOf(">"))
634                     .split(",\\s*");
635             switch (parameterTypes.length) {
636                 case 1: // if only one parameter type, assume collection, first parameter type is most interesting
637                     binaryName = extractBinaryNameForJavadoc(parameterTypes[0]);
638                     break;
639                 case 2: // if two parameter types assume map, second parameter type is most interesting
640                     binaryName = extractBinaryNameForJavadoc(parameterTypes[1]);
641                     break;
642                 default:
643                     // all other cases link to main type
644                     binaryName = mainType;
645             }
646         } else {
647             binaryName = type;
648         }
649         return binaryName;
650     }
651 
652     static URI getJavadocUrlForType(JavadocLinkGenerator javadocLinkGenerator, String type) {
653         return javadocLinkGenerator.createLink(extractBinaryNameForJavadoc(type));
654     }
655 
656     /**
657      * Get the expression value, eventually surrounding it with <code>${ }</code>.
658      *
659      * @param parameter the parameter
660      * @return the expression value
661      */
662     private String getExpression(Parameter parameter) {
663         String expression = parameter.getExpression();
664         if (StringUtils.isNotBlank(expression) && !expression.contains("${")) {
665             expression = "${" + expression.trim() + "}";
666             parameter.setExpression(expression);
667         }
668         return expression;
669     }
670 }