Skip to content

Content of file DefaultSiteNode.java

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

import javax.annotation.CheckForNull;
import javax.annotation.Nonnull;
import javax.inject.Inject;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.io.InputStream;
import java.io.IOException;
import org.springframework.beans.factory.annotation.Configurable;
import it.tidalwave.util.Finder;
import it.tidalwave.util.Id;
import it.tidalwave.util.NotFoundException;
import it.tidalwave.northernwind.core.model.ModelFactory;
import it.tidalwave.northernwind.core.model.RequestLocaleManager;
import it.tidalwave.northernwind.core.model.ResourceFile;
import it.tidalwave.northernwind.core.model.SiteNode;
import it.tidalwave.northernwind.core.model.ResourcePath;
import it.tidalwave.northernwind.core.model.spi.SiteNodeSupport;
import it.tidalwave.northernwind.frontend.ui.Layout;
import it.tidalwave.northernwind.frontend.impl.ui.DefaultLayout;
import it.tidalwave.northernwind.frontend.impl.ui.LayoutLoggerVisitor;
import lombok.Cleanup;
import lombok.Getter;
import lombok.ToString;
import lombok.extern.slf4j.Slf4j;
import static it.tidalwave.role.io.Unmarshallable._Unmarshallable_;
import static it.tidalwave.northernwind.util.UrlEncoding.*;

/***********************************************************************************************************************
 *
 * A node of the site, mapped to a given URL.
 *
 * @author  Fabrizio Giudici
 *
 **********************************************************************************************************************/
@Configurable(preConstruction = true) @Slf4j @ToString(callSuper = false, of = "relativeUri")
/* package */ class DefaultSiteNode extends SiteNodeSupport
  {
    @Nonnull
    private final Map<Locale, Layout> layoutMapByLocale = new HashMap<>();

    @Getter @Nonnull
    /* package */ InternalSite site;

    @Inject
    private InheritanceHelper inheritanceHelper;

    @Inject
    private RequestLocaleManager localeRequestManager;

    @CheckForNull
    private ResourcePath relativeUri;

    /* package */ int uriComputationCounter;

    /*******************************************************************************************************************
     *
     * Creates a new instance with the given configuration file and mapped to the given URI.
     *
     * @param  file          the file with the configuration
     * @param  relativeUri   the bound URI
     *
     ******************************************************************************************************************/
    public DefaultSiteNode (@Nonnull final ModelFactory modelFactory,
                            @Nonnull final InternalSite site,
                            @Nonnull final ResourceFile file)
      {
        super(modelFactory, file);
        this.site = site;
        try
          {
            loadLayouts();
          }
        catch (IOException e)
          {
            throw new RuntimeException(e);
          }
      }

    /*******************************************************************************************************************
     *
     * {@inheritDoc}
     *
     ******************************************************************************************************************/
    @Nonnull @Override
    public synchronized ResourcePath getRelativeUri()
      {
        if (relativeUri == null) // FIXME: is lazy evaluation really needed?
          {
            uriComputationCounter++;

            relativeUri = ResourcePath.EMPTY;
            final ResourceFile file = getResource().getFile();

            if (!file.equals(site.getNodeFolder()))
              {
                try
                  {
                    final String segment = getResource().getProperty(P_EXPOSED_URI).orElse(decodedUtf8(file.getName()));
                    relativeUri = relativeUri.appendedWith(getParent().getRelativeUri()).appendedWith(segment);
                  }
                catch (NotFoundException e) // FIXME: for getParent()
for getParent()
{ log.error("", e); // should never occur throw new RuntimeException(e); } } } log.trace(">>>> relativeUri: {}", relativeUri); return relativeUri; } /******************************************************************************************************************* * * {@inheritDoc} * ******************************************************************************************************************/ @Override @Nonnull public Layout getLayout() { return layoutMapByLocale.get(localeRequestManager.getLocales().get(0)); } /******************************************************************************************************************* * * {@inheritDoc} * ******************************************************************************************************************/ @Override @Nonnull public Finder<SiteNode> findChildren() { throw new UnsupportedOperationException("Not supported yet."); } /******************************************************************************************************************* * * {@inheritDoc} * ******************************************************************************************************************/ private void loadLayouts() throws IOException { for (final Locale locale : localeRequestManager.getLocales()) { Layout layout = null; // Cannot be implemented by recursion, since each SiteNode could have a local override for its Layout - // local overrides are not inherited. Perhaps you could do if you keep two layouts per Node, one without the override. // On the other hand, inheritanceHelper encapsulates the local override policy, which applies also to Properties... final List<ResourceFile> layoutFiles = inheritanceHelper.getInheritedPropertyFiles(getResource().getFile(), locale, "Components"); for (final ResourceFile layoutFile : layoutFiles) { final DefaultLayout overridingLayout = loadLayout(layoutFile); layout = (layout == null) ? overridingLayout : layout.withOverride(overridingLayout); if (log.isDebugEnabled()) { overridingLayout.accept(new LayoutLoggerVisitor(LayoutLoggerVisitor.Level.DEBUG)); } } layout = (layout != null) ? layout : modelFactory.createLayout() .withId(new Id("")) .withType("emptyPlaceholder") .build(); if (site.isLogConfigurationEnabled() || log.isDebugEnabled()) { log.debug(">>>> layout for {} {}:", getResource().getFile().getPath().asString(), locale); layout.accept(new LayoutLoggerVisitor(LayoutLoggerVisitor.Level.INFO)); } layoutMapByLocale.put(locale, layout); } } /******************************************************************************************************************* * * Returns the parent {@code SiteNode}. * * @return the parent node * @throws NotFoundException if the parent doesn't exist * ******************************************************************************************************************/ @Nonnull private SiteNode getParent() throws NotFoundException { final ResourcePath parentRelativePath = getResource().getFile().getParent().getPath().urlDecoded() .relativeTo(site.getNodeFolder().getPath()); return site.find(_SiteNode_).withRelativePath(parentRelativePath).result(); } /******************************************************************************************************************* * ******************************************************************************************************************/ @Nonnull private DefaultLayout loadLayout (@Nonnull final ResourceFile layoutFile) throws IOException { log.trace(">>>> reading layout from {}...", layoutFile.getPath().asString()); @Cleanup final InputStream is = layoutFile.getInputStream(); return modelFactory.createLayout().build().as(_Unmarshallable_).unmarshal(is); } }