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