/* * * * Copyright (c) 2016. David Sowerby * * * * 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 uk.q3c.krail.core.navigate.sitemap; import com.google.common.collect.ImmutableList; import com.google.inject.Inject; import com.google.inject.Provider; import com.google.inject.Singleton; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import uk.q3c.krail.core.config.ApplicationConfiguration; import uk.q3c.krail.core.config.ConfigKeys; import uk.q3c.krail.core.config.InheritingConfiguration; import uk.q3c.krail.core.eventbus.GlobalBusProvider; import uk.q3c.krail.core.i18n.DescriptionKey; import uk.q3c.krail.core.i18n.I18NKey; import uk.q3c.krail.core.i18n.LabelKey; import uk.q3c.krail.core.i18n.Translate; import uk.q3c.krail.core.navigate.sitemap.set.MasterSitemapQueue; import uk.q3c.krail.core.services.AbstractService; import uk.q3c.krail.core.services.RelatedServicesExecutor; import uk.q3c.krail.util.ResourceUtils; import uk.q3c.util.ClassNameUtils; import java.util.ArrayList; import java.util.List; import java.util.Map; @Singleton public class DefaultSitemapService extends AbstractService implements SitemapService { private static Logger log = LoggerFactory.getLogger(DefaultSitemapService.class); private final Provider<MasterSitemap> sitemapProvider; private final ApplicationConfiguration configuration; private final ResourceUtils resourceUtils; private final ClassNameUtils classNameUtils; private final Provider<DirectSitemapLoader> directSitemapLoaderProvider; private final Provider<AnnotationSitemapLoader> annotationSitemapLoaderProvider; private final SitemapFinisher sitemapFinisher; private MasterSitemapQueue masterSitemapQueue; private boolean loaded; private List<SitemapLoader> loaders; private StringBuilder report; private List<SitemapSourceType> sourceTypes; @SuppressFBWarnings({"FCBL_FIELD_COULD_BE_LOCAL"}) // ResourceUtils have to be injected @Inject protected DefaultSitemapService(Translate translate, Provider<DirectSitemapLoader> directSitemapLoaderProvider, Provider<AnnotationSitemapLoader> annotationSitemapLoaderProvider, Provider<MasterSitemap> sitemapProvider, SitemapFinisher sitemapFinisher, MasterSitemapQueue masterSitemapQueue, ApplicationConfiguration configuration, GlobalBusProvider globalBusProvider, ResourceUtils resourceUtils, ClassNameUtils classNameUtils, RelatedServicesExecutor servicesExecutor) { super(translate, globalBusProvider, servicesExecutor); this.annotationSitemapLoaderProvider = annotationSitemapLoaderProvider; this.directSitemapLoaderProvider = directSitemapLoaderProvider; this.sitemapProvider = sitemapProvider; this.sitemapFinisher = sitemapFinisher; this.masterSitemapQueue = masterSitemapQueue; this.configuration = configuration; this.resourceUtils = resourceUtils; this.classNameUtils = classNameUtils; setDescriptionKey(DescriptionKey.Sitemap_Service); } @Override protected void doStart() { //start with a new and empty model MasterSitemap sitemap = sitemapProvider.get(); loadSources(sitemap); LoaderReportBuilder lrb = new LoaderReportBuilder(loaders, classNameUtils); report = lrb.getReport(); sitemap.setReport(report.toString()); if (!loaded) { throw new SitemapException("No valid sources found"); } sitemap.lock(); masterSitemapQueue.addModel(sitemap); log.info("{}", report.toString()); } /** * Loads the Sitemap from all the sources specified in {@link #sourceTypes}. The first call to * {@link #loadSource(SitemapSourceType, MasterSitemap)} has {@code firstLoad} set to true. Subsequent calls have {@code firstLoad} * set to false */ private void loadSources(MasterSitemap sitemap) { extractSourcesFromConfig(); loaders = new ArrayList<>(); for (SitemapSourceType source : sourceTypes) { loadSource(source, sitemap); } log.debug("Checking Sitemap, sitemap has {} nodes", sitemap.getNodeCount()); sitemapFinisher.check(sitemap); log.debug("Sitemap checked, no errors found"); } /** * Loads the Sitemap with all sources of the specified {@code source type}. * * @param sourceType the source type to use * @param sitemap the sitemap to load */ private void loadSource(SitemapSourceType sourceType, MasterSitemap sitemap) { log.debug("Loading Sitemap from {}", sourceType); switch (sourceType) { case DIRECT: DirectSitemapLoader directSitemapLoader = directSitemapLoaderProvider.get(); loaders.add(directSitemapLoader); directSitemapLoader.load(sitemap); sitemapFinisher.setSourceModuleNames(directSitemapLoader.sourceModules()); loaded = true; return; case ANNOTATION: AnnotationSitemapLoader annotationSitemapLoader = annotationSitemapLoaderProvider.get(); loaders.add(annotationSitemapLoader); annotationSitemapLoader.load(sitemap); Map<String, AnnotationSitemapEntry> sources = annotationSitemapLoader.getSources(); if (sources != null) { sitemapFinisher.setAnnotationSources(sources.keySet()); } loaded = true; } } /** * Extracts the source types from the {@link InheritingConfiguration}, and populates {@link #sourceTypes}. The * default is to load from all source types ( */ private void extractSourcesFromConfig() { List<String> defaultValues = new ArrayList<>(); defaultValues.add(SitemapSourceType.DIRECT.name()); defaultValues.add(SitemapSourceType.ANNOTATION.name()); List<Object> list = configuration.getList(ConfigKeys.SITEMAP_SOURCES, defaultValues); sourceTypes = new ArrayList<>(); for (Object o : list) { try { SitemapSourceType source = SitemapSourceType.valueOf(o.toString() .toUpperCase()); sourceTypes.add(source); } catch (IllegalArgumentException iae) { log.warn("'{}' is not a valid Sitemap source type", o.toString()); } } // this will only happen if there is a key with an empty value if (sourceTypes.isEmpty()) { throw new SitemapException("At least one sitemap source must be specified"); } } @Override protected void doStop() { loaded = false; } public synchronized StringBuilder getReport() { return report; } public synchronized boolean isLoaded() { return loaded; } public synchronized ImmutableList<SitemapSourceType> getSourceTypes() { return ImmutableList.copyOf(sourceTypes); } @Override public I18NKey getNameKey() { return LabelKey.Sitemap_Service; } }