/*
* #%L
* ACS AEM Tools Bundle
* %%
* Copyright (C) 2015 Adobe
* %%
* 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.
* #L%
*/
package com.adobe.acs.tools.tag_maker.impl;
import com.adobe.acs.tools.tag_maker.TagData;
import com.adobe.acs.tools.tag_maker.tagdataconverters.TagDataConverter;
import com.adobe.acs.tools.tag_maker.tagdataconverters.impl.DefaultConverterImpl;
import com.day.cq.tagging.InvalidTagFormatException;
import com.day.cq.tagging.Tag;
import com.day.cq.tagging.TagConstants;
import com.day.cq.tagging.TagManager;
import com.day.text.csv.Csv;
import org.apache.commons.io.IOUtils;
import org.apache.commons.io.LineIterator;
import org.apache.commons.lang.StringUtils;
import org.apache.felix.scr.annotations.Reference;
import org.apache.felix.scr.annotations.ReferenceCardinality;
import org.apache.felix.scr.annotations.ReferencePolicy;
import org.apache.felix.scr.annotations.References;
import org.apache.felix.scr.annotations.sling.SlingServlet;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.SlingHttpServletResponse;
import org.apache.sling.api.request.RequestParameter;
import org.apache.sling.api.servlets.SlingAllMethodsServlet;
import org.apache.sling.commons.json.JSONArray;
import org.apache.sling.commons.json.JSONException;
import org.apache.sling.commons.json.JSONObject;
import org.apache.sling.commons.osgi.PropertiesUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.jcr.Node;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import javax.servlet.ServletException;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
@SuppressWarnings("PackageAccessibility")
@SlingServlet(
label = "ACS AEM Tools - Tag Maker Servlet",
methods = {"GET", "POST"},
resourceTypes = {"acs-tools/components/tag-maker"},
selectors = {"init", "make-tags"},
extensions = {"json"}
)
@References({
@Reference(
name = "tagDataConverter",
referenceInterface = TagDataConverter.class,
policy = ReferencePolicy.DYNAMIC,
cardinality = ReferenceCardinality.OPTIONAL_MULTIPLE)
})
public class TagMakerServlet extends SlingAllMethodsServlet {
private static final Logger log = LoggerFactory.getLogger(TagMakerServlet.class);
private static final String DEFAULT_CHARSET = "UTF-8";
private static final String DEFAULT_CONVERTER = DefaultConverterImpl.LABEL;
private static final String NONE_CONVERTER = "__NONE";
private static final boolean DEFAULT_CLEAN = true;
private Map<String, TagDataConverter> tagDataConverters = new ConcurrentHashMap<String, TagDataConverter>();
@Override
protected final void doGet(SlingHttpServletRequest request, SlingHttpServletResponse response)
throws ServletException, IOException {
response.setContentType("application/json");
response.setCharacterEncoding("UTF-8");
final JSONArray jsonArray = new JSONArray();
try {
for (Map.Entry<String, TagDataConverter> entry : this.tagDataConverters.entrySet()) {
final JSONObject jsonObject = new JSONObject();
jsonObject.put("label", entry.getValue().getLabel());
jsonObject.put("value", entry.getKey());
jsonArray.put(jsonObject);
}
response.getWriter().print(jsonArray.toString());
} catch (JSONException e) {
response.setStatus(SlingHttpServletResponse.SC_INTERNAL_SERVER_ERROR);
}
}
@Override
protected final void doPost(SlingHttpServletRequest request, SlingHttpServletResponse response)
throws ServletException, IOException {
response.setContentType("application/json");
response.setCharacterEncoding("UTF-8");
final JSONObject jsonResponse = new JSONObject();
final TagManager tagManager = request.getResourceResolver().adaptTo(TagManager.class);
final RequestParameter charsetParam = request.getRequestParameter("charset");
final RequestParameter cleanParam = request.getRequestParameter("clean");
final RequestParameter delimiterParam = request.getRequestParameter("delimiter");
final RequestParameter fileParameter = request.getRequestParameter("file");
final RequestParameter separatorParam = request.getRequestParameter("separator");
final RequestParameter converterParam = request.getRequestParameter("converter");
final RequestParameter fallbackConverterParam = request.getRequestParameter("fallbackConverter");
boolean clean = DEFAULT_CLEAN;
if (cleanParam != null) {
clean = Boolean.valueOf(StringUtils.defaultIfEmpty(cleanParam.toString(),
String.valueOf(DEFAULT_CLEAN)));
}
String converter = DEFAULT_CONVERTER;
if (converterParam != null) {
converter = StringUtils.defaultIfEmpty(converterParam.toString(), DEFAULT_CONVERTER);
}
String fallbackConverter = NONE_CONVERTER;
if (fallbackConverterParam != null) {
fallbackConverter = StringUtils.defaultIfEmpty(fallbackConverterParam.toString(), NONE_CONVERTER);
}
String charset = DEFAULT_CHARSET;
if (charsetParam != null) {
charset = StringUtils.defaultIfEmpty(charsetParam.toString(), DEFAULT_CHARSET);
}
Character delimiter = null;
if (delimiterParam != null && StringUtils.isNotBlank(delimiterParam.toString())) {
delimiter = delimiterParam.toString().charAt(0);
}
Character separator = null;
if (separatorParam != null && StringUtils.isNotBlank(separatorParam.toString())) {
separator = separatorParam.toString().charAt(0);
}
final List<TagDataConverter> tagDataConverters = new ArrayList<TagDataConverter>();
final TagDataConverter primaryTagConverter = this.getTagDataConverter(converter);
if (primaryTagConverter != null) {
tagDataConverters.add(primaryTagConverter);
}
final TagDataConverter fallbackTagConverter = this.getTagDataConverter(fallbackConverter);
if (fallbackTagConverter != null) {
tagDataConverters.add(fallbackTagConverter);
}
if (tagDataConverters.isEmpty()) {
log.error("Could not find Tag Data Converter [ {} ]", converter);
response.setStatus(SlingHttpServletResponse.SC_INTERNAL_SERVER_ERROR);
} else if (fileParameter != null && fileParameter.getInputStream() != null) {
InputStream is = fileParameter.getInputStream();
final Csv csv = new Csv();
if (delimiter != null) {
log.debug("Setting Field Delimiter to [ {} ]", delimiter);
csv.setFieldDelimiter(delimiter);
}
if (separator != null) {
log.debug("Setting Field Separator to [ {} ]", separator);
csv.setFieldSeparatorRead(separator);
}
if (clean) {
is = this.stripLineEnds(is, charset, csv.getFieldSeparatorRead());
}
final Iterator<String[]> rows = csv.read(is, charset);
try {
request.getResourceResolver().adaptTo(Session.class).getWorkspace().getObservationManager().setUserData("acs-aem-tools.tag-maker");
final List<String> result = this.makeTags(tagManager, tagDataConverters, rows);
try {
jsonResponse.put("tagIds", result);
} catch (JSONException e) {
log.error("Could not serialized Tag Maker results into JSON", e);
}
this.addMessage(jsonResponse, result.size() + " tags were processed");
} catch (InvalidTagFormatException e) {
log.error("Could not create Tag due to illegal formatting", e);
this.addMessage(jsonResponse, "Could not create tags due to illegal formatting");
response.setStatus(SlingHttpServletResponse.SC_INTERNAL_SERVER_ERROR);
} catch (RepositoryException e) {
log.error("Could not save Tags to JCR", e);
this.addMessage(jsonResponse, "Could not save tags");
response.setStatus(SlingHttpServletResponse.SC_INTERNAL_SERVER_ERROR);
}
} else {
log.error("Could not find CSV file in request.");
this.addMessage(jsonResponse, "CSV file is missing");
response.setStatus(SlingHttpServletResponse.SC_INTERNAL_SERVER_ERROR);
}
response.getWriter().print(jsonResponse.toString());
}
private List<String> makeTags(final TagManager tagManager,
final List<TagDataConverter> tagDataConverters,
final Iterator<String[]> rows) throws InvalidTagFormatException, RepositoryException {
final Set<String> result = new LinkedHashSet<String>();
while (rows.hasNext()) {
final String[] row = rows.next();
log.debug("Processing data from row {}", Arrays.asList(row));
String tagId = null;
for (int i = 0; i < row.length; i++) {
TagData tagData = null;
final String element = StringUtils.trimToNull(row[i]);
if (element == null) {
log.warn("Element is null skipping this row [ {} ]", tagId);
break;
}
for (final TagDataConverter tagDataConverter : tagDataConverters) {
if (tagDataConverter.accepts(element)) {
tagData = tagDataConverter.convert(element);
break;
}
}
if (tagData == null) {
log.warn("Could not find a Tag Data Converter that accepts CSV element [ {} ]; skipping...");
break;
} else if (!tagData.isValid()) {
log.warn("Could not convert CSV element [ {} ] into valid Tag Data; skipping...");
break;
}
if (i == 0) {
// Tag Namespace
tagId = tagData.getName() + TagConstants.NAMESPACE_DELIMITER;
} else if (i == 1) {
// First Tag under Namespace
tagId += tagData.getName();
} else {
// Subsequent Tags
tagId += "/" + tagData.getName();
}
final Tag tag = tagManager.createTag(tagId, tagData.getTitle(), tagData.getDescription());
if(tagData.getLocalizedTitles()!=null){
Map<String,String> translationsMap = tagData.getLocalizedTitles();
Node node = tag.adaptTo(Node.class);
for (Map.Entry<String, String> entry : translationsMap.entrySet()) {
node.setProperty("jcr:title."+entry.getKey(), entry.getValue());
}
node.getSession().save();
}
log.trace("Created Tag [ {} ] with Title [ {} ]", tag.getTagID(), tagData.getTitle());
result.add(tagId);
}
}
if (tagManager.getSession().hasPendingChanges()) {
final long start = System.currentTimeMillis();
tagManager.getSession().save();
if (log.isInfoEnabled()) {
log.info("Persisting tags to JCR in {} ms", System.currentTimeMillis() - start);
}
}
return new ArrayList<String>(result);
}
private InputStream stripLineEnds(InputStream is, String charset, char chartoStrip) throws IOException {
log.debug("Stripping [ {} ] from the end of lines.", chartoStrip);
final ByteArrayOutputStream baos = new ByteArrayOutputStream();
final PrintStream printStream = new PrintStream(baos);
final LineIterator lineIterator = IOUtils.lineIterator(is, charset);
while (lineIterator.hasNext()) {
String line = StringUtils.stripToNull(lineIterator.next());
if (line != null) {
line = StringUtils.stripEnd(line, String.valueOf(chartoStrip));
printStream.println(line);
}
}
return new ByteArrayInputStream(baos.toByteArray());
}
private TagDataConverter getTagDataConverter(final String name) {
if (StringUtils.isNotBlank(name)) {
return this.tagDataConverters.get(name);
}
return null;
}
private void addMessage(JSONObject jsonObject, String message) {
try {
jsonObject.put("message", message);
} catch (JSONException e) {
log.error("Could not formulate JSON Response", e);
}
}
protected final void bindTagDataConverter(final TagDataConverter service,
final Map<Object, Object> props) {
final String type = PropertiesUtil.toString(props.get(TagDataConverter.PROP_NAME), null);
if (type != null) {
this.tagDataConverters.put(type, service);
}
}
protected final void unbindTagDataConverter(final TagDataConverter service,
final Map<Object, Object> props) {
final String type = PropertiesUtil.toString(props.get(TagDataConverter.PROP_NAME), null);
if (type != null) {
this.tagDataConverters.remove(type);
}
}
}