001    /*
002     * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003     *
004     * Copyright (c) 2010 Oracle and/or its affiliates. All rights reserved.
005     *
006     * The contents of this file are subject to the terms of either the GNU
007     * General Public License Version 2 only ("GPL") or the Common Development
008     * and Distribution License("CDDL") (collectively, the "License").  You
009     * may not use this file except in compliance with the License.  You can
010     * obtain a copy of the License at
011     * https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html
012     * or packager/legal/LICENSE.txt.  See the License for the specific
013     * language governing permissions and limitations under the License.
014     *
015     * When distributing the software, include this License Header Notice in each
016     * file and include the License file at packager/legal/LICENSE.txt.
017     *
018     * GPL Classpath Exception:
019     * Oracle designates this particular file as subject to the "Classpath"
020     * exception as provided by Oracle in the GPL Version 2 section of the License
021     * file that accompanied this code.
022     *
023     * Modifications:
024     * If applicable, add the following below the License Header, with the fields
025     * enclosed by brackets [] replaced by your own identifying information:
026     * "Portions Copyright [year] [name of copyright owner]"
027     *
028     * Contributor(s):
029     * If you wish your version of this file to be governed by only the CDDL or
030     * only the GPL Version 2, indicate your decision by adding "[Contributor]
031     * elects to include this software in this distribution under the [CDDL or GPL
032     * Version 2] license."  If you don't indicate a single choice of license, a
033     * recipient has the option to distribute your version of this file under
034     * either the CDDL, the GPL Version 2 or to extend the choice of license to
035     * its licensees as provided above.  However, if you add GPL Version 2 code
036     * and therefore, elected the GPL Version 2 license, then the option applies
037     * only if the new code is made subject to such option by the copyright
038     * holder.
039     */
040    
041    package com.sun.enterprise.admin.cli.schemadoc;
042    
043    import java.io.File;
044    import java.io.FilenameFilter;
045    import java.io.IOException;
046    import java.io.InputStream;
047    import java.net.URI;
048    import java.util.ArrayList;
049    import java.util.Enumeration;
050    import java.util.HashMap;
051    import java.util.List;
052    import java.util.Map;
053    import java.util.jar.JarEntry;
054    import java.util.jar.JarFile;
055    
056    import com.sun.enterprise.config.serverbeans.Domain;
057    import org.glassfish.api.Param;
058    import org.glassfish.api.admin.AdminCommand;
059    import org.glassfish.api.admin.AdminCommandContext;
060    import org.glassfish.api.admin.ExecuteOn;
061    import org.glassfish.api.admin.RuntimeType;
062    import org.glassfish.config.support.CommandTarget;
063    import org.glassfish.config.support.TargetType;
064    import org.jvnet.hk2.annotations.Inject;
065    import org.jvnet.hk2.annotations.Scoped;
066    import org.jvnet.hk2.annotations.Service;
067    import org.jvnet.hk2.component.Habitat;
068    import org.jvnet.hk2.component.PerLookup;
069    import org.objectweb.asm.ClassReader;
070    
071    @Service(name = "generate-domain-schema")
072    @Scoped(PerLookup.class)
073    @ExecuteOn(value={RuntimeType.DAS})
074    @TargetType(value={CommandTarget.DOMAIN, CommandTarget.DAS, CommandTarget.STANDALONE_INSTANCE, CommandTarget.CLUSTER})
075    public class GenerateDomainSchema implements AdminCommand {
076        @Inject
077        private Domain domain;
078        @Inject
079        private Habitat habitat;
080        @Param(name = "format", defaultValue = "html", optional = true)
081        private String format;
082        File docDir;
083        private Map<String, ClassDef> classDefs = new HashMap<String, ClassDef>();
084        @Param(name = "showSubclasses", defaultValue = "false", optional = true)
085        private Boolean showSubclasses;
086        @Param(name = "showDeprecated", defaultValue = "false", optional = true)
087        private Boolean showDeprecated;
088    
089        public void execute(AdminCommandContext context) {
090            try {
091                URI uri = new URI(System.getProperty("com.sun.aas.instanceRootURI"));
092                docDir = new File(new File(uri), "config");
093                findClasses(classDefs, locateJarFiles(System.getProperty("com.sun.aas.installRoot") + "/modules"));
094    
095                getFormat().output(new Context(classDefs, docDir, showDeprecated, showSubclasses, Domain.class.getName()));
096                context.getActionReport().setMessage("Finished generating " + format + " documentation in " + docDir);
097            } catch (Exception e) {
098                e.printStackTrace();
099                throw new RuntimeException(e.getMessage(), e);
100            }
101        }
102    
103        private SchemaOutputFormat getFormat() {
104            return habitat.getComponent(SchemaOutputFormat.class, format);
105        }
106    
107        private List<JarFile> locateJarFiles(String modulesDir) throws IOException {
108            List<JarFile> result = new ArrayList<JarFile>();
109            final File[] files = new File(modulesDir).listFiles(new FilenameFilter() {
110                public boolean accept(File dir, String name) {
111                    return name.endsWith(".jar");
112                }
113            });
114            for (File f : files) {
115                result.add(new JarFile(f));
116            }
117            return result;
118        }
119    
120        private void findClasses(Map<String, ClassDef> classDefs, List<JarFile> jarFiles) throws IOException {
121            for (JarFile jf : jarFiles) {
122                for (Enumeration<JarEntry> entries = jf.entries(); entries.hasMoreElements();) {
123                    JarEntry entry = entries.nextElement();
124                    if (entry.getName().endsWith(".class")) {
125                        ClassDef def = parse(jf.getInputStream(entry));
126                        if (def != null) {
127                            classDefs.put(def.getDef(), def);
128                            for (String intf : def.getInterfaces()) {
129                                final ClassDef parent = classDefs.get(intf);
130                                if (parent != null) {
131                                    parent.addSubclass(def);
132                                }
133                            }
134                        }
135                    }
136                }
137            }
138            if (showSubclasses) {
139                for (ClassDef def : classDefs.values()) {
140                    for (String anInterface : def.getInterfaces()) {
141                        final ClassDef parent = classDefs.get(anInterface);
142                        if (parent != null) {
143                            parent.addSubclass(def);
144                        }
145                    }
146                }
147            }
148        }
149    
150        private ClassDef parse(InputStream is) throws IOException {
151            DocClassVisitor visitor = new DocClassVisitor(Boolean.valueOf(showDeprecated));
152            new ClassReader(is).accept(visitor, 0);
153            return visitor.isConfigured() ? visitor.getClassDef() : null;
154        }
155    
156        public static String toClassName(String value) {
157            int start = value.startsWith("()") ? 2 : 0;
158            start = value.substring(start).startsWith("L") ? start + 1 : start;
159            final int end = value.endsWith(";") ? value.length() - 1 : value.length();
160            return value
161                .substring(start, end)
162                .replace('/', '.');
163        }
164    }