package fr.openwide.core.wicket.more.notification.service; import java.io.FilterInputStream; import java.io.IOException; import java.io.InputStream; import java.nio.charset.Charset; import java.util.Map; import javax.annotation.Nullable; import org.apache.commons.lang3.tuple.Pair; import org.apache.wicket.util.resource.IResourceStream; import org.apache.wicket.util.resource.ResourceStreamNotFoundException; import org.apache.wicket.util.time.Time; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.common.collect.Maps; import com.helger.commons.io.IHasInputStream; import com.helger.css.ECSSVersion; import com.helger.css.decl.CascadingStyleSheet; import com.helger.css.reader.CSSReader; import fr.openwide.core.jpa.exception.ServiceException; import fr.openwide.core.wicket.more.css.lesscss.LessCssResourceReference; import fr.openwide.core.wicket.more.notification.service.impl.SimplePhlocCssHtmlNotificationCssRegistry; public class PhlocCssHtmlNotificationCssServiceImpl implements IHtmlNotificationCssService { private static final Logger LOGGER = LoggerFactory.getLogger(PhlocCssHtmlNotificationCssServiceImpl.class); private final Map<LessCssResourceReference, Pair<IHtmlNotificationCssRegistry, Time>> registryCache = Maps.newHashMap(); private final Map<String, LessCssResourceReference> registrySpecs = Maps.newHashMap(); @Override public synchronized boolean hasRegistry(String componentVariation) { return registrySpecs.containsKey(componentVariation); } @Override public synchronized IHtmlNotificationCssRegistry getRegistry(String componentVariation) throws ServiceException { LessCssResourceReference cssResourceReference = registrySpecs.get(componentVariation); if (cssResourceReference == null) { return null; } return getRegistry(cssResourceReference); } private synchronized IHtmlNotificationCssRegistry getRegistry(LessCssResourceReference cssResourceReference) throws ServiceException { IResourceStream resourceStream = cssResourceReference.getResource().getResourceStream(); if (resourceStream == null) { // NOSONAR findbugs:RCN_REDUNDANT_NULLCHECK_OF_NONNULL_VALUE throw new ServiceException("Could not retrieve resource stream for resource reference " + cssResourceReference + " when accessing a notification CSS style registry"); } Time currentResourceLastModifiedTime = resourceStream.lastModifiedTime(); Pair<IHtmlNotificationCssRegistry, Time> cacheEntry = registryCache.get(cssResourceReference); if (cacheEntry != null && cacheEntry.getRight().equals(currentResourceLastModifiedTime)) { return cacheEntry.getLeft(); } else { IHtmlNotificationCssRegistry registry = createRegistry(resourceStream); registryCache.put(cssResourceReference, Pair.of(registry, currentResourceLastModifiedTime)); return registry; } } @Override public synchronized void registerStyles(String componentVariation, LessCssResourceReference cssResourceReference) throws ServiceException { if (registrySpecs.containsKey(componentVariation)) { LOGGER.warn("Overrding Html notification style registry for component variation " + componentVariation); } registrySpecs.put(componentVariation, cssResourceReference); registryCache.remove(cssResourceReference); } private IHtmlNotificationCssRegistry createRegistry(IResourceStream resourceStream) throws ServiceException { CascadingStyleSheet sheet = CSSReader.readFromStream(new WicketResourceStreamToPhlocInputStreamProviderWrapper(resourceStream), Charset.defaultCharset(), ECSSVersion.CSS30); if (sheet == null) { throw new ServiceException("An error occurred while parsing notification CSS; see the logs for details."); } else { return new SimplePhlocCssHtmlNotificationCssRegistry(sheet); } } private static class WicketResourceStreamToPhlocInputStreamProviderWrapper implements IHasInputStream { private final IResourceStream resourceStream; public WicketResourceStreamToPhlocInputStreamProviderWrapper(IResourceStream resourceStream) { super(); this.resourceStream = resourceStream; } @Override @Nullable public InputStream getInputStream() { try { return new FilterInputStream(resourceStream.getInputStream()) { @Override public void close() throws IOException { resourceStream.close(); // The wicket way: close the resource stream, not the input stream } }; } catch (ResourceStreamNotFoundException e) { LOGGER.error("Error while getting a resource for CSS parsing", e); return null; // The phloc way: return null, do not throw exceptions } } } }