nl.strohalm.cyclos.themes.ThemeHandlerImpl.java Source code

Java tutorial

Introduction

Here is the source code for nl.strohalm.cyclos.themes.ThemeHandlerImpl.java

Source

/*
This file is part of Cyclos (www.cyclos.org).
A project of the Social Trade Organisation (www.socialtrade.org).
    
Cyclos is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
    
Cyclos is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
    
You should have received a copy of the GNU General Public License
along with Cyclos; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
    
 */
package nl.strohalm.cyclos.themes;

import java.io.File;
import java.io.FileFilter;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipOutputStream;

import javax.servlet.ServletContext;

import nl.strohalm.cyclos.entities.customization.files.CustomizedFile;
import nl.strohalm.cyclos.entities.customization.files.CustomizedFile.Type;
import nl.strohalm.cyclos.entities.customization.images.Image;
import nl.strohalm.cyclos.entities.settings.LocalSettings;
import nl.strohalm.cyclos.services.customization.CustomizedFileService;
import nl.strohalm.cyclos.services.customization.ImageService;
import nl.strohalm.cyclos.services.settings.SettingsService;
import nl.strohalm.cyclos.themes.Theme.Style;
import nl.strohalm.cyclos.themes.exceptions.ThemeException;
import nl.strohalm.cyclos.themes.exceptions.ThemeNotFoundException;
import nl.strohalm.cyclos.utils.CSSHelper;
import nl.strohalm.cyclos.utils.CustomizationHelper;
import nl.strohalm.cyclos.utils.ImageHelper.ImageType;
import nl.strohalm.cyclos.utils.WebImageHelper;
import nl.strohalm.cyclos.utils.conversion.CoercionHelper;
import nl.strohalm.cyclos.utils.validation.ValidationException;
import nl.strohalm.cyclos.utils.validation.Validator;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.io.filefilter.SuffixFileFilter;
import org.apache.commons.lang.StringUtils;
import org.springframework.web.context.ServletContextAware;

/**
 * Theme handler implementation
 * @author luis
 */
public class ThemeHandlerImpl extends BaseThemeHandler implements ServletContextAware {

    private static final String THEME_PROPERTIES_ENTRY = "theme.properties";
    private static final String THEMES_PATH = "/WEB-INF/themes/";
    private static final FilenameFilter THEME_FILTER;
    private static final FilenameFilter STYLE_FILTER;
    private static final FileFilter IMAGE_FILTER;
    static {
        THEME_FILTER = new SuffixFileFilter(".theme");
        STYLE_FILTER = new SuffixFileFilter(".css");
        IMAGE_FILTER = new FileFilter() {
            @Override
            public boolean accept(final File pathname) {
                // Check if is a recognized image
                try {
                    ImageType.getByContent(pathname);
                    return true;
                } catch (final Exception e) {
                    return false;
                }
            }

        };
    }

    /**
     * Return the properties for the given zip file
     */
    private static Properties properties(final ZipFile zipFile) throws IOException {
        final ZipEntry propertiesEntry = zipFile.getEntry(THEME_PROPERTIES_ENTRY);
        if (propertiesEntry == null) {
            throw new FileNotFoundException(THEME_PROPERTIES_ENTRY);
        }
        final Properties properties = new Properties();
        properties.load(zipFile.getInputStream(propertiesEntry));
        return properties;
    }

    private ServletContext context;
    private ImageService imageService;
    private CustomizedFileService customizedFileService;
    private CustomizationHelper customizationHelper;
    private WebImageHelper webImageHelper;
    private SettingsService settingsService;

    public void setCustomizationHelper(final CustomizationHelper customizationHelper) {
        this.customizationHelper = customizationHelper;
    }

    public void setCustomizedFileService(final CustomizedFileService customizedFileService) {
        this.customizedFileService = customizedFileService;
    }

    public void setImageService(final ImageService imageService) {
        this.imageService = imageService;
    }

    @Override
    public void setServletContext(final ServletContext servletContext) {
        context = servletContext;
    }

    public void setSettingsService(final SettingsService settingsService) {
        this.settingsService = settingsService;
    }

    public void setWebImageHelper(final WebImageHelper webImageHelper) {
        this.webImageHelper = webImageHelper;
    }

