package com.idega.content.util.resources;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Service;
import com.idega.core.business.DefaultSpringBean;
import com.idega.idegaweb.IWMainApplication;
import com.idega.idegaweb.include.ExternalLink;
import com.idega.idegaweb.include.JavaScriptLink;
import com.idega.idegaweb.include.RSSLink;
import com.idega.idegaweb.include.StyleSheetLink;
import com.idega.servlet.filter.IWBundleResourceFilter;
import com.idega.slide.business.IWSlideService;
import com.idega.util.CoreConstants;
import com.idega.util.FileUtil;
import com.idega.util.IOUtil;
import com.idega.util.ListUtil;
import com.idega.util.StringUtil;
import com.idega.util.resources.AbstractMinifier;
import com.idega.util.resources.CSSMinifier;
import com.idega.util.resources.JavaScriptMinifier;
import com.idega.util.resources.ResourcesAdder;
import com.idega.util.resources.ResourcesManager;
@Scope("request")
@Service(ResourcesManager.SPRING_BEAN_IDENTIFIER)
public class ResourcesManagerImpl extends DefaultSpringBean implements ResourcesManager {
private static final long serialVersionUID = -3876318831841387443L;
private static final Logger LOGGER = Logger.getLogger(ResourcesManagerImpl.class.getName());
private static final String WEB_PAGE_RESOURCES = "idegaCoreWebPageResources";
static final String CONCATENATED_RESOURCES = "idegaCoreConcatenatedRecources";
private List<JavaScriptLink> javaScriptActions;
private List<JavaScriptLink> javaScriptResources;
private List<StyleSheetLink> cssFiles;
private List<RSSLink> feedLinks;
private Map<String, String> mediaMap;
public List<JavaScriptLink> getJavaScriptResources() {
if (javaScriptResources == null) {
javaScriptResources = new ArrayList<JavaScriptLink>();
}
return javaScriptResources;
}
public List<JavaScriptLink> getJavaScriptActions() {
if (javaScriptActions == null) {
javaScriptActions = new ArrayList<JavaScriptLink>();
}
return javaScriptActions;
}
public List<StyleSheetLink> getCSSFiles() {
if (cssFiles == null) {
cssFiles = new ArrayList<StyleSheetLink>();
}
return cssFiles;
}
public Map<String, String> getMediaMap() {
if (mediaMap == null) {
mediaMap = new HashMap<String, String>();
}
return mediaMap;
}
private String getCachedConcatenatedResources(String resourceName) {
String concatenatedResourcesUri = getCachedResource(CONCATENATED_RESOURCES, resourceName);
if (StringUtil.isEmpty(concatenatedResourcesUri))
return null;
String cachedContent = getCachedResource(CONCATENATED_RESOURCES, new StringBuilder(resourceName.toString()).append("_content").toString());
if (StringUtil.isEmpty(cachedContent))
return null;
return concatenatedResourcesUri;
}
private String getUriToConcatenatedResourcesFromCache(List<? extends ExternalLink> resources, String fileType) {
if (ListUtil.isEmpty(resources) || StringUtil.isEmpty(fileType))
return null;
// Will try to get big file URI from requested resources
StringBuilder cacheName = new StringBuilder();
for (ExternalLink resource: resources) {
cacheName.append(resource.getUrl());
}
String uriToConcatenatedResources = getCachedConcatenatedResources(cacheName.toString());
if (!StringUtil.isEmpty(uriToConcatenatedResources)) {
resources.clear();
addNotifierAboutLoadedCSSFiles(resources, fileType);
}
return uriToConcatenatedResources;
}
public String getConcatenatedResources(List<? extends ExternalLink> resources, String fileType, String serverName) {
if (ListUtil.isEmpty(resources))
return null;
// Checking if ALL resources were concatenated already
String uriToAllConcatenatedResources = getUriToConcatenatedResourcesFromCache(resources, fileType);
if (!StringUtil.isEmpty(uriToAllConcatenatedResources)) {
return uriToAllConcatenatedResources;
}
// Didn't find concatenated file for ALL resources, will check every resource separately if it's available to be minified
String resourceContent = null;
List<ExternalLink> resourcesToLoad = new ArrayList<ExternalLink>();
Map<String, String> addedResources = new HashMap<String, String>();
for (ExternalLink resource: resources) {
resourceContent = getResource(WEB_PAGE_RESOURCES, resource, serverName);
if (StringUtil.isEmpty(resourceContent)) {
if (resourceContent == null) {
LOGGER.warning(new StringBuilder("Impossible to concatenate file: ").append(resource.getUrl()).toString());
} else {
resourceContent = new StringBuilder("/* ").append(resource.getUrl()).append(resourceContent == null ? " not available */" : " is empty */")
.toString();
}
}
if (!StringUtil.isEmpty(resourceContent)) {
// Resource is available to be concatenated
resourcesToLoad.add(resource);
addedResources.put(resource.getUrl(), resourceContent);
}
}
if (ListUtil.isEmpty(addedResources.values()))
return null; // Nothing to concatenate
// Removing AVAILABLE resources from request. Available resources will be concatenated to one big file
resources.removeAll(resourcesToLoad);
// Checking if there is concatenated resource from ONLY AVAILABLE resources
String concatenatedResourcesUri = getUriToConcatenatedResourcesFromCache(resources, fileType);
if (!StringUtil.isEmpty(concatenatedResourcesUri))
return concatenatedResourcesUri;
// Nothing found in cache, creating big file
StringBuilder allResources = null;
if (isJavaScriptFile(fileType)) {
allResources = new StringBuilder("var IdegaResourcesHandler = [");
allResources = addResourcesToList(allResources, resourcesToLoad);
allResources.append("];\n");
} else {
addNotifierAboutLoadedCSSFiles(resourcesToLoad, fileType);
}
if (allResources == null)
allResources = new StringBuilder();
StringBuilder key = new StringBuilder();
for (ExternalLink resource: resourcesToLoad) {
allResources.append(addedResources.get(resource.getUrl()));
key.append(resource);
}
concatenatedResourcesUri = copyConcatenatedResourcesToWebApp(allResources.toString(), fileType);
if (StringUtil.isEmpty(concatenatedResourcesUri))
return null;
// Putting in cache
setCachedResource(CONCATENATED_RESOURCES, key.toString(), concatenatedResourcesUri);
setCachedResource(CONCATENATED_RESOURCES, key.append("_content").toString(), allResources.toString());
return concatenatedResourcesUri;
}
private void addNotifierAboutLoadedCSSFiles(List<? extends ExternalLink> resources, String fileType) {
if (isJavaScriptFile(fileType))
return;
// Notifying about CSS files
String addedCSSFilesNotifier = getJavaScriptActionForLoadedCSSFiles(resources);
if (StringUtil.isEmpty(addedCSSFilesNotifier))
return;
JavaScriptLink loadedCSSFilesAction = new JavaScriptLink();
loadedCSSFilesAction.addAction(addedCSSFilesNotifier);
if (!getJavaScriptActions().contains(loadedCSSFilesAction))
getJavaScriptActions().add(loadedCSSFilesAction);
}
public boolean isJavaScriptFile(String fileType) {
return ResourcesAdder.FILE_TYPE_JAVA_SCRIPT.equals(fileType);
}
private String getJavaScriptActionForLoadedCSSFiles(List<? extends ExternalLink> cssFiles) {
if (ListUtil.isEmpty(cssFiles)) {
return null;
}
StringBuilder addedCSSFilesNotifier = new StringBuilder("IWCORE.addLoadedResources(");
addedCSSFilesNotifier = addResourcesToList(addedCSSFilesNotifier, cssFiles);
addedCSSFilesNotifier.append(");");
return addedCSSFilesNotifier.toString();
}
@SuppressWarnings("unchecked")
private StringBuilder addResourcesToList(StringBuilder content, List<? extends ExternalLink> resources) {
for (Iterator<ExternalLink> resourcesIter = (Iterator<ExternalLink>) resources.iterator(); resourcesIter.hasNext();) {
content.append(CoreConstants.QOUTE_SINGLE_MARK).append(resourcesIter.next().getUrl()).append(CoreConstants.QOUTE_SINGLE_MARK);
if (resourcesIter.hasNext()) {
content.append(CoreConstants.COMMA);
}
}
return content;
}
private String copyConcatenatedResourcesToWebApp(String content, String fileType) {
if (StringUtil.isEmpty(content)) {
return null;
}
String fileName = new StringBuilder().append(getCachePrefix()).append(ResourcesAdder.OPTIMIZIED_RESOURCES).append(System.currentTimeMillis())
.append(fileType).toString();
String uriToResources = IWMainApplication.getDefaultIWMainApplication().getBundle(CoreConstants.CORE_IW_BUNDLE_IDENTIFIER)
.getVirtualPathWithFileNameString(fileName);
File file = IWBundleResourceFilter.copyResourceFromJarOrCustomContentToWebapp(IWMainApplication.getDefaultIWMainApplication(), uriToResources, content);
return (file == null || !file.exists()) ? null : uriToResources;
}
private String getCachedResource(String cacheName, String resourceName) {
String prefix = getCachePrefix();
try {
Map<String, String> cache = getCache(new StringBuilder().append(prefix).append(cacheName).toString());
return cache.get(resourceName);
} catch(Exception e) {
LOGGER.log(Level.WARNING, "Error putting resource to cache: " + cacheName, e);
}
return null;
}
private boolean setCachedResource(String cacheName, String key, String value) {
String prefix = getCachePrefix();
try {
Map<String, String> cache = getCache(new StringBuilder().append(prefix).append(cacheName).toString());
String objectInCache = cache.put(key, value);
return objectInCache != null;
} catch(Exception e) {
LOGGER.log(Level.WARNING, "Error putting resource to cache: " + cacheName, e);
}
return false;
}
static final String getCachePrefix() {
try {
return IWMainApplication.getDefaultIWMainApplication().getSettings().getProperty("idega_core.resources_prefix", CoreConstants.EMPTY);
} catch(Exception e) {
LOGGER.log(Level.WARNING, "Error getting application property", e);
}
return CoreConstants.EMPTY;
}
private String getResource(String cacheName, ExternalLink resource, String serverName) {
String minifiedResource = getCachedResource(cacheName, resource.getUrl());
if (!StringUtil.isEmpty(minifiedResource)) {
return minifiedResource;
}
File resourceInWorkspace = IWBundleResourceFilter.copyResourceFromJarToWebapp(IWMainApplication.getDefaultIWMainApplication(), resource.getUrl());
minifiedResource = (resourceInWorkspace == null || !resourceInWorkspace.exists()) ?
getMinifiedResourceFromApplication(serverName, resource) :
getMinifiedResourceFromWorkspace(resourceInWorkspace, resource);
if (minifiedResource == null) {
return null;
}
setCachedResource(cacheName, resource.getUrl(), minifiedResource);
return minifiedResource;
}
private InputStream getStreamFromRepository(String path) {
if (StringUtil.isEmpty(path)) {
return null;
}
if (path.startsWith(CoreConstants.WEBDAV_SERVLET_URI) || path.startsWith(CoreConstants.PATH_FILES_ROOT)) {
try {
IWSlideService slideService = getServiceInstance(IWSlideService.class);
return slideService.getInputStream(path);
} catch (Exception e) {
LOGGER.log(Level.WARNING, "Error getting input stream from repository: " + path, e);
}
}
return null; // Object not in repository or error occurred
}
private String getMinifiedResourceFromApplication(String serverURL, ExternalLink resource) {
String resourceURI = resource.getUrl();
InputStream input = getStreamFromRepository(resourceURI);
if (input == null) {
IWMainApplication iwma = IWMainApplication.getDefaultIWMainApplication();
input = iwma.getResourceAsStream(resourceURI);
}
if (input == null) {
if (resourceURI.startsWith(CoreConstants.SLASH)) {
resourceURI = resourceURI.replaceFirst(CoreConstants.SLASH, CoreConstants.EMPTY);
}
String fullLink = new StringBuilder(serverURL).append(resourceURI).toString();
URL url = null;
try {
url = new URL(fullLink);
} catch (MalformedURLException e) {
LOGGER.warning("Error getting resource from: " + fullLink);
}
if (url == null) {
return null;
}
try {
input = url.openStream();
} catch (IOException e) {
LOGGER.warning("Error getting resource from: " + fullLink);
}
}
if (input == null) {
return null;
}
resource.setContentStream(input);
String minified = null;
try {
minified = getMinifiedResource(resource);
} catch(Exception e) {
LOGGER.log(Level.WARNING, "Error getting resource ('" + resourceURI + "') from stream!", e);
} finally {
IOUtil.closeInputStream(input);
}
return minified;
}
private String getMinifiedResourceFromWorkspace(File resource, ExternalLink resourceLink) {
if (resource == null || !resource.exists()) {
return null;
}
String fileContent = null;
try {
fileContent = FileUtil.getStringFromFile(resource);
} catch (IOException e) {
LOGGER.log(Level.WARNING, "Error getting content from file: " + resource.getName());
}
if (StringUtil.isEmpty(fileContent)) {
return null;
}
resourceLink.setContent(fileContent);
String minified = null;
try {
minified = getMinifiedResource(resourceLink);
} catch(Exception e) {
LOGGER.log(Level.WARNING, "Error while minifying resource: " + resource.getName(), e);
return fileContent;
}
if (StringUtil.isEmpty(minified)) {
LOGGER.log(Level.WARNING, "Error while minifying resource: " + resource.getName());
return fileContent;
}
return minified;
}
private String getMinifiedResource(ExternalLink resource) {
AbstractMinifier minifier = resource instanceof StyleSheetLink ? new CSSMinifier() : new JavaScriptMinifier();
return minifier.getMinifiedResource(resource);
}
public List<RSSLink> getFeedLinks() {
if (feedLinks == null) {
feedLinks = new ArrayList<RSSLink>();
}
return feedLinks;
}
}