001/*
002 * (C) Copyright 2006-2008 Nuxeo SAS (http://nuxeo.com/) and contributors.
003 *
004 * All rights reserved. This program and the accompanying materials
005 * are made available under the terms of the GNU Lesser General Public License
006 * (LGPL) version 2.1 which accompanies this distribution, and is available at
007 * http://www.gnu.org/licenses/lgpl.html
008 *
009 * This library is distributed in the hope that it will be useful,
010 * but WITHOUT ANY WARRANTY; without even the implied warranty of
011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
012 * Lesser General Public License for more details.
013 *
014 * Contributors:
015 *     bstefanescu
016 *
017 * $Id$
018 */
019
020package org.nuxeo.ecm.webengine.model.impl;
021
022import java.io.BufferedWriter;
023import java.io.File;
024import java.io.FileWriter;
025import java.io.IOException;
026import java.io.Writer;
027
028import org.apache.commons.logging.Log;
029import org.apache.commons.logging.LogFactory;
030import org.nuxeo.common.utils.FileUtils;
031import org.nuxeo.ecm.webengine.WebEngine;
032import org.nuxeo.ecm.webengine.WebException;
033import org.nuxeo.ecm.webengine.loader.ClassProxy;
034import org.nuxeo.ecm.webengine.loader.WebLoader;
035import org.nuxeo.ecm.webengine.model.WebAdapter;
036import org.nuxeo.ecm.webengine.model.WebObject;
037
038/**
039 * Load web types extracted from Groovy source files. Types are cached in META-INF/groovy-web-types. When types are
040 * reloaded this file will be removed.
041 *
042 * @author <a href="mailto:bs@nuxeo.com">Bogdan Stefanescu</a>
043 */
044public class GroovyTypeLoader {
045
046    public static final Log log = LogFactory.getLog(GroovyTypeLoader.class);
047
048    public static final String CRLF = System.getProperty("line.separator");
049
050    public static final String WEB_TYPES_FILE = "META-INF/groovy-web-types";
051
052    protected final WebLoader loader;
053
054    protected final TypeRegistry typeReg;
055
056    protected final File root;
057
058    public GroovyTypeLoader(WebEngine engine, TypeRegistry typeReg, File root) {
059        this.typeReg = typeReg;
060        this.root = root;
061        loader = engine.getWebLoader();
062    }
063
064    public synchronized void flushCache() {
065        log.info("Flush directory type provider cache");
066        File cache = new File(root, WEB_TYPES_FILE);
067        cache.delete();
068    }
069
070    public synchronized void load() {
071        try {
072            File cache = new File(root, WEB_TYPES_FILE);
073            if (cache.isFile()) {
074                for (String line : FileUtils.readLines(cache)) {
075                    if (line.equals("")) {
076                        continue;
077                    }
078                    TypeDescriptor td = loadType(line);
079                    if (td != null) {
080                        typeReg.registerTypeDescriptor(td);
081                    }
082                }
083            } else {
084                cache.getParentFile().mkdirs();
085                boolean completedAbruptly = true;
086                try (Writer w = new BufferedWriter(new FileWriter(cache))) {
087                    scan(root, null, w);
088                    completedAbruptly = false;
089                } finally {
090                    if (completedAbruptly) {
091                        cache.delete();
092                    }
093                }
094            }
095        } catch (IOException | ClassNotFoundException e) {
096            throw WebException.wrap(e);
097        }
098    }
099
100    protected void scan(File root, String path, Writer cache) {
101        for (File file : root.listFiles()) {
102            String name = file.getName();
103            if (file.isDirectory() && !"skin".equals(name) && !"samples".equals(name)) {
104                scan(file, path == null ? name : new StringBuilder().append(path).append('.').append(name).toString(),
105                        cache);
106            } else if (name.endsWith(".groovy") && Character.isUpperCase(name.charAt(0))) {
107                String className = null;
108                if (path == null) {
109                    className = name.substring(0, name.length() - 7);
110                } else {
111                    StringBuilder buf = new StringBuilder().append(path).append('.').append(name);
112                    buf.setLength(buf.length() - 7);
113                    className = buf.toString();
114                }
115                try {
116                    TypeDescriptor td = loadTypeAndRecord(cache, className);
117                    if (td != null) {
118                        typeReg.registerTypeDescriptor(td);
119                    }
120                } catch (IOException | ClassNotFoundException e) {
121                    throw WebException.wrap(e);
122                }
123            }
124        }
125    }
126
127    /**
128     * Loads a type and cache it.
129     *
130     * @param cache
131     * @param className
132     * @throws ClassNotFoundException
133     * @throws IOException
134     */
135    protected TypeDescriptor loadTypeAndRecord(Writer cache, String className) throws ClassNotFoundException,
136            IOException {
137        TypeDescriptor td = loadType(className);
138        if (td != null) {
139            cache.write(className);
140            cache.write(CRLF);
141        }
142        return td;
143    }
144
145    /**
146     * Gets a type descriptor given an absolute className.
147     * <p>
148     * If this class doesn't define a type or type adapter, return null.
149     */
150    protected TypeDescriptor loadType(String className) throws ClassNotFoundException {
151        ClassProxy clazz = loader.getGroovyClassProxy(className);
152        WebObject type = clazz.get().getAnnotation(WebObject.class);
153        if (type != null) {
154            return TypeDescriptor.fromAnnotation(clazz, type);
155        }
156        WebAdapter ws = clazz.get().getAnnotation(WebAdapter.class);
157        if (ws != null) {
158            return AdapterDescriptor.fromAnnotation(clazz, ws);
159        }
160        return null;
161    }
162
163}