it.tidalwave.northernwind.core.impl.model.DefaultSite.java Source code

Java tutorial

Introduction

Here is the source code for it.tidalwave.northernwind.core.impl.model.DefaultSite.java

Source

/*
 * #%L
 * *********************************************************************************************************************
 *
 * NorthernWind - lightweight CMS
 * http://northernwind.tidalwave.it - git clone https://bitbucket.org/tidalwave/northernwind-src.git
 * %%
 * Copyright (C) 2011 - 2016 Tidalwave s.a.s. (http://tidalwave.it)
 * %%
 * *********************************************************************************************************************
 *
 * 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.
 *
 * *********************************************************************************************************************
 *
 * $Id$
 *
 * *********************************************************************************************************************
 * #L%
 */
package it.tidalwave.northernwind.core.impl.model;

import javax.annotation.Nonnull;
import javax.inject.Inject;
import javax.inject.Named;
import java.beans.PropertyVetoException;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.TreeMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.io.IOException;
import org.springframework.beans.factory.annotation.Configurable;
import it.tidalwave.util.NotFoundException;
import it.tidalwave.northernwind.core.model.ResourceFile;
import it.tidalwave.northernwind.core.model.ResourceFileSystem;
import it.tidalwave.northernwind.core.model.Content;
import it.tidalwave.northernwind.core.model.Media;
import it.tidalwave.northernwind.core.model.ModelFactory;
import it.tidalwave.northernwind.core.model.Resource;
import it.tidalwave.northernwind.core.model.ResourcePath;
import it.tidalwave.northernwind.core.model.Site;
import it.tidalwave.northernwind.core.model.SiteFinder;
import it.tidalwave.northernwind.core.model.SiteNode;
import it.tidalwave.northernwind.core.model.spi.LinkPostProcessor;
import it.tidalwave.northernwind.core.model.spi.RequestHolder;
import it.tidalwave.northernwind.core.model.ResourceFileSystemProvider;
import it.tidalwave.northernwind.core.impl.util.RegexTreeMap;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;

/***********************************************************************************************************************
 *
 * The default implementation of {@link Site}.
 *
 * @author  Fabrizio Giudici
 * @version $Id$
 *
 **********************************************************************************************************************/