    @Override
    protected void doExport(final Theme theme, final OutputStream out) {
        validateForExport(theme);
        final ZipOutputStream zipOut = new ZipOutputStream(out);

        final LocalSettings settings = settingsService.getLocalSettings();
        final String charset = settings.getCharset();

        try {
            // Retrieve which files will be exported
            final List<String> exportedFiles = new ArrayList<String>();
            final List<String> exportedImages = new ArrayList<String>();
            final Collection<Style> styles = theme.getStyles();
            if (styles != null) {
                for (final Style style : styles) {
                    exportedFiles.addAll(style.getFiles());
                }
            }

            // Store the properties file
            final Properties properties = asProperties(theme);
            zipOut.putNextEntry(new ZipEntry(THEME_PROPERTIES_ENTRY));
            properties.store(zipOut, "");
            zipOut.closeEntry();

            // Store each css file
            final List<File> styleFiles = customizationHelper.listByType(CustomizedFile.Type.STYLE);
            for (File file : styleFiles) {
                // We must use the customized one, not the original
                final String name = file.getName();
                if (!exportedFiles.contains(name)) {
                    // When not exporting the given file, continue
                    continue;
                }
                // Read the file contents
                file = customizationHelper.findFileOf(CustomizedFile.Type.STYLE, null, name);
                final String contents = FileUtils.readFileToString(file, charset);

                // Resolve the referenced images by reading all url(name) values from the css
                exportedImages.addAll(CSSHelper.resolveURLs(contents));

                // Write the contents to the zip file
                zipOut.putNextEntry(new ZipEntry("styles/" + name));
                IOUtils.copy(new StringReader(contents), zipOut, charset);
                zipOut.closeEntry();
            }

            // Store each image
            final File dir = webImageHelper.imagePath(Image.Nature.STYLE);
            final File[] imageFiles = dir.listFiles(IMAGE_FILTER);
            for (final File file : imageFiles) {
                final String name = file.getName();
                // Export referenced images only
                if (!exportedImages.contains(name)) {
                    continue;
                }
                zipOut.putNextEntry(new ZipEntry("images/" + name));
                IOUtils.copy(new FileInputStream(file), zipOut);
                zipOut.closeEntry();
            }

        } catch (final Exception e) {
            throw new ThemeException(e);
        } finally {
            // Close the stream
            IOUtils.closeQuietly(zipOut);
        }
    }

    @Override
    protected void doImportNew(final String fileName, final InputStream in) {
        final File file = realFile(fileName);
        try {
            final byte[] data = IOUtils.toByteArray(in);
            customizationHelper.updateFile(file, System.currentTimeMillis(), data);
        } catch (final Exception e) {
            throw new ThemeException();
        }
    }

    @Override
    protected List<Theme> doList() {
        final String path = context.getRealPath(THEMES_PATH);
        final File dir = new File(path);
        if (!dir.exists()) {
            dir.mkdirs();
        }
        final File[] files = dir.listFiles(THEME_FILTER);
        final List<Theme> themes = new ArrayList<Theme>(files.length);
        for (final File file : files) {
            try {
                themes.add(read(file));
            } catch (final ThemeException e) {
                // Skip this theme
            }
        }
        Collections.sort(themes);
        return themes;
    }

    @Override
    protected void doRemove(final String fileName) {
        final File file = realFile(fileName);
        if (!file.exists()) {
            throw new ThemeNotFoundException(fileName);
        }
        customizationHelper.deleteFile(file);
    }

