@Component(service = Servlet.class,
property = {
Constants.SERVICE_DESCRIPTION + "=Page and Asset Site Map Servlet",
ServletResolverConstants.SLING_SERVLET_METHODS + "=" + HttpConstants.METHOD_GET,
ServletResolverConstants.SLING_SERVLET_RESOURCE_TYPES + "=latam/components/amplifon-latam/structure/page-config",
ServletResolverConstants.SLING_SERVLET_SELECTORS + "=sitemap_image",
ServletResolverConstants.SLING_SERVLET_EXTENSIONS + "=xml"
})
public final class SiteMapServletImage extends SlingSafeMethodsServlet {
private static final long serialVersionUID = -7473561047318806388L;
private static final Logger logger = LoggerFactory.getLogger(SiteMapServletImage.class);
private static final FastDateFormat DATE_FORMAT = FastDateFormat.getInstance("yyyy-MM-dd'T'HH:mm:ss");
private static final String NS = "http://www.sitemaps.org/schemas/sitemap/0.9";
private static final String[] IMAGE_EXTENSIONS = {".jpg", ".png", ".svg", ".jpeg"};
private static final String HOME_PAGE_TEMPLATE_LATAM = "page-config";
private static final String UTF_8 = "UTF-8";
@Reference
private transient AmpSiteMapImage ampSiteMap;
@Reference
private transient AmpConfig config;
@Reference
private transient Externalizer externalizer;
private Map<String, String> externalizerDomains;
private String externalizerDomain;
private boolean includeInheritValue;
private boolean includeLastModified;
private String lastModifiedFormat;
private boolean isExternalizeEnabled;
private String[] changefreqProperties;
private String[] priorityProperties;
private String damAssetProperty;
private List<String> damAssetTypes;
private String excludeFromSiteMapProperty;
private String characterEncoding;
private boolean extensionlessUrls;
private boolean removeTrailingSlash;
private String[] imageProperties;
@Activate
protected void activate(Map<String, Object> properties) {
this.externalizerDomains = config.getExternalizerDomains();
this.isExternalizeEnabled = this.ampSiteMap.isExternalizeEnabled();
this.includeLastModified = ampSiteMap.isIncludeLastModified();
this.lastModifiedFormat = ampSiteMap.getLastModifiedFormat();
this.includeInheritValue = ampSiteMap.isIncludeInheritValue();
this.changefreqProperties = ArrayUtils.isNotEmpty(ampSiteMap.getFrequencyProperties())
? ampSiteMap.getFrequencyProperties()
: ArrayUtils.EMPTY_STRING_ARRAY;
this.priorityProperties = ArrayUtils.isNotEmpty(ampSiteMap.getPriorityProperties())
? ampSiteMap.getPriorityProperties()
: ArrayUtils.EMPTY_STRING_ARRAY;
this.damAssetProperty = ampSiteMap.getDamFolderProperty();
this.damAssetTypes = ArrayUtils.isNotEmpty(ampSiteMap.getDamAssetsMIMETypes())
? Arrays.asList(ampSiteMap.getDamAssetsMIMETypes())
: new ArrayList<>();
this.excludeFromSiteMapProperty = ampSiteMap.getExcludeSitemapProperty();
this.characterEncoding = ampSiteMap.getCharacterEncoding();
this.extensionlessUrls = ampSiteMap.hasExtensionlessURL();
this.removeTrailingSlash = ampSiteMap.hasRemovedSlash();
this.imageProperties = ampSiteMap.getImageProperties();
}
@Override
protected void doGet(SlingHttpServletRequest request, SlingHttpServletResponse response)
throws ServletException, IOException {
response.setContentType(request.getResponseContentType());
if (StringUtils.isNotEmpty(this.characterEncoding)) {
response.setCharacterEncoding(characterEncoding);
}
this.externalizerDomain = WcmUtils.getDomain(this.externalizerDomains, getCurrentPage(request).getPath(), getCurrentPage(request).getPath());
ResourceResolver resourceResolver = request.getResourceResolver();
Page page = getCurrentPage(request);
XMLStreamWriter stream = null;
try {
stream = XMLOutputFactory.newFactory().createXMLStreamWriter(response.getWriter());
stream.writeStartDocument(this.characterEncoding, "1.0");
stream.writeStartElement("", "urlset", NS);
stream.writeNamespace("", NS);
write(page, stream, resourceResolver);
for (Iterator<Page> children = page.listChildren(new PageFilter(false, true), true); children.hasNext(); ) {
write(children.next(), stream, resourceResolver);
}
if (!damAssetTypes.isEmpty() && StringUtils.isNotEmpty(damAssetProperty)) {
for (Resource assetFolder : getAssetFolders(page, resourceResolver)) {
writeAssets(stream, assetFolder, resourceResolver);
}
}
stream.writeEndElement();
stream.writeEndDocument();
} catch (XMLStreamException e) {
throw new IOException("Error writing XML response", e);
} finally {
if (stream != null) {
try {
stream.close();
} catch (XMLStreamException e) {
logger.error("Error closing XMLStreamWriter", e);
}
}
}
}
private Collection<Resource> getAssetFolders(Page page, ResourceResolver resolver) {
List<Resource> allAssetFolders = new ArrayList<>();
ValueMap properties = page.getProperties();
String[] configuredAssetFolderPaths = properties.get(damAssetProperty, String[].class);
if (configuredAssetFolderPaths != null) {
Arrays.sort(configuredAssetFolderPaths);
String prevPath = "#";
for (String configuredAssetFolderPath : configuredAssetFolderPaths) {
if (StringUtils.isNotBlank(configuredAssetFolderPath) && !configuredAssetFolderPath.equals(prevPath)
&& !StringUtils.startsWith(configuredAssetFolderPath, prevPath + "/")) {
Resource assetFolder = resolver.getResource(configuredAssetFolderPath);
if (assetFolder != null) {
prevPath = configuredAssetFolderPath;
allAssetFolders.add(assetFolder);
}
}
}
}
return allAssetFolders;
}
private void write(Page page, XMLStreamWriter stream, ResourceResolver resolver) throws XMLStreamException {
write(page.getPath(), StringUtils.EMPTY, page, stream, resolver);
}
private void write(String path, String lastmod, Page page, XMLStreamWriter stream, ResourceResolver resolver) throws XMLStreamException {
if (isHidden(page)) {
return;
}
stream.writeStartElement(NS, "url");
String loc = isExternalizeEnabled && StringUtils.isNotBlank(externalizerDomain)
? externalizer.externalLink(resolver, externalizerDomain, formatPath(path))
: path;
writeElement(stream, "loc", loc);
if (includeLastModified) {
FastDateFormat dateFormat = getDateFormat();
if (dateFormat != null) {
lastmod = getLastMod(lastmod, page, dateFormat);
if (StringUtils.isNotEmpty(lastmod)) {
writeElement(stream, "lastmod", lastmod);
}
}
}
ValueMap properties = page.getProperties();
if (includeInheritValue) {
HierarchyNodeInheritanceValueMap inheritanceValueMap = new HierarchyNodeInheritanceValueMap(page.getContentResource());
writeFirstPropertyValue(stream, "changefreq", changefreqProperties, inheritanceValueMap);
writeFirstPropertyValue(stream, "priority", priorityProperties, inheritanceValueMap);
} else {
writeFirstPropertyValue(stream, "changefreq", changefreqProperties, properties);
writeFirstPropertyValue(stream, "priority", priorityProperties, properties);
}
try {
handleImages(page, resolver, stream);
} catch (Exception e) {
throw new RuntimeException("Error handling images", e);
}
stream.writeEndElement();
}
private String formatPath(String path) {
String urlFormat = removeTrailingSlash ? "%s" : "%s/";
return !extensionlessUrls ? String.format("%s.html", path) : String.format(urlFormat, path);
}
private void handleImages(Page page, ResourceResolver resourceResolver, XMLStreamWriter stream) throws Exception {
String pagePath = page.getPath();
Resource pageResource = resourceResolver.getResource(pagePath + "/jcr:content");
ArrayList<String> images = new ArrayList<>();
this.checkImages(pageResource, images);
if (!images.isEmpty()) {
stream.writeStartElement(NS, "url");
String loc;
if (isExternalizeEnabled && StringUtils.isNotBlank(externalizerDomain)) {
if (!extensionlessUrls) {
loc = externalizer.externalLink(resourceResolver, externalizerDomain, String.format("%s.html", pagePath));
} else {
String urlFormat;
if (isHomePage(page)) {
urlFormat = "%s/";
} else {
urlFormat = ampSiteMap.hasRemovedSlash() ? "%s" : "%s/";
}
loc = externalizerDomain + String.format(urlFormat, pagePath);
}
} else {
loc = pagePath;
}
writeElement(stream, "loc", loc);
for (String image : images) {
stream.setPrefix("image", "");
stream.writeStartElement(NS, "image:image");
stream.writeStartElement(NS, "image:loc");
stream.writeCharacters(externalizerDomain + String.format("%s", image));
stream.writeEndElement();
stream.writeEndElement();
}
stream.writeEndElement();
}
}
private void checkImages(Resource res, ArrayList<String> images) {
if (res == null) return;
for (Resource child : res.getChildren()) {
if (child.hasChildren()) {
checkImages(child, images);
} else {
ValueMap map = child.getValueMap();
for (Map.Entry<String, Object> entry : map.entrySet()) {
if (isString(entry.getValue())) {
for (String imageExtension : IMAGE_EXTENSIONS) {
if (entry.getValue().toString().contains(imageExtension)) {
images.add(entry.getValue().toString());
}
}
}
}
}
}
}
private boolean isString(Object obj) {
return obj instanceof String;
}
private boolean isHomePage(Page page) {
String pageTemplate = page.getProperties().get(NameConstants.NN_TEMPLATE, String.class);
return HOME_PAGE_TEMPLATE_LATAM.equals(pageTemplate);
}
private String getLastMod(String lastmod, Page page, FastDateFormat dateFormat) {
if (StringUtils.isEmpty(lastmod)) {
Calendar cal = page.getLastModified();
if (cal != null) {
lastmod = dateFormat.format(cal);
}
} else {
try {
lastmod = dateFormat.format(DATE_FORMAT.parse(lastmod));
} catch (ParseException e) {
logger.error("Error parsing last modified date", e);
}
}
return lastmod;
}
private boolean isHidden(final Page page) {
ValueMap pageProperties = page.getProperties();
boolean excludeFromSitemap = pageProperties.get(this.excludeFromSiteMapProperty, false);
boolean noIndex = pageProperties.get("noIndex", false);
boolean canonical = StringUtils.isNotBlank(pageProperties.get("canonical", ""));
boolean redirect = pageProperties.get("redirect", false);
boolean disabled = pageProperties.get("disabled", false);
int statusCode = pageProperties.get("statusCode", -1);
return noIndex || redirect || disabled || statusCode == 404 || statusCode == 410 || (canonical && excludeFromSitemap);
}
private void writeAsset(Asset asset, XMLStreamWriter stream, ResourceResolver resolver) throws XMLStreamException {
stream.writeStartElement(NS, "url");
String loc = externalizer.externalLink(resolver, externalizerDomain, asset.getPath());
writeElement(stream, "loc", loc);
if (includeLastModified) {
long lastModified = asset.getLastModified();
if (lastModified > 0) {
writeElement(stream, "lastmod", DATE_FORMAT.format(lastModified));
}
}
stream.writeEndElement();
}
private void writeAssets(XMLStreamWriter stream, Resource assetFolder, ResourceResolver resolver) throws XMLStreamException {
for (Resource child : assetFolder.getChildren()) {
Asset asset = child.adaptTo(Asset.class);
if (asset != null && isAssetIncluded(asset)) {
writeAsset(asset, stream, resolver);
}
}
}
private boolean isAssetIncluded(Asset asset) {
String[] excludedAssetTypes = getExcludedAssetTypes();
if (excludedAssetTypes != null && excludedAssetTypes.length > 0) {
String[] mimeTypes = asset.getMetadataValue("jcr:mimeType").split(",");
for (String mimeType : mimeTypes) {
if (Arrays.asList(excludedAssetTypes).contains(mimeType)) {
return false;
}
}
}
return true;
}
private String[] getExcludedAssetTypes() {
String[] excludedAssetTypes = null;
if (damAssetTypes.contains("dam:Asset")) {
excludedAssetTypes = damAssetTypes.toArray(new String[0]);
excludedAssetTypes = Arrays.copyOf(excludedAssetTypes, excludedAssetTypes.length - 1);
}
return excludedAssetTypes;
}
private void writeElement(XMLStreamWriter stream, String name, String value) throws XMLStreamException {
stream.writeStartElement(NS, name);
if (StringUtils.isNotBlank(value)) {
stream.writeCharacters(value);
}
stream.writeEndElement();
}
private void writeFirstPropertyValue(XMLStreamWriter stream, String name, String[] properties, ValueMap propertiesMap) throws XMLStreamException {
String value = null;
if (propertiesMap != null) {
value = propertiesMap.get(name, String.class);
}
if (StringUtils.isEmpty(value)) {
for (String property : properties) {
value = propertiesMap.get(property, String.class);
if (StringUtils.isNotBlank(value)) {
break;
}
}
}
if (StringUtils.isNotBlank(value)) {
writeElement(stream, name, value);
}
}
private FastDateFormat getDateFormat() {
FastDateFormat dateFormat = null;
if (includeLastModified && StringUtils.isNotBlank(lastModifiedFormat)) {
try {
dateFormat = FastDateFormat.getInstance(lastModifiedFormat);
} catch (IllegalArgumentException e) {
logger.error("Invalid lastModifiedFormat: ", e);
}
}
return dateFormat;
}
private Page getCurrentPage(SlingHttpServletRequest request) {
Resource resource = request.getResource();
return resource.adaptTo(Page.class);
}
}
I have a sitemap that needs to show images, and I took inspiration from another sitemap that doesn’t have images, but I added my own. However, this sitemap doesn’t load correctly like the other one, and I want to understand if I duplicated the code incorrectly or if I didn’t take into account other factors. It should also consider child pages and exceptions.
New contributor
Katia Valentini is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.