1/*
2 * Copyright (c) 1997, 2017, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.  Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26package com.sun.tools.internal.ws.wscompile;
27
28import com.oracle.webservices.internal.api.databinding.WSDLResolver;
29import com.sun.tools.internal.ws.ToolVersion;
30import com.sun.tools.internal.ws.processor.modeler.annotation.WebServiceAp;
31import com.sun.tools.internal.ws.processor.modeler.wsdl.ConsoleErrorReporter;
32import com.sun.tools.internal.ws.resources.WscompileMessages;
33import com.sun.tools.internal.xjc.util.NullStream;
34import com.sun.xml.internal.txw2.TXW;
35import com.sun.xml.internal.txw2.TypedXmlWriter;
36import com.sun.xml.internal.txw2.annotation.XmlAttribute;
37import com.sun.xml.internal.txw2.annotation.XmlElement;
38import com.sun.xml.internal.txw2.output.StreamSerializer;
39import com.sun.xml.internal.ws.api.BindingID;
40import com.sun.xml.internal.ws.api.databinding.DatabindingConfig;
41import com.sun.xml.internal.ws.api.databinding.DatabindingFactory;
42import com.sun.xml.internal.ws.api.databinding.WSDLGenInfo;
43import com.sun.xml.internal.ws.api.server.Container;
44import com.sun.xml.internal.ws.api.wsdl.writer.WSDLGeneratorExtension;
45import com.sun.xml.internal.ws.binding.WebServiceFeatureList;
46import com.sun.xml.internal.ws.model.ExternalMetadataReader;
47import com.sun.xml.internal.ws.model.AbstractSEIModelImpl;
48import com.sun.xml.internal.ws.util.ServiceFinder;
49import org.xml.sax.SAXParseException;
50
51import javax.tools.DiagnosticCollector;
52import javax.tools.JavaCompiler;
53import javax.tools.JavaFileObject;
54import javax.tools.StandardJavaFileManager;
55import javax.tools.ToolProvider;
56import javax.xml.namespace.QName;
57import javax.xml.transform.Result;
58import javax.xml.transform.stream.StreamResult;
59import javax.xml.ws.Holder;
60import java.io.BufferedOutputStream;
61import java.io.File;
62import java.io.FileNotFoundException;
63import java.io.FileOutputStream;
64import java.io.IOException;
65import java.io.OutputStream;
66import java.io.PrintStream;
67import java.net.URLClassLoader;
68import java.util.ArrayList;
69import java.util.Collections;
70import java.util.HashMap;
71import java.util.List;
72import java.util.Map;
73
74/**
75 * @author Vivek Pandey
76 */
77
78/*
79 * All annotation types are supported.
80 */
81public class WsgenTool {
82    private final PrintStream out;
83    private final WsgenOptions options = new WsgenOptions();
84
85
86    public WsgenTool(OutputStream out, Container container) {
87        this.out = (out instanceof PrintStream) ? (PrintStream) out : new PrintStream(out);
88        this.container = container;
89    }
90
91
92    public WsgenTool(OutputStream out) {
93        this(out, null);
94    }
95
96    public boolean run(String[] args) {
97        final Listener listener = new Listener();
98        for (String arg : args) {
99            if (arg.equals("-version")) {
100                listener.message(
101                        WscompileMessages.WSGEN_VERSION(ToolVersion.VERSION.MAJOR_VERSION));
102                return true;
103            }
104            if (arg.equals("-fullversion")) {
105                listener.message(
106                        WscompileMessages.WSGEN_FULLVERSION(ToolVersion.VERSION.toString()));
107                return true;
108            }
109        }
110        try {
111            options.parseArguments(args);
112            options.validate();
113            if (!buildModel(options.endpoint.getName(), listener)) {
114                return false;
115            }
116        } catch (Options.WeAreDone done) {
117            usage(done.getOptions());
118        } catch (BadCommandLineException e) {
119            if (e.getMessage() != null) {
120                System.out.println(e.getMessage());
121                System.out.println();
122            }
123            usage(e.getOptions());
124            return false;
125        } catch (AbortException e) {
126            //error might have been reported
127        } finally {
128            if (!options.keep) {
129                options.removeGeneratedFiles();
130            }
131        }
132        return true;
133    }
134
135    private final Container container;
136
137    /**
138     *
139     * @param endpoint
140     * @param listener
141     * @return
142     * @throws BadCommandLineException
143     */
144    public boolean buildModel(String endpoint, Listener listener) throws BadCommandLineException {
145        final ErrorReceiverFilter errReceiver = new ErrorReceiverFilter(listener);
146
147        List<String> args = new ArrayList<>(6 + (options.nocompile ? 1 : 0)
148                + (options.encoding != null ? 2 : 0));
149
150        args.add("-d");
151        args.add(options.destDir.getAbsolutePath());
152        args.add("-classpath");
153        args.add(options.classpath);
154        args.add("-s");
155        args.add(options.sourceDir.getAbsolutePath());
156        if (options.nocompile) {
157            args.add("-proc:only");
158        }
159        if (options.encoding != null) {
160            args.add("-encoding");
161            args.add(options.encoding);
162        }
163
164        boolean addModules = true;
165        if (options.javacOptions != null) {
166            List<String> javacOptions = options.getJavacOptions(args, listener);
167            for (int i = 0; i < javacOptions.size(); i++) {
168                String opt = javacOptions.get(i);
169                if ("-source".equals(opt) && 9 >= getVersion(javacOptions.get(i + 1))) {
170                    addModules = false;
171                }
172                if ("-target".equals(opt) && 9 >= getVersion(javacOptions.get(i + 1))) {
173                    addModules = false;
174                }
175                if ("--release".equals(opt) && 9 >= getVersion(javacOptions.get(i + 1))) {
176                    addModules = false;
177                }
178                args.add(opt);
179            }
180        }
181        if (addModules) {
182            args.add("--add-modules");
183            args.add("java.xml.ws");
184        }
185
186        JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
187        if (compiler == null) {
188            out.println(WscompileMessages.WSCOMPILE_CANT_GET_COMPILER(property("java.home"), property("java.version"), property("java.vendor")));
189            return false;
190        }
191        DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<JavaFileObject>();
192        StandardJavaFileManager fileManager = compiler.getStandardFileManager(diagnostics, null, null);
193        JavaCompiler.CompilationTask task = compiler.getTask(
194                null,
195                fileManager,
196                diagnostics,
197                args,
198                Collections.singleton(endpoint.replaceAll("\\$", ".")),
199                null);
200        task.setProcessors(Collections.singleton(new WebServiceAp(options, out)));
201        boolean result = task.call();
202
203        if (!result) {
204            out.println(WscompileMessages.WSCOMPILE_ERROR(WscompileMessages.WSCOMPILE_COMPILATION_FAILED()));
205            return false;
206        }
207        if (options.genWsdl) {
208            DatabindingConfig config = new DatabindingConfig();
209
210            List<String> externalMetadataFileNames = options.externalMetadataFiles;
211            boolean disableXmlSecurity = options.disableXmlSecurity;
212            if (externalMetadataFileNames != null && externalMetadataFileNames.size() > 0) {
213                config.setMetadataReader(new ExternalMetadataReader(getExternalFiles(externalMetadataFileNames), null, null, true, disableXmlSecurity));
214            }
215
216            String tmpPath = options.destDir.getAbsolutePath() + File.pathSeparator + options.classpath;
217            ClassLoader classLoader = new URLClassLoader(Options.pathToURLs(tmpPath),
218                    this.getClass().getClassLoader());
219            Class<?> endpointClass;
220            try {
221                endpointClass = classLoader.loadClass(endpoint);
222            } catch (ClassNotFoundException e) {
223                throw new BadCommandLineException(WscompileMessages.WSGEN_CLASS_NOT_FOUND(endpoint));
224            }
225
226            BindingID bindingID = options.getBindingID(options.protocol);
227            if (!options.protocolSet) {
228                bindingID = BindingID.parse(endpointClass);
229            }
230            WebServiceFeatureList wsfeatures = new WebServiceFeatureList(endpointClass);
231//            RuntimeModeler rtModeler = new RuntimeModeler(endpointClass, options.serviceName, bindingID, wsfeatures.toArray());
232//            rtModeler.setClassLoader(classLoader);
233            if (options.portName != null)
234                config.getMappingInfo().setPortName(options.portName);//rtModeler.setPortName(options.portName);
235//            AbstractSEIModelImpl rtModel = rtModeler.buildRuntimeModel();
236
237            DatabindingFactory fac = DatabindingFactory.newInstance();
238            config.setEndpointClass(endpointClass);
239            config.getMappingInfo().setServiceName(options.serviceName);
240            config.setFeatures(wsfeatures.toArray());
241            config.setClassLoader(classLoader);
242            config.getMappingInfo().setBindingID(bindingID);
243            com.sun.xml.internal.ws.db.DatabindingImpl rt = (com.sun.xml.internal.ws.db.DatabindingImpl) fac.createRuntime(config);
244
245            final File[] wsdlFileName = new File[1]; // used to capture the generated WSDL file.
246            final Map<String, File> schemaFiles = new HashMap<>();
247
248            WSDLGenInfo wsdlGenInfo = new WSDLGenInfo();
249            wsdlGenInfo.setSecureXmlProcessingDisabled(disableXmlSecurity);
250
251            wsdlGenInfo.setWsdlResolver(
252                    new WSDLResolver() {
253                        private File toFile(String suggestedFilename) {
254                            return new File(options.nonclassDestDir, suggestedFilename);
255                        }
256
257                        private Result toResult(File file) {
258                            Result result;
259                            try {
260                                result = new StreamResult(new FileOutputStream(file));
261                                result.setSystemId(file.getPath().replace('\\', '/'));
262                            } catch (FileNotFoundException e) {
263                                errReceiver.error(e);
264                                return null;
265                            }
266                            return result;
267                        }
268
269                        @Override
270                        public Result getWSDL(String suggestedFilename) {
271                            File f = toFile(suggestedFilename);
272                            wsdlFileName[0] = f;
273                            return toResult(f);
274                        }
275
276                        public Result getSchemaOutput(String namespace, String suggestedFilename) {
277                            if (namespace == null)
278                                return null;
279                            File f = toFile(suggestedFilename);
280                            schemaFiles.put(namespace, f);
281                            return toResult(f);
282                        }
283
284                        @Override
285                        public Result getAbstractWSDL(Holder<String> filename) {
286                            return toResult(toFile(filename.value));
287                        }
288
289                        @Override
290                        public Result getSchemaOutput(String namespace, Holder<String> filename) {
291                            return getSchemaOutput(namespace, filename.value);
292                        }
293                        // TODO pass correct impl's class name
294                    });
295
296            wsdlGenInfo.setContainer(container);
297            wsdlGenInfo.setExtensions(ServiceFinder.find(WSDLGeneratorExtension.class).toArray());
298            wsdlGenInfo.setInlineSchemas(options.inlineSchemas);
299            rt.generateWSDL(wsdlGenInfo);
300
301
302            if (options.wsgenReport != null)
303                generateWsgenReport(endpointClass, (AbstractSEIModelImpl) rt.getModel(), wsdlFileName[0], schemaFiles);
304        }
305        return true;
306    }
307
308    private String property(String key) {
309        try {
310            String property = System.getProperty(key);
311            return property != null ? property : "UNKNOWN";
312        } catch (SecurityException ignored) {
313            return "UNKNOWN";
314        }
315    }
316
317    private List<File> getExternalFiles(List<String> exts) {
318        List<File> files = new ArrayList<>();
319        for (String ext : exts) {
320            // first try absolute path ...
321            File file = new File(ext);
322            if (!file.exists()) {
323                // then relative path ...
324                file = new File(options.sourceDir.getAbsolutePath() + File.separator + ext);
325            }
326            files.add(file);
327        }
328        return files;
329    }
330
331    /**
332     * Generates a small XML file that captures the key activity of wsgen,
333     * so that test harness can pick up artifacts.
334     */
335    private void generateWsgenReport(Class<?> endpointClass, AbstractSEIModelImpl rtModel, File wsdlFile, Map<String, File> schemaFiles) {
336        try {
337            ReportOutput.Report report = TXW.create(ReportOutput.Report.class,
338                    new StreamSerializer(new BufferedOutputStream(new FileOutputStream(options.wsgenReport))));
339
340            report.wsdl(wsdlFile.getAbsolutePath());
341            ReportOutput.writeQName(rtModel.getServiceQName(), report.service());
342            ReportOutput.writeQName(rtModel.getPortName(), report.port());
343            ReportOutput.writeQName(rtModel.getPortTypeName(), report.portType());
344
345            report.implClass(endpointClass.getName());
346
347            for (Map.Entry<String, File> e : schemaFiles.entrySet()) {
348                ReportOutput.Schema s = report.schema();
349                s.ns(e.getKey());
350                s.location(e.getValue().getAbsolutePath());
351            }
352
353            report.commit();
354        } catch (IOException e) {
355            // this is code for the test, so we can be lousy in the error handling
356            throw new Error(e);
357        }
358    }
359
360    private float getVersion(String s) {
361        return Float.parseFloat(s);
362    }
363
364    /**
365     * "Namespace" for code needed to generate the report file.
366     */
367    static class ReportOutput {
368        @XmlElement("report")
369        interface Report extends TypedXmlWriter {
370            @XmlElement
371            void wsdl(String file); // location of WSDL
372
373            @XmlElement
374            QualifiedName portType();
375
376            @XmlElement
377            QualifiedName service();
378
379            @XmlElement
380            QualifiedName port();
381
382            /**
383             * Name of the class that has {@link javax.jws.WebService}.
384             */
385            @XmlElement
386            void implClass(String name);
387
388            @XmlElement
389            Schema schema();
390        }
391
392        interface QualifiedName extends TypedXmlWriter {
393            @XmlAttribute
394            void uri(String ns);
395
396            @XmlAttribute
397            void localName(String localName);
398        }
399
400        interface Schema extends TypedXmlWriter {
401            @XmlAttribute
402            void ns(String ns);
403
404            @XmlAttribute
405            void location(String filePath);
406        }
407
408        private static void writeQName(QName n, QualifiedName w) {
409            w.uri(n.getNamespaceURI());
410            w.localName(n.getLocalPart());
411        }
412    }
413
414    protected void usage(Options options) {
415        // Just don't see any point in passing WsgenOptions
416        // BadCommandLineException also shouldn't have options
417        if (options == null)
418            options = this.options;
419        if (options instanceof WsgenOptions) {
420            System.out.println(WscompileMessages.WSGEN_HELP("WSGEN",
421                    ((WsgenOptions)options).protocols,
422                    ((WsgenOptions)options).nonstdProtocols.keySet()));
423            System.out.println(WscompileMessages.WSGEN_USAGE_EXTENSIONS());
424            System.out.println(WscompileMessages.WSGEN_USAGE_EXAMPLES());
425        }
426    }
427
428    class Listener extends WsimportListener {
429        ConsoleErrorReporter cer = new ConsoleErrorReporter(out == null ? new PrintStream(new NullStream()) : out);
430
431        @Override
432        public void generatedFile(String fileName) {
433            message(fileName);
434        }
435
436        @Override
437        public void message(String msg) {
438            out.println(msg);
439        }
440
441        @Override
442        public void error(SAXParseException exception) {
443            cer.error(exception);
444        }
445
446        @Override
447        public void fatalError(SAXParseException exception) {
448            cer.fatalError(exception);
449        }
450
451        @Override
452        public void warning(SAXParseException exception) {
453            cer.warning(exception);
454        }
455
456        @Override
457        public void info(SAXParseException exception) {
458            cer.info(exception);
459        }
460    }
461}
462