/** * Copyright 2012 Universitat Pompeu Fabra. * * 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 org.onexus.resource.manager.internal.providers; import freemarker.core.TemplateElement; import freemarker.template.*; import org.apache.commons.io.FilenameUtils; import org.apache.commons.io.filefilter.FileFilterUtils; import org.apache.commons.io.filefilter.HiddenFileFilter; import org.apache.commons.io.filefilter.IOFileFilter; import org.apache.commons.io.monitor.FileAlterationListenerAdaptor; import org.apache.commons.io.monitor.FileAlterationMonitor; import org.apache.commons.io.monitor.FileAlterationObserver; import org.onexus.data.api.Data; import org.onexus.resource.api.*; import org.onexus.resource.api.exceptions.ResourceNotFoundException; import org.onexus.resource.api.exceptions.UnserializeException; import org.onexus.resource.manager.internal.PluginLoader; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.swing.tree.TreeNode; import java.io.*; import java.security.InvalidParameterException; import java.util.*; import java.util.concurrent.ConcurrentHashMap; public abstract class AbstractProjectProvider { private static final Logger LOGGER = LoggerFactory.getLogger(AbstractProjectProvider.class); public static final String ONEXUS_EXTENSION = "onx"; public static final String ONEXUS_PROJECT_FILE = "onexus-project." + ONEXUS_EXTENSION; private IResourceSerializer serializer; private PluginLoader pluginLoader; private Set<Long> bundleDependencies = new HashSet<Long>(); private Map<String, Set<File>> includeDependencies = new HashMap<String, Set<File>>(); private String projectName; private String projectUrl; private File projectFolder; // FreeMarker private Configuration freemarkerConfig = new Configuration(); private FileAlterationObserver observer; private Project project; private Properties projectAlias; private Map<ORI, Resource> resources; private List<IResourceListener> listeners = new ArrayList<IResourceListener>(); public AbstractProjectProvider(String projectName, String projectUrl, File projectFolder, FileAlterationMonitor monitor, List<IResourceListener> listeners) { super(); this.projectName = projectName; this.projectUrl = projectUrl; this.projectFolder = projectFolder; this.listeners = listeners; // Don't watch hidden folders and files IOFileFilter notHiddenDirectoryFilter = FileFilterUtils.notFileFilter( FileFilterUtils.or( FileFilterUtils.and( FileFilterUtils.directoryFileFilter(), HiddenFileFilter.HIDDEN ), HiddenFileFilter.HIDDEN ) ); String MONITOR_PROJECTS = System.getProperty("onexus.monitor.projects"); if (MONITOR_PROJECTS == null || Boolean.valueOf(MONITOR_PROJECTS)) { observer = new FileAlterationObserver(projectFolder, notHiddenDirectoryFilter); observer.addListener(new FileAlterationListenerAdaptor() { @Override public void onDirectoryCreate(File directory) { LOGGER.info("Creating folder '" + directory.getName() + "'"); onResourceCreate(loadFile(directory)); } @Override public void onDirectoryDelete(File directory) { LOGGER.info("Deleting folder '" + directory.getName() + "'"); ORI resourceOri = convertFileToORI(directory); onResourceDelete(resources.remove(resourceOri)); } @Override public void onFileChange(File file) { LOGGER.info("Reloading file '" + file.getName() + "'"); onResourceChange(loadFile(file)); } @Override public void onFileCreate(File file) { LOGGER.info("Creating file '" + file.getName() + "'"); onResourceCreate(loadFile(file)); } @Override public void onFileDelete(File file) { LOGGER.info("Deleting file '" + file.getName() + "'"); ORI resourceOri = convertFileToORI(file); onResourceDelete(resources.remove(resourceOri)); } }); monitor.addObserver(observer); } // Initialize template engine try { freemarkerConfig.setDirectoryForTemplateLoading(projectFolder); } catch (IOException e) { LOGGER.error("At template engine configuration. Project folder: '" + projectFolder + "'", e); throw new RuntimeException(e); } freemarkerConfig.setObjectWrapper(new DefaultObjectWrapper()); freemarkerConfig.setDefaultEncoding("UTF-8"); freemarkerConfig.setTemplateExceptionHandler(TemplateExceptionHandler.HTML_DEBUG_HANDLER); freemarkerConfig.setIncompatibleImprovements(new Version(2, 3, 20)); // FreeMarker 2.3.20 } public Project getProject() { if (project == null) { loadProject(); } return project; } public File getProjectFolder() { return projectFolder; } protected void setProjectFolder(File projectFolder) { this.projectFolder = projectFolder; } public synchronized void loadProject() { File projectOnx = new File(projectFolder, ONEXUS_PROJECT_FILE); if (!projectOnx.exists()) { throw new InvalidParameterException("No Onexus project in " + projectFolder.getAbsolutePath()); } this.project = (Project) loadResource(projectOnx); this.project.setName(projectName); this.bundleDependencies.clear(); if (project.getPlugins() != null) { for (Plugin plugin : project.getPlugins()) { //TODO Remove this property if (plugin.getParameter("location") == null && plugin.getParameters() != null) { plugin.getParameters().add(new Parameter("location", projectFolder.getAbsolutePath())); } try { long bundleId = pluginLoader.load(plugin); bundleDependencies.add(bundleId); } catch (InvalidParameterException e) { LOGGER.error(e.getMessage()); } } } if (project.getAlias() != null && !project.getAlias().isEmpty()) { this.projectAlias = new Properties(); try { this.projectAlias.load(new StringReader(project.getAlias())); } catch (IOException e) { LOGGER.error("Malformed project alias", e); this.projectAlias = null; } } this.resources = null; } private synchronized void loadResources() { this.resources = new ConcurrentHashMap<ORI, Resource>(); Collection<File> files = addFilesRecursive(new ArrayList<File>(), projectFolder); for (File file : files) { // Skip project file if (ONEXUS_PROJECT_FILE.equals(file.getName())) { continue; } Resource resource = loadResource(file); if (resource != null) { if (resources == null) { return; } resources.put(resource.getORI(), resource); } } } public Resource getResource(ORI resourceUri) { if (resourceUri.getPath() == null && projectUrl.equals(resourceUri.getProjectUrl())) { return getProject(); } if (resources == null) { loadResources(); } if (!resources.containsKey(resourceUri)) { throw new ResourceNotFoundException(resourceUri); } return resources.get(resourceUri); } public <T extends Resource> List<T> getResourceChildren(IAuthorizationManager authorizationManager, Class<T> resourceType, ORI parentURI) { if (resources == null) { loadResources(); } List<T> children = new ArrayList<T>(); if (resources != null) { for (Resource resource : resources.values()) { if (parentURI.isChild(resource.getORI()) && resourceType.isAssignableFrom(resource.getClass()) && authorizationManager.check(IAuthorizationManager.READ, resource.getORI())) { children.add((T) resource); } } } return children; } public void syncProject() { loadProject(); loadResources(); } public void updateProject() { importProject(); syncProject(); } protected abstract void importProject(); private Resource loadResource(File resourceFile) { Resource resource; if (ONEXUS_EXTENSION.equalsIgnoreCase(FilenameUtils.getExtension(resourceFile.getName()))) { StringWriter out = null; try { String relativePath = projectFolder.toURI().relativize(resourceFile.toURI()).getPath(); Template resourceTemplate = freemarkerConfig.getTemplate(relativePath); out = new StringWriter((int) resourceFile.length() / 4); resourceTemplate.process(projectAlias, out); InputStream input = new ByteArrayInputStream(out.toString().getBytes("UTF-8")); resource = serializer.unserialize(Resource.class, input); for (String include : getIncludes(resourceTemplate)) { addIncludeDependency(include, resourceFile); } } catch (FileNotFoundException e) { resource = createErrorResource(resourceFile, "File '" + resourceFile.getPath() + "' not found."); } catch (UnserializeException e) { String msg = "Parsing file " + resourceFile.getPath() + " at line " + e.getLine() + " on " + e.getPath(); if (out != null) { String[] lines = out.toString().split(System.getProperty("line.separator")); int error = Integer.valueOf(e.getLine()); int from = (error-1 > 0 ? error-1 : 0); int to = (error+1 < lines.length ? error+1 : lines.length); for (int i=from ; i <= to; i++) { msg = msg + "\n " + i + " >> " + lines[i]; } } resource = createErrorResource(resourceFile, msg); } catch (Exception e) { resource = createErrorResource(resourceFile, e.getMessage()); } } else { if (resourceFile.isDirectory()) { resource = new Folder(); } else { resource = createDataResource(resourceFile); } } if (resource == null) { return null; } resource.setORI(convertFileToORI(resourceFile)); return resource; } private ORI convertFileToORI(File file) { String projectPath = projectFolder.getAbsolutePath() + File.separator; String filePath = file.getAbsolutePath(); String relativePath = filePath.replace(projectPath, ""); if (relativePath.equals(ONEXUS_PROJECT_FILE)) { relativePath = null; } else { relativePath = relativePath.replace("." + ONEXUS_EXTENSION, ""); } return new ORI(projectUrl, relativePath); } private Resource createErrorResource(File resourceFile, String msg) { LOGGER.error(msg); Resource errorResource = createDataResource(resourceFile); errorResource.setDescription("ERROR: " + msg); return errorResource; } private Resource createDataResource(File resourceFile) { Data data = new Data(); Loader loader = new Loader(); loader.setParameters(new ArrayList<Parameter>()); loader.getParameters().add(new Parameter("data-url", resourceFile.toURI().toString())); data.setLoader(loader); return data; } private static Collection<File> addFilesRecursive(Collection<File> files, File parentFolder) { if (parentFolder.isDirectory()) { File[] inFiles = parentFolder.listFiles(); if (inFiles != null) { for (File file : inFiles) { if (!file.isHidden()) { files.add(file); if (file.isDirectory()) { addFilesRecursive(files, file); } } } } } return files; } public void save(Resource resource) { if (resource == null) { return; } if (resource instanceof Project) { throw new IllegalArgumentException("Cannot create a project '" + resource.getORI() + "' inside project '" + projectUrl + "'"); } String resourcePath = resource.getORI().getPath(); File file; if (resource instanceof Folder) { file = new File(projectFolder, resourcePath); file.mkdirs(); return; } file = new File(projectFolder, resourcePath + "." + ONEXUS_EXTENSION); try { if (!file.exists()) { file.createNewFile(); } FileOutputStream os = new FileOutputStream(file); serializer.serialize(resource, os); os.close(); } catch (IOException e) { LOGGER.error("Saving resource '" + resource.getORI() + "' in file '" + file.getAbsolutePath() + "'", e); } if (observer != null) { observer.checkAndNotify(); } } public String getProjectUrl() { return projectUrl; } public PluginLoader getPluginLoader() { return pluginLoader; } public void setPluginLoader(PluginLoader pluginLoader) { this.pluginLoader = pluginLoader; } public IResourceSerializer getSerializer() { return serializer; } public void setSerializer(IResourceSerializer serializer) { this.serializer = serializer; } protected void onResourceCreate(Resource resource) { if (resource == null) { return; } LOGGER.info("Resource '" + resource.getName() + "' created."); for (IResourceListener listener : listeners) { listener.onResourceCreate(resource); } } protected void onResourceChange(Resource resource) { if (resource == null) { return; } LOGGER.info("Resource '" + resource.getName() + "' changed."); for (IResourceListener listener : listeners) { listener.onResourceChange(resource); } // If it's an include dependency reload parent resources String resourcePath = resource.getORI().getPath().substring(1); if (includeDependencies.containsKey(resourcePath)) { freemarkerConfig.clearTemplateCache(); for (File parentResourceFile : includeDependencies.get(resourcePath)) { onResourceChange(loadFile(parentResourceFile)); } } } protected void onResourceDelete(Resource resource) { if (resource == null) { return; } LOGGER.info("Resource '" + resource.getName() + "' deleted."); for (IResourceListener listener : listeners) { listener.onResourceDelete(resource); } } public boolean dependsOnBundle(long bundleId) { return bundleDependencies.contains(Long.valueOf(bundleId)); } private List<String> getIncludes(Template resourceTemplate) { List<String> includes = new ArrayList<String>(); TemplateElement root = resourceTemplate.getRootTreeNode(); for (int i=0; i < root.getChildCount(); i++) { TreeNode node = root.getChildAt(i); if (node instanceof TemplateElement) { String tag = ((TemplateElement) node).getCanonicalForm(); if (tag != null && tag.startsWith("<#include")) { String include = tag.replace("<#include \"", "").replace("\"/>", ""); String templatePath = FilenameUtils.getFullPath(resourceTemplate.getName()); includes.add(templatePath + include); try { Template includeTemplate = freemarkerConfig.getTemplate(templatePath + include); includes.addAll( getIncludes(includeTemplate)); } catch (IOException e) { LOGGER.error(e.getMessage(), e); } } } } return includes; } private void addIncludeDependency(String templateName, File parentResourceFile) { if (!includeDependencies.containsKey(templateName)) { includeDependencies.put(templateName, new HashSet<File>()); } includeDependencies.get(templateName).add(parentResourceFile); } private Resource loadFile(File file) { // Skip project file if (ONEXUS_PROJECT_FILE.equals(file.getName())) { return null; } Resource resource = loadResource(file); if (resource != null) { resources.put(resource.getORI(), resource); } return resource; } }