    @Override
    protected void doSelect(final String fileName) {
        ZipFile zipFile = null;
        final LocalSettings settings = settingsService.getLocalSettings();
        final String charset = settings.getCharset();
        try {
            final File file = realFile(fileName);
            if (!file.exists()) {
                throw new ThemeNotFoundException(fileName);
            }
            zipFile = new ZipFile(file);

            // Ensure the properties entry exists
            properties(zipFile);

            // Find all currently used images by style
            final Map<String, Collection<String>> imagesByFile = new HashMap<String, Collection<String>>();
            final File imageDir = webImageHelper.imagePath(Image.Nature.STYLE);
            final File[] cssFiles = imageDir.listFiles(STYLE_FILTER);
            for (final File css : cssFiles) {
                final String contents = FileUtils.readFileToString(css, charset);
                final List<String> urls = CSSHelper.resolveURLs(contents);
                imagesByFile.put(css.getName(), urls);
            }

            // Read the files
            final Enumeration<? extends ZipEntry> entries = zipFile.entries();
            while (entries.hasMoreElements()) {
                final ZipEntry entry = entries.nextElement();
                // We will not handle directories
                if (entry.isDirectory()) {
                    continue;
                }
                final String name = entry.getName();
                final String entryFileName = new File(name).getName();
                if (name.startsWith("images/")) {
                    final ImageType type = ImageType.getByFileName(entryFileName);
                    // Save the image
                    final Image image = imageService.save(Image.Nature.STYLE, type, entryFileName,
                            zipFile.getInputStream(entry));
                    // Update the physical image
                    webImageHelper.update(image);
                } else if (name.startsWith("styles/")) {
                    // Save the style sheet
                    CustomizedFile customizedFile = new CustomizedFile();
                    customizedFile.setName(entryFileName);
                    customizedFile.setType(CustomizedFile.Type.STYLE);
                    final String contents = IOUtils.toString(zipFile.getInputStream(entry), charset);
                    customizedFile.setContents(contents);
                    final File originalFile = customizationHelper.originalFileOf(Type.STYLE, entryFileName);
                    if (originalFile.exists()) {
                        customizedFile.setOriginalContents(FileUtils.readFileToString(originalFile, charset));
                    }
                    customizedFile = customizedFileService.saveForTheme(customizedFile);

                    // Update the physical file
                    final File physicalFile = customizationHelper.customizedFileOf(CustomizedFile.Type.STYLE,
                            entryFileName);
                    customizationHelper.updateFile(physicalFile, customizedFile);

                    // Remove images that are no longer used
                    final List<String> newImages = CSSHelper.resolveURLs(contents);
                    final Collection<String> oldImages = imagesByFile.get(entryFileName);
                    if (CollectionUtils.isNotEmpty(oldImages)) {
                        for (final String imageName : oldImages) {
                            if (!newImages.contains(imageName)) {
                                // No longer used. Remove it
                                imageService.removeStyleImage(imageName);
                                // Remove the physical file
                                final File imageFile = new File(imageDir, imageName);
                                customizationHelper.deleteFile(imageFile);
                            }
                        }
                    }
                }
            }
        } catch (final ThemeException e) {
            throw e;
        } catch (final Exception e) {
            throw new ThemeException(e);
        } finally {
            try {
                zipFile.close();
            } catch (final Exception e) {
                // Ignore
            }
        }
    }

    @Override
    protected void doValidateForExport(final Theme theme) throws ValidationException {
        getExportValidator().validate(theme);
    }

    /**
     * Returns the theme as a properties object
     */
    private Properties asProperties(final Theme theme) {
        final Properties properties = new Properties();
        properties.setProperty("title", StringUtils.trimToEmpty(theme.getTitle()));
        properties.setProperty("author", StringUtils.trimToEmpty(theme.getAuthor()));
        properties.setProperty("version", StringUtils.trimToEmpty(theme.getVersion()));
        properties.setProperty("description", StringUtils.trimToEmpty(theme.getDescription()));
        final Collection<String> strings = CoercionHelper.coerceCollection(String.class, theme.getStyles());
        properties.setProperty("styles", StringUtils.join(strings.iterator(), ','));
        return properties;
    }

    /**
     * Reads properties as a Theme object
     */
    private Theme fromProperties(final Properties properties) {
        final Theme theme = new Theme();
        theme.setTitle(StringUtils.trimToNull(properties.getProperty("title")));
        theme.setAuthor(StringUtils.trimToNull(properties.getProperty("author")));
        theme.setVersion(StringUtils.trimToNull(properties.getProperty("version")));
        theme.setDescription(StringUtils.trimToNull(properties.getProperty("description")));
        final String styles = StringUtils.trimToNull(properties.getProperty("styles"));
        if (styles == null) {
            // None found - Assume all styles
            theme.setStyles(EnumSet.allOf(Style.class));
        } else {
            final String[] array = StringUtils.split(styles, ',');
            theme.setStyles(CoercionHelper.coerceCollection(Theme.Style.class, array));
        }
        return theme;
    }

    private Validator getExportValidator() {
        final Validator exportValidator = new Validator("theme");
        exportValidator.property("title").required();
        exportValidator.property("filename").required();
        exportValidator.property("styles").key("theme.stylesToExport").required();
        return exportValidator;
    }

    /**
     * Reads a theme from file
     */
    private Theme read(final File file) throws ThemeException {
        ZipFile zipFile = null;
        try {
            if (!file.exists()) {
                throw new ThemeNotFoundException(file.getName());
            }
            zipFile = new ZipFile(file);
            final Properties properties = properties(zipFile);
            final Theme theme = fromProperties(properties);
            theme.setFilename(file.getName());
            return theme;
        } catch (final Exception e) {
            throw new ThemeException(e);
        } finally {
            try {
                zipFile.close();
            } catch (final Exception e) {
                // Ignore
            }
        }
    }

    /**
     * Returns the real file for the theme
     */
    private File realFile(final String fileName) {
        final String path = context.getRealPath(THEMES_PATH);
        return new File(path, fileName);
    }
}