All Downloads are FREE. Search and download functionalities are using the official Maven repository.

jmms.plugins.Templates Maven / Gradle / Ivy

There is a newer version: 0.6.2
Show newest version
/*
 *  Copyright 2018 the original author or authors.
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *        http://www.apache.org/licenses/LICENSE-2.0
 *
 *   Unless required by applicable law or agreed to in writing, software
 *   distributed under the License is distributed on an "AS IS" BASIS,
 *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *   See the License for the specific language governing permissions and
 *   limitations under the License.
 */

package jmms.plugins;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import leap.lang.*;
import leap.lang.json.JSON;
import leap.lang.net.Urls;
import leap.lang.resource.Resource;
import leap.lang.resource.Resources;
import org.trimou.Mustache;
import org.trimou.engine.MustacheEngine;
import org.trimou.engine.MustacheEngineBuilder;
import org.trimou.engine.locale.FixedLocaleSupport;
import org.trimou.engine.locator.FileSystemTemplateLocator;
import org.trimou.engine.locator.PathTemplateLocator;
import org.trimou.engine.resolver.Mapper;
import org.trimou.exception.MustacheException;
import org.trimou.exception.MustacheProblem;
import org.trimou.handlebars.*;
import org.trimou.handlebars.i18n.ResourceBundleHelper;

import java.io.*;
import java.net.URLDecoder;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import static jmms.plugins.SwaggerConstants.*;

class Templates {

    static final String ROOT_CLASS_PATH = "jmms/plugins/swaggerdoc/";

    private static final Map CACHE = new ConcurrentHashMap<>();
    private static final String MARKDOWN = "markdown";

    static Templates markdown(String dir, String locale) {
        return of(dir, MARKDOWN, locale);
    }

    static Templates of(String dir, String language, String locale) {
        String key = language + "_" + locale;

        Templates templates = CACHE.get(key);
        if (null != templates) {
            return templates;
        }

        ClassPathTemplateLocator locator1 = new ClassPathTemplateLocator(1, ROOT_CLASS_PATH + language, "hbs");
        FileSystemTemplateLocator locator2 = new FileSystemTemplateLocatorEx(2, dir + "_templates/", "hbs");

        Set names = new LinkedHashSet<>();
        names.addAll(locator1.getAllIdentifiers());
        names.addAll(locator2.getAllIdentifiers());

        ResourceBundleHelper i18n = new I18nHelper(dir, ROOT_CLASS_PATH + "messages", locale);

        MustacheEngine engine = MustacheEngineBuilder
                .newBuilder()
                .addTemplateLocator(locator1)
                .addTemplateLocator(locator2)
                .setLocaleSupport(FixedLocaleSupport.from(Locales.forName(locale)))
                .registerHelper("i18n", i18n)
                .registerHelper("each", new EachHelperEx())
                .registerHelper("ifeq", new EqualsHelper())
                .registerHelper("ifneq", new EqualsHelper(true))
                .registerHelper("join", new JoinHelper())
                .registerHelper("json", new JsonHelper())
                .registerHelper("lower_", new LowerUnderscoreHelper())
                .registerHelper("basename", new BaseNameHelper(false))
                .registerHelper("lower_basename", new BaseNameHelper(true))
                .registerHelper("swagger_model", new SwaggerModelHelper())
                .registerHelper("swagger_path", new SwaggerPathHelper())
                .build();

        templates = new Templates(engine, names);

        //CACHE.put(language, templates);
        return templates;
    }

    private final MustacheEngine engine;
    private final List all = new ArrayList<>();

    private Templates(MustacheEngine engine, Set names) {
        this.engine = engine;
        for (String name : names) {
            if (!name.startsWith("@")) {
                all.add(engine.getMustache(name));
            }
        }
    }

    public List all() {
        return all;
    }

    private static final class MapMapper extends HashMap implements Mapper {
        @Override
        public Object get(String key) {
            return this.get((Object) key);
        }
    }

    private static class ClassPathTemplateLocator extends PathTemplateLocator {

        private final Map allIdentifiers;

        public ClassPathTemplateLocator(int priority, String rootPath, String suffix) {
            super(priority, rootPath, suffix);
            this.allIdentifiers = scanAllIdentifiers(rootPath, suffix);
        }

        @Override
        protected String constructVirtualPath(String source) {
            throw new UnsupportedOperationException();
        }

        @Override
        public Reader locate(String name) {
            String cp = allIdentifiers.get(name);
            if (null == cp) {
                return null;
            }
            return Resources.getResource(cp).getInputStreamReader(Charsets.forName(getDefaultFileEncoding()));
        }

