001/*
002 * (C) Copyright 2006-2016 Nuxeo SA (http://nuxeo.com/) and others.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *     http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 *
016 * Contributors:
017 *     bstefanescu
018 */
019package org.nuxeo.ecm.webengine.model.impl;
020
021import java.io.BufferedWriter;
022import java.io.File;
023import java.io.FileWriter;
024import java.io.IOException;
025import java.io.Writer;
026
027import org.apache.commons.io.FileUtils;
028import org.apache.commons.logging.Log;
029import org.apache.commons.logging.LogFactory;
030import org.nuxeo.ecm.core.api.NuxeoException;
031import org.nuxeo.ecm.webengine.WebEngine;
032import org.nuxeo.ecm.webengine.loader.ClassProxy;
033import org.nuxeo.ecm.webengine.loader.WebLoader;
034import org.nuxeo.ecm.webengine.model.WebAdapter;
035import org.nuxeo.ecm.webengine.model.WebObject;
036
037/**
038 * Load web types extracted from Groovy source files. Types are cached in META-INF/groovy-web-types. When types are
039 * reloaded this file will be removed.
040 *
041 * @author <a href="mailto:bs@nuxeo.com">Bogdan Stefanescu</a>
042 */
043public class GroovyTypeLoader {
044
045    public static final Log log = LogFactory.getLog(GroovyTypeLoader.class);
046
047    public static final String CRLF = System.getProperty("line.separator");
048
049    public static final String WEB_TYPES_FILE = "META-INF/groovy-web-types";
050
051    protected final WebLoader loader;
052
053    protected final TypeRegistry typeReg;
054
055    protected final File root;
056
057    public GroovyTypeLoader(WebEngine engine, TypeRegistry typeReg, File root) {
058        this.typeReg = typeReg;
059        this.root = root;
060        loader = engine.getWebLoader();
061    }
062
063    public synchronized void flushCache() {
064        log.info("Flush directory type provider cache");
065        File cache = new File(root, WEB_TYPES_FILE);
066        cache.delete();
067    }
068
069    public synchronized void load() {
070        try {
071            File cache = new File(root, WEB_TYPES_FILE);
072            if (cache.isFile()) {
073                for (String line : FileUtils.readLines(cache)) {
074                    if (line.equals("")) {
075                        continue;
076                    }
077                    TypeDescriptor td = loadType(line);
078                    if (td != null) {
079                        typeReg.registerTypeDescriptor(td);
080                    }
081                }
082            } else {
083                cache.getParentFile().mkdirs();
084                boolean completedAbruptly = true;
085                try (Writer w = new BufferedWriter(new FileWriter(cache))) {
086                    scan(root, null, w);
087                    completedAbruptly = false;
088                } finally {
089                    if (completedAbruptly) {
090                        cache.delete();
091                    }
092                }
093            }
094        } catch (IOException | ClassNotFoundException e) {
095            throw new NuxeoException(e);
096        }
097    }
098
099    protected void scan(File root, String path, Writer cache) {
100        for (File file : root.listFiles()) {
101            String name = file.getName();
102            if (file.isDirectory() && !"skin".equals(name) && !"samples".equals(name)) {
103                scan(file, path == null ? name : path + '.' + name, cache);
104            } else if (name.endsWith(".groovy") && Character.isUpperCase(name.charAt(0))) {
105                String className;
106                if (path == null) {
107                    className = name.substring(0, name.length() - 7);
108                } else {
109                    StringBuilder buf = new StringBuilder().append(path).append('.').append(name);
110                    buf.setLength(buf.length() - 7);
111                    className = buf.toString();
112                }
113                try {
114                    TypeDescriptor td = loadTypeAndRecord(cache, className);
115                    if (td != null) {
116                        typeReg.registerTypeDescriptor(td);
117                    }
118                } catch (IOException | ClassNotFoundException e) {
119                    throw new NuxeoException(e);
120                }
121            }
122        }
123    }
124
125    /**
126     * Loads a type and cache it.
127     */
128    protected TypeDescriptor loadTypeAndRecord(Writer cache, String className)
129            throws ClassNotFoundException, IOException {
130        TypeDescriptor td = loadType(className);
131        if (td != null) {
132            cache.write(className);
133            cache.write(CRLF);
134        }
135        return td;
136    }
137
138    /**
139     * Gets a type descriptor given an absolute className.
140     * <p>
141     * If this class doesn't define a type or type adapter, return null.
142     */
143    protected TypeDescriptor loadType(String className) throws ClassNotFoundException {
144        ClassProxy clazz = loader.getGroovyClassProxy(className);
145        WebObject type = clazz.get().getAnnotation(WebObject.class);
146        if (type != null) {
147            return TypeDescriptor.fromAnnotation(clazz, type);
148        }
149        WebAdapter ws = clazz.get().getAnnotation(WebAdapter.class);
150        if (ws != null) {
151            return AdapterDescriptor.fromAnnotation(clazz, ws);
152        }
153        return null;
154    }
155
156}