blob: 4d96dee0cf079edc054ae7edf7895109c01edf5a [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 org.apache.sling.sitemap.impl.builder.extensions;
import org.apache.sling.sitemap.builder.extensions.GoogleVideoExtension;
import org.apache.sling.sitemap.spi.builder.AbstractExtension;
import org.apache.sling.sitemap.spi.builder.SitemapExtensionProvider;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.osgi.service.component.annotations.Component;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamWriter;
import java.time.LocalDate;
import java.time.OffsetDateTime;
import java.time.format.DateTimeFormatter;
import java.time.temporal.TemporalAccessor;
import java.util.*;
import java.util.stream.Collectors;
@Component(
property = {
SitemapExtensionProvider.PROPERTY_INTERFACE + "=org.apache.sling.sitemap.builder.extensions.GoogleVideoExtension",
SitemapExtensionProvider.PROPERTY_PREFIX + "=video",
SitemapExtensionProvider.PROPERTY_NAMESPACE + "=http://www.google.com/schemas/sitemap-video/1.1",
SitemapExtensionProvider.PROPERTY_LOCAL_NAME + "=video"
}
)
public class GoogleVideoExtensionProvider implements SitemapExtensionProvider {
private static final Logger LOG = LoggerFactory.getLogger(GoogleVideoExtensionProvider.class);
@Override
@NotNull
public AbstractExtension newInstance() {
return new ExtensionImpl();
}
private static class PriceImpl {
private String currency;
private String type;
private String resolution;
private String price;
}
private static class ExtensionImpl extends AbstractExtension implements GoogleVideoExtension {
private String thumbnailLocation;
private String title;
private String description;
private String contentLocation;
private String playerLocation;
private String duration;
private TemporalAccessor expirationDate;
private String rating;
private String viewCount;
private TemporalAccessor publicationDate;
private List<String> tags;
private String category;
private String familyFriendly;
private String accessRestrictions;
private String accessRestrictionsRel;
private String platformRestrictions;
private String platformRestrictionsRel;
private Collection<PriceImpl> prices;
private String requiresSubscription;
private String uploader;
private String uploaderInfo;
private String live;
private static String booleanToString(Boolean bool) {
if (bool != null) {
return bool ? "yes" : "no";
} else {
return null;
}
}
private static String required(String object, String message) throws XMLStreamException {
if (object == null) {
throw new XMLStreamException(message);
}
return object;
}
private static void write(XMLStreamWriter writer, String value, String tag) throws XMLStreamException {
writer.writeStartElement(tag);
writer.writeCharacters(value);
writer.writeEndElement();
}
private static void write(XMLStreamWriter writer, TemporalAccessor date, String tag) throws XMLStreamException {
try {
if (date instanceof LocalDate) {
write(writer, DateTimeFormatter.ISO_LOCAL_DATE.format(date), tag);
} else {
write(writer, DateTimeFormatter.ISO_OFFSET_DATE_TIME.format(date), tag);
}
} catch (RuntimeException ex) {
throw new XMLStreamException("failed to write " + tag, ex);
}
}
private static void writeCond(XMLStreamWriter writer, String value, String tag) throws XMLStreamException {
if (value != null) {
write(writer, value, tag);
}
}
private static void writeCond(XMLStreamWriter writer, TemporalAccessor value, String tag) throws XMLStreamException {
if (value != null) {
write(writer, value, tag);
}
}
private static void writeReq(XMLStreamWriter writer, String value, String tag, String msg) throws XMLStreamException {
write(writer, required(value, msg), tag);
}
@Override
@NotNull
public GoogleVideoExtension setThumbnail(@NotNull String thumbnailLocation) {
this.thumbnailLocation = thumbnailLocation;
return this;
}
@Override
@NotNull
public GoogleVideoExtension setTitle(@NotNull String title) {
this.title = title;
return this;
}
@Override
@NotNull
public GoogleVideoExtension setDescription(@NotNull String description) {
this.description = description;
return this;
}
@Override
@NotNull
public GoogleVideoExtension setUrl(String contentLocation) {
this.contentLocation = contentLocation;
return this;
}
@Override
@NotNull
public GoogleVideoExtension setPlayerUrl(String playerLocation) {
this.playerLocation = playerLocation;
return this;
}
@Override
@NotNull
public GoogleVideoExtension setDuration(Integer duration) {
if (duration != null) {
if (duration < 0 || duration > 28800) {
LOG.warn("Adjusting duration as it is out of bounds (0, 28800): {}", duration);
duration = Math.max(0, Math.min(duration, 28800));
}
this.duration = String.valueOf(duration);
} else {
this.duration = null;
}
return this;
}
@Override
@NotNull
public GoogleVideoExtension setExpirationDate(LocalDate date) {
this.expirationDate = date;
return this;
}
@Override
@NotNull
public GoogleVideoExtension setExpirationDate(OffsetDateTime date) {
this.expirationDate = date;
return this;
}
@Override
@NotNull
public GoogleVideoExtension setRating(Float rating) {
if (rating != null) {
if (rating < 0 || rating > 5) {
LOG.warn("Adjusting rating as it is out of bounds (0,5): {}", rating);
rating = Math.max(0.0f, Math.min(rating, 5.0f));
}
this.rating = String.valueOf(rating);
} else {
this.rating = null;
}
return this;
}
@Override
@NotNull
public GoogleVideoExtension setViewCount(Integer viewCount) {
if (viewCount != null) {
if (viewCount < 0) {
LOG.warn("Adjusting negative view count: {}", viewCount);
viewCount = 0;
}
this.viewCount = String.valueOf(viewCount);
} else {
this.viewCount = null;
}
return this;
}
@Override
@NotNull
public GoogleVideoExtension setPublicationDate(LocalDate date) {
this.publicationDate = date;
return this;
}
@Override
@NotNull
public GoogleVideoExtension setPublicationDate(OffsetDateTime date) {
this.publicationDate = date;
return this;
}
@Override
@NotNull
public GoogleVideoExtension setTags(Collection<String> tags) {
this.tags = new ArrayList<>(tags);
return this;
}
@Override
@NotNull
public GoogleVideoExtension setCategory(String category) {
this.category = category;
return this;
}
@Override
@NotNull
public GoogleVideoExtension setFamilyFriendly(Boolean familyFriendly) {
this.familyFriendly = booleanToString(familyFriendly);
return this;
}
@Override
@NotNull
public GoogleVideoExtension setAccessRestriction(@Nullable Access restriction, @Nullable Collection<String> countryCodes) {
String accessRestrictionsCorrected = countryCodes != null
? countryCodes.stream()
.map(countryCode -> countryCode.toUpperCase(Locale.ROOT))
.filter(countryCode -> countryCode.length() == 2)
.collect(Collectors.joining(" "))
: null;
if (restriction != null && accessRestrictionsCorrected != null && accessRestrictionsCorrected.length() > 0) {
this.accessRestrictions = accessRestrictionsCorrected;
this.accessRestrictionsRel = restriction.getValue();
} else {
this.accessRestrictions = null;
this.accessRestrictionsRel = null;
}
return this;
}
@Override
@NotNull
public GoogleVideoExtension setPlatformRestriction(@Nullable Access restriction, @Nullable Collection<Platform> platforms) {
if (restriction != null && platforms != null) {
this.platformRestrictions = platforms.stream().map(Platform::getValue).collect(Collectors.joining(" "));
this.platformRestrictionsRel = restriction.getValue();
} else {
this.platformRestrictions = null;
this.platformRestrictionsRel = null;
}
return this;
}
@Override
@NotNull
public GoogleVideoExtension addPrice(float price, @NotNull String currency, @Nullable PriceType type, @Nullable Resolution resolution) {
if (prices == null) {
this.prices = new ArrayList<>();
}
PriceImpl newPrice = new PriceImpl();
newPrice.price = String.valueOf(price);
newPrice.currency = currency;
newPrice.type = type != null ? type.getValue() : null;
newPrice.resolution = resolution != null ? resolution.getValue() : null;
prices.add(newPrice);
return this;
}
@Override
@NotNull
public GoogleVideoExtension setRequiresSubscription(Boolean requiresSubscription) {
this.requiresSubscription = booleanToString(requiresSubscription);
return this;
}
@Override
@NotNull
public GoogleVideoExtension setUploader(String uploader) {
this.uploader = uploader;
return this;
}
@Override
@NotNull
public GoogleVideoExtension setUploaderUrl(String uploaderInfo) {
this.uploaderInfo = uploaderInfo;
return this;
}
@Override
@NotNull
public GoogleVideoExtension setLive(Boolean live) {
this.live = booleanToString(live);
return this;
}
@Override
public void writeTo(@NotNull XMLStreamWriter writer) throws XMLStreamException {
writeReq(writer, thumbnailLocation, "thumbnail_loc", "thumbnail location missing");
writeReq(writer, title, "title", "title missing");
writeReq(writer, description, "description", "description missing");
if (contentLocation == null && playerLocation == null) {
throw new XMLStreamException("either content location or player location is required");
} else if (contentLocation != null) {
write(writer, contentLocation, "content_loc");
} else {
write(writer, playerLocation, "player_loc");
}
writeCond(writer, duration, "duration");
writeCond(writer, expirationDate, "expiration_date");
writeCond(writer, rating, "rating");
writeCond(writer, viewCount, "view_count");
writeCond(writer, publicationDate, "publication_date");
writeTags(writer);
writeCond(writer, category, "category");
writeCond(writer, familyFriendly, "family_friendly");
writeRestriction(writer, accessRestrictions, accessRestrictionsRel, "restriction");
writePrices(writer);
writeCond(writer, requiresSubscription, "requires_subscription");
writeUploader(writer);
writeRestriction(writer, platformRestrictions, platformRestrictionsRel, "platform");
writeCond(writer, live, "live");
}
private void writeTags(XMLStreamWriter writer) throws XMLStreamException {
if (tags != null) {
if (tags.size() > 32) {
LOG.warn("Truncating tags as more then 32 were given: {}", tags.size());
}
for (String tag : tags.subList(0, Math.min(tags.size(), 32))) {
writer.writeStartElement("tag");
writer.writeCharacters(tag);
writer.writeEndElement();
}
}
}
private void writePrices(XMLStreamWriter writer) throws XMLStreamException {
if (prices != null) {
for (PriceImpl price : prices) {
writer.writeStartElement("price");
writer.writeAttribute("currency", price.currency);
if (price.type != null) {
writer.writeAttribute("type", price.type);
}
if (price.resolution != null) {
writer.writeAttribute("resolution", price.resolution);
}
writer.writeCharacters(price.price);
writer.writeEndElement();
}
}
}
private void writeUploader(XMLStreamWriter writer) throws XMLStreamException {
if (uploader != null) {
writer.writeStartElement("uploader");
if (uploaderInfo != null) {
writer.writeAttribute("info", uploaderInfo);
}
writer.writeCharacters(uploader);
writer.writeEndElement();
}
}
private static void writeRestriction(XMLStreamWriter writer, String value, String rel, String tag) throws XMLStreamException {
if (value != null) {
writer.writeStartElement(tag);
writer.writeAttribute("relationship", rel);
writer.writeCharacters(value);
writer.writeEndElement();
}
}
}
}