        @Override
        public Set getAllIdentifiers() {
            return allIdentifiers.keySet();
        }

        private static Map scanAllIdentifiers(String rootPath, String suffix) {
            Map map = new LinkedHashMap<>();

            for (Resource resource : Resources.scan("classpath*:" + rootPath + "/**/*." + suffix)) {
                String name = Strings.removeStart(resource.getClasspath(), rootPath + "/");
                name = Strings.removeEnd(name, "." + suffix);
                map.put(name, "classpath:" + resource.getClasspath());
            }

            return map;
        }
    }

    private static class FileSystemTemplateLocatorEx extends FileSystemTemplateLocator {
        public FileSystemTemplateLocatorEx(int priority, String rootPath, String suffix) {
            super(priority, rootPath, suffix);
        }

        @Override
        public Reader locateRealPath(String realPath) {
            try {
                File template = new File(new File(getRootPath()), addSuffix(realPath));
                if (!template.exists()) {
                    return null;
                }
                return new InputStreamReader(new FileInputStream(template), getDefaultFileEncoding());
            } catch (FileNotFoundException e) {
                return null;
            } catch (UnsupportedEncodingException e) {
                throw new MustacheException(MustacheProblem.TEMPLATE_LOADING_ERROR, e);
            }
        }

        @Override
        protected void checkRootDir() {
        }
    }

    private static class EachHelperEx extends EachHelper {
        @Override
        protected int processParameter(Object param, Options options, int index, int size, boolean isOmitMeta) {
            if (param instanceof Map) {
                return processMap((Map) param, options, index, size, isOmitMeta);
            }
            try {
                return super.processParameter(param, options, index, size, isOmitMeta);
            } catch (MustacheException e) {
                return processMap(Beans.toMap(param), options, index, size, isOmitMeta);
            }
        }

        private int processMap(Map map, Options options, int index, int size, boolean isOmitMeta) {
            for (Object entry : map.entrySet()) {
                nextEntry(options, (Map.Entry) entry, isOmitMeta);
            }
            return index;
        }

        private void nextEntry(Options options, Map.Entry entry, boolean isOmitMeta) {
            if (!isOmitMeta) {
                MapMapper meta = new MapMapper();
                meta.put("@key", entry.getKey());
                options.push(meta);
            }

            options.pushAnd(entry.getValue()).fnAnd().pop();

            if (!isOmitMeta) {
                options.pop();
            }
        }
    }

    private static final class I18nHelper extends ResourceBundleHelper {
        private final Map props = new HashMap<>();

        public I18nHelper(String dir, String defaultBaseName, String locale) {
            super(defaultBaseName);

            File file = new File(dir + "_i18n/messages.properties");
            if (file.exists()) {
                this.props.putAll(Props.load(file).toMap());
            }

            file = new File(dir + "_i18n/messages_" + locale + ".properties");
            if (file.exists()) {
                this.props.putAll(Props.load(file).toMap());
            }
        }

        @Override
        public void execute(Options options) {
            if (!props.isEmpty() && options.getParameters().size() > 0) {
                Object v = options.getParameters().get(0);
                if (null == v) {
                    return;
                }
                String key = v.toString();
                if (props.containsKey(key)) {
                    options.append(props.get(key));
                    return;
                }
            }
            super.execute(options);
        }
    }

    private static final class JsonHelper extends BasicValueHelper {
        @Override
        public void execute(Options options) {
            options.append(JSON.stringify(options.getParameters().get(0)));
        }
    }

    private static final class LowerUnderscoreHelper extends BasicValueHelper {
        @Override
        public void execute(Options options) {
            if (options.getParameters().size() > 0) {
                Object v = options.getParameters().get(0);
                if (null != v) {
                    options.append(Strings.lowerUnderscore(v.toString()));
                }
            }
        }
    }

    private static final class BaseNameHelper extends BasicValueHelper {

        private boolean lowerUnderscore;

        public BaseNameHelper(boolean lowerUnderscore) {
            this.lowerUnderscore = lowerUnderscore;
        }

        @Override
        public void execute(Options options) {
            if (options.getParameters().size() > 0) {
                Object v = options.getParameters().get(0);
                if (null == v) {
                    return;
                }

                String s = v.toString();
                int index = s.lastIndexOf('/');
                if (index < 0) {
                    index = s.lastIndexOf('.');
                }
                if (index > 0) {
                    s = s.substring(index + 1);
                }
                if (lowerUnderscore) {
                    s = Strings.lowerUnderscore(s);
                }
                options.append(s);
            }
        }
    }

    private static final class SwaggerModelHelper extends BasicValueHelper {