@Configurable
@Slf4j
/* package */ class DefaultSite implements InternalSite {
    static interface FilePredicate {
        public void apply(@Nonnull ResourceFile file, @Nonnull ResourcePath relativeUri);
    }

    static interface FileFilter {
        public boolean accept(@Nonnull ResourceFile file);
    }

    private final FileFilter FOLDER_FILTER = new FileFilter() {
        @Override
        public boolean accept(final @Nonnull ResourceFile file) {
            return file.isFolder() && !ignoredFolders.contains(file.getName());
        }
    };

    private final FileFilter FILE_FILTER = new FileFilter() {
        @Override
        public boolean accept(final @Nonnull ResourceFile file) {
            return file.isData() && !ignoredFolders.contains(file.getName());
        }
    };

    @Inject
    private List<LinkPostProcessor> linkPostProcessors;

    @Inject
    private RequestHolder requestHolder;

    @Nonnull
    private final ModelFactory modelFactory;

    @Inject
    @Named("fileSystemProvider")
    @Getter
    @Nonnull
    private ResourceFileSystemProvider fileSystemProvider;

    @Nonnull
    /* package */ final String documentPath;

    @Nonnull
    /* package */ final String mediaPath;

    @Nonnull
    /* package */ final String libraryPath;

    @Nonnull
    /* package */ final String nodePath;

    @Getter
    /* package */ final boolean logConfigurationEnabled;

    @Getter
    @Nonnull
    /* package */ final String contextPath;

    /* package */ final List<String> ignoredFolders = new ArrayList<>();

    private ResourceFile documentFolder;

    private ResourceFile libraryFolder;

    private ResourceFile mediaFolder;

    @Getter
    private ResourceFile nodeFolder;

    // Note that this class can't be final neither use ImmutableMaps, since during the traversing of the filesystem
    // resources need to access the partially created internal structure.

    /* package */ final Map<String, Content> documentMapByRelativePath = new TreeMap<>();

    /* package */ final Map<String, Resource> libraryMapByRelativePath = new TreeMap<>();

    /* package */ final Map<String, Media> mediaMapByRelativePath = new TreeMap<>();

    /* package */ final Map<String, SiteNode> nodeMapByRelativePath = new TreeMap<>();

    /* package */ final RegexTreeMap<SiteNode> nodeMapByRelativeUri = new RegexTreeMap<>();

    private final Map<Class<?>, Map<String, ?>> relativePathMapsByType = new HashMap<>();

    private final Map<Class<?>, RegexTreeMap<?>> relativeUriMapsByType = new HashMap<>();

    private final List<Locale> configuredLocales = new ArrayList<>();

    /*******************************************************************************************************************
     *
     *
     *
     ******************************************************************************************************************/
    protected DefaultSite(final @Nonnull Site.Builder siteBuilder) {
        this.modelFactory = siteBuilder.getModelFactory();
        this.contextPath = siteBuilder.getContextPath();
        this.documentPath = siteBuilder.getDocumentPath();
        this.mediaPath = siteBuilder.getMediaPath();
        this.libraryPath = siteBuilder.getLibraryPath();
        this.nodePath = siteBuilder.getNodePath();
        this.logConfigurationEnabled = siteBuilder.isLogConfigurationEnabled();
        this.configuredLocales.addAll(siteBuilder.getConfiguredLocales());
        this.ignoredFolders.addAll(siteBuilder.getIgnoredFolders());
    }

    /*******************************************************************************************************************
     *
     * {@inheritDoc}
     *
     ******************************************************************************************************************/
    @Override
    @Nonnull
    @SuppressWarnings("unchecked")
    public <Type> SiteFinder<Type> find(final @Nonnull Class<Type> type) {
        final Map<String, Type> relativePathMap = (Map<String, Type>) relativePathMapsByType.get(type);
        final RegexTreeMap<Type> relativeUriMap = (RegexTreeMap<Type>) relativeUriMapsByType.get(type);
        return new DefaultSiteFinder<>(type.getSimpleName(), relativePathMap, relativeUriMap);
    }

    /*******************************************************************************************************************
     *
     * {@inheritDoc}
     *
     ******************************************************************************************************************/
    @Override
    @Nonnull
    public String createLink(final @Nonnull ResourcePath relativeUri) {
        final ResourcePath link = new ResourcePath(contextPath).appendedWith(relativeUri);
        String linkAsString = requestHolder.get().getBaseUrl() + link.asString();

        for (final LinkPostProcessor linkPostProcessor : linkPostProcessors) {
            linkAsString = linkPostProcessor.postProcess(linkAsString);
        }

        return linkAsString;
    }

    /*******************************************************************************************************************
     *
     * {@inheritDoc}
     *
     ******************************************************************************************************************/
    @Override
    @Nonnull
    public List<Locale> getConfiguredLocales() {
        return new CopyOnWriteArrayList<>(configuredLocales);
    }

    /*******************************************************************************************************************
     *
     * {@inheritDoc}
     *
     ******************************************************************************************************************/
    @Override
    @Nonnull
    public String toString() {
        return String.format("DefaultSite(@%x)", System.identityHashCode(this));
    }

    /*******************************************************************************************************************
     *
     *
     ******************************************************************************************************************/
    /* package */ void initialize() throws IOException, NotFoundException, PropertyVetoException {
        log.info("initialize()");

        relativePathMapsByType.put(Content.class, documentMapByRelativePath);
        relativePathMapsByType.put(Media.class, mediaMapByRelativePath);
        relativePathMapsByType.put(Resource.class, libraryMapByRelativePath);
        relativePathMapsByType.put(SiteNode.class, nodeMapByRelativePath);

        relativeUriMapsByType.put(SiteNode.class, nodeMapByRelativeUri);

        log.info(">>>> fileSystemProvider: {}", fileSystemProvider);
        final ResourceFileSystem fileSystem = fileSystemProvider.getFileSystem();
        documentFolder = findMandatoryFolder(fileSystem, documentPath);
        libraryFolder = findMandatoryFolder(fileSystem, libraryPath);
        mediaFolder = findMandatoryFolder(fileSystem, mediaPath);
        nodeFolder = findMandatoryFolder(fileSystem, nodePath);

        log.info(">>>> contextPath:        {}", contextPath);
        log.info(">>>> ignoredFolders:     {}", ignoredFolders);
        log.info(">>>> fileSystem:         {}", fileSystem);
        log.info(">>>> documentPath:       {}", documentFolder.getPath().asString());
        log.info(">>>> libraryPath:        {}", libraryFolder.getPath().asString());
        log.info(">>>> mediaPath:          {}", mediaFolder.getPath().asString());
        log.info(">>>> nodePath:           {}", nodeFolder.getPath().asString());
        log.info(">>>> locales:            {}", configuredLocales);

        documentMapByRelativePath.clear();
        libraryMapByRelativePath.clear();
        mediaMapByRelativePath.clear();
        nodeMapByRelativePath.clear();
        nodeMapByRelativeUri.clear();

        traverse(libraryFolder, FILE_FILTER, new FilePredicate() {
            @Override
            public void apply(final @Nonnull ResourceFile file, final @Nonnull ResourcePath relativePath) {
                libraryMapByRelativePath.put(relativePath.asString(),
                        modelFactory.createResource().withFile(file).build());
            }
        });

        traverse(mediaFolder, FILE_FILTER, new FilePredicate() {
            @Override
            public void apply(final @Nonnull ResourceFile file, final @Nonnull ResourcePath relativePath) {
                mediaMapByRelativePath.put(relativePath.asString(),
                        modelFactory.createMedia().withFile(file).build());
            }
        });

        traverse(documentFolder, FOLDER_FILTER, new FilePredicate() {
            @Override
            public void apply(final @Nonnull ResourceFile folder, final @Nonnull ResourcePath relativePath) {
                documentMapByRelativePath.put(relativePath.asString(),
                        modelFactory.createContent().withFolder(folder).build());
            }
        });

        traverse(nodeFolder, FOLDER_FILTER, new FilePredicate() {
            @Override
            public void apply(final @Nonnull ResourceFile folder, final @Nonnull ResourcePath relativePath) {
                try {
                    final SiteNode siteNode = modelFactory.createSiteNode(DefaultSite.this, folder);
                    nodeMapByRelativePath.put(relativePath.asString(), siteNode);

                    if (!siteNode.isPlaceHolder()) {
                        final ResourcePath relativeUri = siteNode.getRelativeUri();
                        // Nodes which manage path params are registered with a relativeUri having a wildcard suffix
                        if ("true".equals(siteNode.getProperties()
                                .getProperty(SiteNode.PROPERTY_MANAGES_PATH_PARAMS, "false"))) {
                            final String suffix = relativeUri.asString().endsWith("/") ? "(|.*$)" : "(|/.*$)";
                            nodeMapByRelativeUri
                                    .putRegex("^" + RegexTreeMap.escape(relativeUri.asString()) + suffix, siteNode);
                        } else {
                            nodeMapByRelativeUri.put(relativeUri.asString(), siteNode);
                        }
                    }
                } catch (IOException | NotFoundException e) {
                    throw new RuntimeException(e);
                }
            }
        });

        if (logConfigurationEnabled) {
            logConfiguration("Documents by relative path:", documentMapByRelativePath);
            logConfiguration("Library by relative path:", libraryMapByRelativePath);
            logConfiguration("Media by relative path:", mediaMapByRelativePath);
            logConfiguration("Nodes by relative path:", nodeMapByRelativePath);
            logConfiguration("Nodes by relative URI:", nodeMapByRelativeUri);
        }
    }

    /*******************************************************************************************************************
     *
     * Traverse the file system with a {@link FilePredicate}.
     *
     * @param  folder      the folder to traverse
     * @param  fileFilter  the filter for directory contents
     * @param  predicate   the predicate
     *
     ******************************************************************************************************************/
    private void traverse(final @Nonnull ResourceFile folder, final @Nonnull FileFilter fileFilter,
            final @Nonnull FilePredicate predicate) {
        traverse(folder.getPath(), folder, fileFilter, predicate);
    }

    /*******************************************************************************************************************
     *
     * Traverse the file system with a {@link FilePredicate}.
     *
     * @param  file        the file to traverse
     * @param  fileFilter  the filter for directory contents
     * @param  predicate   the predicate
     *
     ******************************************************************************************************************/
    private void traverse(final @Nonnull ResourcePath rootPath, final @Nonnull ResourceFile file,
            final @Nonnull FileFilter fileFilter, final @Nonnull FilePredicate predicate) {
        log.trace("traverse({}, {}, {}, {})", rootPath, file, fileFilter, predicate);
        final ResourcePath relativePath = file.getPath().urlDecoded().relativeTo(rootPath);

        if (fileFilter.accept(file)) {
            predicate.apply(file, relativePath);
        }

        for (final ResourceFile child : file.findChildren().results()) {
            traverse(rootPath, child, fileFilter, predicate);
        }
    }

    /*******************************************************************************************************************
     *
     * Logs the configuration contained in the given map of properties.
     *
     ******************************************************************************************************************/
    private static void logConfiguration(final @Nonnull String name, Map<String, ?> propertyMap) {
        log.info(name);

        for (final Entry<String, ?> entry : propertyMap.entrySet()) {
            log.info(">>>> {}: {}", entry.getKey(), entry.getValue());
        }
    }

    /*******************************************************************************************************************
     *
     * FIXME Wrapper against ResourceFileSystem: its methods should throw NFE by themselves
     *
     ******************************************************************************************************************/
    @Nonnull
    private static ResourceFile findMandatoryFolder(final @Nonnull ResourceFileSystem fileSystem,
            final @Nonnull String path) throws NotFoundException {
        return NotFoundException.throwWhenNull(fileSystem.findFileByPath(path), "Cannot find folder: " + path);
        // don't log fileSystem.getRoot() since if fileSystem is broken it can trigger secondary errors
        // FileUtil.toFile(fileSystem.getRoot()).getAbsolutePath() + "/"  + path);
    }
}