        private final static String DESCRIPTION_REG = "(#//.*//#)";

        @Override
        public void execute(Options options) {
            Map m = (Map) options.getParameters().get(0);

            if (Maps.isEmpty(m)) {
                return;
            }

            Map model = formatModel(m);

            String val;
            try {
                val = new ObjectMapper().writerWithDefaultPrettyPrinter().writeValueAsString(model);
            } catch (JsonProcessingException e) {
                // ignore
                return;
            }
            String[] lines = val.split("\n");

            int max = 0;
            for (String l : lines) {
                max = l.indexOf("#//") > max ? l.indexOf("#//") : max;
            }

            StringBuffer s = new StringBuffer();
            for (String l : lines) {
                if (l.contains("//")) {
                    int pos = max + 10;

                    Pattern p = Pattern.compile(DESCRIPTION_REG);
                    Matcher matcher = p.matcher(l);
                    if (matcher.find()) {
                        String description = matcher.group();
                        l = l.replace(description, "");

                        StringBuffer t = new StringBuffer();
                        if (l.length() < pos) {
                            int interval = pos - l.length();
                            while (interval > 0) {
                                t.append(" ");
                                interval--;
                            }
                        }

                        description = Strings.trimEnd(Strings.trimStart(description, '#'), '/', '#');

                        s.append(new StringBuffer(l).insert(l.length() - 1, t).insert(pos - 1, description));
                    }
                } else {
                    s.append(l);
                }
            }

            options.append(s.toString());
        }

        private Map formatModel(Map m) {
            return  formatModel(m, new HashSet<>());
        }

        private Map formatModel(Map m, Set refChain) {
            Map model = new HashMap<>(16);

            for (Object o : m.keySet()) {
                Map property = (Map) m.get(o);
                String description = (String) property.get(PROPERTY_DESCRIPTION);

                StringBuffer b = formatDescription(description);

                if (property.containsKey(PARAMETER_MODEL)) {
                    if(isCycle(property, refChain, model, o)){
                        return model;
                    }

                    b.insert(0, (String) o);
                    model.put(b.toString(), formatModel((Map) property.get(PARAMETER_MODEL), refChain));

                } else if (PROPERTY_TYPE_ARRAY.equalsIgnoreCase((String) property.get(PROPERTY_TYPE))) {
                    List array = new ArrayList();
                    Map item = (Map) property.get(SCHEMA_ITEMS);
                    if (!Maps.isEmpty(item) && item.containsKey(PARAMETER_MODEL)) {
                        if(isCycle(item, refChain, model, o)){
                            return model;
                        }

                        array.add(formatModel((Map) item.get(PARAMETER_MODEL), refChain));
                    } else {
                        array.add(item.get(PROPERTY_TYPE));
                    }

                    b.insert(0, (String) o);
                    model.put(b.toString(), array);
                } else {

                    b.insert(0, (String) property.get(PROPERTY_TYPE));
                    model.put(o, b.toString());

                }
            }

            return model;
        }
    }

    private static boolean isCycle(Map m, Set refChain, Map model, Object o){
        String $ref = m.get(SCHEMA_REF).toString();
        String ref = $ref.substring($ref.lastIndexOf('/') + 1);
        if(refChain.contains(ref)){
            String title = (String) m.get(SCHEMA_TITLE);
            StringBuffer b = formatDescription(title);

            b.insert(0, ref);
            model.put(o, b.toString());
            //avoid circular reference
            return true;
        }else{
            refChain.add(ref);
            return false;
        }
    }

    private static StringBuffer formatDescription(String description){
        StringBuffer b = new StringBuffer();
        if (Strings.isNotBlank(description)) {
            b.append("#//");
            b.append(description);
            b.append("//#");
        }
        return b;
    }

    private static final class SwaggerPathHelper extends BasicValueHelper {
        @Override
        public void execute(Options options) {
            String path = (String) options.getParameters().get(0);

            List parameters = (List) options.getParameters().get(1);
            if (Collections2.isNotEmpty(parameters)) {

                for (Object o : parameters) {
                    Map m = ((Map) o);
                    String in = (String) m.get(PARAMETERS_IN);
                    if (PARAMETERS_IN_QUERY.equalsIgnoreCase(in)) {
                        String pName = (String) m.get(PARAMETERS_NAME);
                        path = Urls.appendQueryParams(path, pName, Strings.format("{{0}}", pName));
                    }
                }
            }

            try {
                options.append(URLDecoder.decode(path, "UTF-8"));
            } catch (UnsupportedEncodingException e) {
                //ignore
            }
        }
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy