/* * Copyright (C) 2012 Tirasa * * 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. */ package net.tirasa.hct.cocoon.sax; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Map.Entry; import javax.jcr.RepositoryException; import net.tirasa.hct.cocoon.cache.AvailabilityLocaleCacheKey; import net.tirasa.hct.cocoon.sax.Constants.Attribute; import net.tirasa.hct.cocoon.sax.Constants.Availability; import net.tirasa.hct.cocoon.sax.Constants.Element; import net.tirasa.hct.cocoon.sax.Constants.StartEndDocumentFilter; import net.tirasa.hct.cocoon.sax.Constants.State; import net.tirasa.hct.hstbeans.HCTTaxonomyCategoryBean; import net.tirasa.hct.hstbeans.HippoCompoundDocument; import net.tirasa.hct.hstbeans.HippoDate; import net.tirasa.hct.hstbeans.RelatedDocs; import net.tirasa.hct.repository.HCTConnManager; import net.tirasa.hct.repository.HCTDocument; import net.tirasa.hct.repository.HCTQuery; import net.tirasa.hct.repository.HCTQueryResult; import net.tirasa.hct.repository.HCTTraversal; import net.tirasa.hct.util.ObjectUtils; import net.tirasa.hct.util.TaxonomyUtils; import org.apache.cocoon.configuration.Settings; import org.apache.cocoon.pipeline.ProcessingException; import org.apache.cocoon.pipeline.caching.CacheKey; import org.apache.cocoon.pipeline.component.CachingPipelineComponent; import org.apache.cocoon.sax.AbstractSAXTransformer; import org.apache.cocoon.sax.util.XMLUtils; import org.apache.commons.lang3.LocaleUtils; import org.apache.commons.lang3.StringUtils; import org.hippoecm.hst.content.beans.ObjectBeanManagerException; import org.hippoecm.hst.content.beans.standard.HippoAsset; import org.hippoecm.hst.content.beans.standard.HippoBean; import org.hippoecm.hst.content.beans.standard.HippoDocument; import org.hippoecm.hst.content.beans.standard.HippoFacetSelect; import org.hippoecm.hst.content.beans.standard.HippoFolder; import org.hippoecm.hst.content.beans.standard.HippoGalleryImageSet; import org.hippoecm.hst.content.beans.standard.HippoHtml; import org.hippoecm.hst.content.beans.standard.HippoItem; import org.hippoecm.hst.content.beans.standard.HippoMirror; import org.hippoecm.repository.api.HippoNodeType; import org.onehippo.forge.ecmtagging.TaggingNodeType; import org.onehippo.forge.ecmtagging.providers.AllTagsProvider; import org.onehippo.taxonomy.api.TaxonomyNodeTypes; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.xml.sax.Attributes; import org.xml.sax.SAXException; import org.xml.sax.XMLReader; public class HippoRepositoryTransformer extends AbstractSAXTransformer implements CachingPipelineComponent { private static final Logger LOG = LoggerFactory.getLogger(HippoRepositoryTransformer.class); private static final String PARAM_LOCALE = "locale"; private static final String PARAM_AVAILABILITY = "availability"; private Locale locale; private Availability availability; private State state; private transient HCTDocument hctDocument; private transient HCTTraversal hctTraversal; private transient HCTQuery hctQuery; @Override @SuppressWarnings("unchecked") public void setConfiguration(final Map<String, ? extends Object> configuration) { this.setup((Map<String, Object>) configuration); } @Override public void setup(final Map<String, Object> parameters) { if (parameters == null) { return; } if (parameters.containsKey(PARAM_AVAILABILITY)) { try { availability = Availability.valueOf((String) parameters.get(PARAM_AVAILABILITY)); } catch (IllegalArgumentException e) { availability = Availability.live; LOG.warn("Invalid availability specified, reverting to " + availability, e); } } else { availability = Availability.live; LOG.warn("No availability specified, reverting to " + availability); } Locale defaultLocale; if (parameters.containsKey(Settings.ROLE)) { final Settings settings = (Settings) parameters.get(Settings.ROLE); final String localeString = settings.getProperty("net.tirasa.hct.defaultLocale", Locale.getDefault().getLanguage()); try { defaultLocale = LocaleUtils.toLocale(localeString); } catch (IllegalArgumentException e) { defaultLocale = Locale.getDefault(); LOG.error("Could not parse provided '{}' as default Locale", localeString, e); } } else { defaultLocale = Locale.getDefault(); } if (parameters.containsKey(PARAM_LOCALE)) { try { locale = LocaleUtils.toLocale((String) parameters.get(PARAM_LOCALE)); } catch (IllegalArgumentException e) { locale = defaultLocale; LOG.error("Could not parse provided '{}' as Locale", parameters.get(PARAM_LOCALE), e); } } else { locale = defaultLocale; LOG.warn("No locale specified, reverting to " + locale); } state = State.OUTSIDE; } @Override public void finish() { state = null; super.finish(); } @Override public CacheKey constructCacheKey() { return new AvailabilityLocaleCacheKey(availability, locale); } private void findAndDumpImagesAndAssets(final HCTConnManager connManager, final HippoDocument doc, final HippoItemXMLDumper dumper) throws ObjectBeanManagerException, SAXException { final List<HippoGalleryImageSet> images = new ArrayList<HippoGalleryImageSet>(); final List<HippoAsset> assets = new ArrayList<HippoAsset>(); for (HippoMirror mirror : doc.getChildBeans(HippoMirror.class)) { final HippoBean bean = mirror.getReferencedBean(); if (bean != null) { if (bean instanceof HippoGalleryImageSet) { final HippoItem subElement = ObjectUtils.getHippoItemByUuid(connManager, (String) ((HippoFacetSelect) mirror).getProperty(HippoNodeType.HIPPO_DOCBASE)); if (subElement instanceof HippoGalleryImageSet) { images.add((HippoGalleryImageSet) subElement); } } if (bean instanceof HippoAsset) { assets.add((HippoAsset) bean); } } } dumper.dumpImages(images, Element.IMAGE.getName(), true); dumper.dumpAssets(assets, Element.ASSET.getName(), true, hctDocument.getDateFormat(), locale); } private void compounds(final HCTConnManager connManager, final HippoDocument container, final HippoItemXMLDumper dumper, final XMLReader xmlReader) throws SAXException, RepositoryException, ObjectBeanManagerException, IOException { dumper.startHippoCompounds(); for (HippoDocument compound : container.getChildBeans(HippoCompoundDocument.class)) { dumper.startHippoCompound(compound); // 1 Compound properties for (Entry<String, Object> entry : compound.getProperties().entrySet()) { dumper.dumpField(entry, hctDocument.getDateFormat(), locale); } // 2 Compound date fields for (HippoDate date : compound.getChildBeans(HippoDate.class)) { dumper.dumpDate(date.getName(), date.getCalendar(), hctDocument.getDateFormat(), locale); } // 3 Compound HTML fields for (HippoHtml rtf : compound.getChildBeans(HippoHtml.class)) { dumper.dumpHtml(connManager, rtf, xmlReader, hctDocument.getDateFormat(), locale); } // 4 Compound images and assets findAndDumpImagesAndAssets(connManager, compound, dumper); compounds(connManager, compound, dumper, xmlReader); dumper.endHippoCompound(compound); } dumper.endHippoCompounds(); } private void document(final HCTConnManager connManager) throws ObjectBeanManagerException, SAXException, IOException, RepositoryException { final HippoDocument doc = hctDocument.getHippoDocument(connManager, locale, availability); final HippoItemXMLDumper dumper = new HippoItemXMLDumper(this.getSAXConsumer()); // 1. document dumper.startHippoItem(doc, doc.getPath()); // 2. properties for (Entry<String, Object> entry : doc.getProperties().entrySet()) { if (TaggingNodeType.PROP_TAGS.equals(entry.getKey())) { dumper.dumpTags((String[]) entry.getValue()); } else if (TaxonomyNodeTypes.HIPPOTAXONOMY_KEYS.equals(entry.getKey())) { dumper.dumpTaxonomies(TaxonomyUtils.getTaxonomies(connManager, (String[]) entry.getValue()), locale); } else { dumper.dumpField(entry, hctDocument.getDateFormat(), locale); } } // 3. Date fields for (HippoDate date : doc.getChildBeans(HippoDate.class)) { dumper.dumpDate(date.getName(), date.getCalendar(), hctDocument.getDateFormat(), locale); } // 4. HTML fields final XMLReader xmlReader = new StartEndDocumentFilter(XMLUtils.createXMLReader(this.getSAXConsumer())); xmlReader.setContentHandler(this.getSAXConsumer()); for (HippoHtml rtf : doc.getChildBeans(HippoHtml.class)) { dumper.dumpHtml(connManager, rtf, xmlReader, hctDocument.getDateFormat(), locale); } // 5. Images and Assets findAndDumpImagesAndAssets(connManager, doc, dumper); // 6. Compounds compounds(connManager, doc, dumper, xmlReader); // 7. Related documents final List<HippoDocument> relDocs = new ArrayList<HippoDocument>(); for (RelatedDocs docs : doc.getChildBeans(RelatedDocs.class)) { for (String relDocUuid : docs.getRelatedDocsUuids()) { HippoDocument docByUuid = ObjectUtils.getHippoItemByUuid(connManager, relDocUuid, HippoDocument.class); if (docByUuid != null) { relDocs.add(doc); } } } dumper.dumpRelatedDocs(relDocs, Element.DOCUMENT.getName(), true); dumper.endHippoItem(doc); } private void tags(final HCTConnManager connManager) throws RepositoryException, SAXException { final HippoItemXMLDumper dumper = new HippoItemXMLDumper(this.getSAXConsumer()); dumper.dumpTags(AllTagsProvider.getTags(connManager.getSession(), "tags")); } private <T extends HippoItem> void recursiveTraversal(final HippoItem item, final Class<T> traversalType, final int depth, final HippoItemXMLDumper dumper) throws SAXException, RepositoryException { dumper.startHippoItem(item, item.getPath()); if (depth > 0) { final List<T> children = item.getChildBeans(traversalType); if (!children.isEmpty()) { for (T child : children) { recursiveTraversal(child, traversalType, depth - 1, dumper); } } } dumper.endHippoItem(item); } private void traverse(final HCTConnManager connManager) throws ObjectBeanManagerException, SAXException, RepositoryException { if (hctTraversal == null) { throw new IllegalArgumentException("HCTTraversal is null"); } final HippoItem base = ObjectUtils.getHippoItem(connManager, hctTraversal.getBase()); if (base == null) { throw new IllegalArgumentException("base is null"); } final Class<? extends HippoItem> traversalType = hctTraversal.getBase().startsWith("/content/taxonomies") ? HCTTaxonomyCategoryBean.class : HippoFolder.class; final HippoItemXMLDumper dumper = new HippoItemXMLDumper(this.getSAXConsumer()); recursiveTraversal(base, traversalType, hctTraversal.getDepth(), dumper); } private void query(final HCTConnManager connManager) throws SAXException, RepositoryException, IOException, ObjectBeanManagerException { if (hctQuery == null) { throw new IllegalArgumentException("HCTQuery is null"); } hctQuery.setSession(connManager.getSession()); final HCTQueryResult queryResult = hctQuery.execute(locale, availability); LOG.debug("Query is {}", hctQuery.getSQLQuery()); final HippoItemXMLDumper dumper = new HippoItemXMLDumper(this.getSAXConsumer()); final HippoItem base = ObjectUtils.getHippoItem(connManager, hctQuery.getBase()); dumper.startQueryResult(queryResult, base); if (hctQuery.isIncludeFolders()) { // 1. group matching documents by folder / taxonomy final Map<HippoItem, List<HippoItem>> resultByFolder = new HashMap<HippoItem, List<HippoItem>>(); for (String uuid : queryResult.getUuids()) { final HippoItem item = ObjectUtils.getHippoItemByUuid(connManager, uuid); if (item != null) { if (hctQuery.getType() == HCTQuery.Type.TAXONOMY_DOCS) { final String[] keys = item.getProperty(TaxonomyNodeTypes.HIPPOTAXONOMY_KEYS); for (int i = 0; keys != null && i < keys.length; i++) { if (hctQuery.getTaxonomies().keySet().contains(keys[i])) { final HCTTaxonomyCategoryBean taxonomy = ObjectUtils.getHippoItem( connManager, hctQuery.getTaxonomies().get(keys[i]), HCTTaxonomyCategoryBean.class); if (taxonomy != null) { if (!resultByFolder.containsKey(taxonomy)) { resultByFolder.put(taxonomy, new ArrayList<HippoItem>()); } resultByFolder.get(taxonomy).add(item); } } } } else { final HippoFolder folder = (HippoFolder) item.getParentBean(); if (!resultByFolder.containsKey(folder)) { resultByFolder.put(folder, new ArrayList<HippoItem>()); } resultByFolder.get(folder).add(item); } } } // 2. output results by folder / taxonomy for (Map.Entry<HippoItem, List<HippoItem>> entry : resultByFolder.entrySet()) { if (hctQuery.getType() == HCTQuery.Type.TAXONOMY_DOCS) { dumper.startTaxonomy((HCTTaxonomyCategoryBean) entry.getKey(), locale); for (HippoItem item : entry.getValue()) { dumper.dumpHippoItem(connManager, item, TaxonomyUtils.buildPathInTaxonomy( entry.getKey().getPath(), item.getName()), hctQuery, locale); } dumper.endTaxonomy(); } else { dumper.startHippoItem(entry.getKey(), entry.getKey().getPath()); for (HippoItem item : entry.getValue()) { dumper.dumpHippoItem(connManager, item, item.getPath(), hctQuery, locale); } dumper.endHippoItem(entry.getKey()); } } } else { for (String uuid : queryResult.getUuids()) { final HippoItem item = ObjectUtils.getHippoItemByUuid(connManager, uuid); if (item != null) { switch (hctQuery.getType()) { case TAXONOMY_DOCS: final String[] keys = item.getProperty(TaxonomyNodeTypes.HIPPOTAXONOMY_KEYS); for (int i = 0; keys != null && i < keys.length; i++) { if (hctQuery.getTaxonomies().keySet().contains(keys[i])) { dumper.dumpHippoItem(connManager, item, TaxonomyUtils.buildPathInTaxonomy( hctQuery.getTaxonomies().get(keys[i]), item.getName()), hctQuery, locale); } } break; case FOLDER_DOCS: default: dumper.dumpHippoItem(connManager, item, item.getPath(), hctQuery, locale); } } } } dumper.endQueryResult(); } private String parseDateFormat(final Attributes atts) { String dateFormat = atts.getValue(Attribute.DATE_FORMAT.getName()); if (StringUtils.isBlank(dateFormat)) { dateFormat = Constants.DEFAULT_DATE_FORMAT; } return dateFormat; } private int parseDepth(final Attributes atts) { int depth = 0; if (StringUtils.isNotBlank(atts.getValue(Attribute.DEPTH.getName()))) { try { depth = Integer.parseInt(atts.getValue(Attribute.DEPTH.getName())); } catch (NumberFormatException e) { LOG.error("Invalid depth specified, reverting to default (0)", e); } } return depth; } private long parseSize(final Attributes atts) { long size = 5; if (StringUtils.isNotBlank(atts.getValue(Attribute.SIZE.getName()))) { try { size = Long.parseLong(atts.getValue(Attribute.SIZE.getName())); } catch (NumberFormatException e) { LOG.error("Invalid size specified, reverting to default (5)", e); } } return size; } private long parsePage(final Attributes atts) { long page = 0L; if (StringUtils.isNotBlank(atts.getValue(Attribute.PAGE.getName()))) { try { page = Long.parseLong(atts.getValue(Attribute.PAGE.getName())); } catch (NumberFormatException e) { LOG.error("Invalid page specified, assuming not paginated result", e); } } return page; } @Override public void startElement(final String uri, final String localName, final String name, final Attributes atts) throws SAXException { if (!Constants.NS_HCT.equals(uri)) { super.startElement(uri, localName, name, atts); return; } Element element; try { element = Element.fromName(localName); } catch (IllegalArgumentException e) { throw new SAXException("Invalid element found", e); } if (element == Element.DOCUMENT) { if (state != State.OUTSIDE) { throw new InvalidHCTRequestException(localName, state); } hctDocument = new HCTDocument(); hctDocument.setPath(atts.getValue(Attribute.PATH.getName())); hctDocument.setUuid(atts.getValue(Attribute.UUID.getName())); hctDocument.setDateFormat(parseDateFormat(atts)); } if (element == Element.FOLDERS) { if (state != State.OUTSIDE) { throw new InvalidHCTRequestException(localName, state); } hctTraversal = new HCTTraversal(); hctTraversal.setBase(atts.getValue(Attribute.BASE.getName()) == null ? "/" : atts.getValue(Attribute.BASE.getName())); hctTraversal.setDepth(parseDepth(atts)); hctTraversal.setSize(parseSize(atts)); } if (element == Element.QUERY) { if (state != State.OUTSIDE) { throw new InvalidHCTRequestException(localName, state); } state = State.INSIDE_QUERY; hctQuery = new HCTQuery(); hctQuery.setBase(atts.getValue(Attribute.BASE.getName()) == null ? "/" : atts.getValue(Attribute.BASE.getName())); hctQuery.setReturnType(atts.getValue(Attribute.TYPE.getName()) == null ? "nt:base" : atts.getValue(Attribute.TYPE.getName())); hctQuery.setDateFormat(parseDateFormat(atts)); hctQuery.setDepth(parseDepth(atts)); hctQuery.setSize(parseSize(atts)); hctQuery.setPage(parsePage(atts)); hctQuery.setIncludeFolders("true".equalsIgnoreCase(atts.getValue(Attribute.INCLUDE_FOLDERS.getName()))); } if (element == Element.FILTER) { if (state != State.INSIDE_QUERY) { throw new InvalidHCTRequestException(localName, state); } state = State.INSIDE_FILTER; } if (element == Element.AND || element == Element.OR) { if (state != State.INSIDE_FILTER) { throw new InvalidHCTRequestException(localName, state); } if (element == Element.AND) { state = State.INSIDE_FILTER_AND; } if (element == Element.OR) { state = State.INSIDE_FILTER_OR; } } if (Constants.FILTER_ELEMENTS.contains(element)) { if (state != State.INSIDE_FILTER_AND && state != State.INSIDE_FILTER_OR) { throw new InvalidHCTRequestException(localName, state); } if (!Constants.Element.CONTAINS.getName().equals(localName) && StringUtils.isBlank(atts.getValue(Attribute.FIELD.getName()))) { throw new InvalidHCTRequestException(Attribute.FIELD.getName() + " must be specified for " + localName); } try { hctQuery.getFilter().addCond(state, element, atts); } catch (RepositoryException e) { throw new SAXException(e); } } if (element == Element.ORDERBY) { if (state != State.INSIDE_QUERY) { throw new InvalidHCTRequestException(localName, state); } state = State.INSIDE_ORDERBY; } if (element == Element.DESCENDING || element == Element.ASCENDING) { if (state != State.INSIDE_ORDERBY) { throw new InvalidHCTRequestException(localName, state); } if (StringUtils.isBlank(atts.getValue(Attribute.FIELD.getName()))) { throw new InvalidHCTRequestException(Attribute.FIELD.getName() + " must be specified for " + localName); } if (element == Element.DESCENDING) { hctQuery.addOrderByDescending(atts.getValue(Attribute.FIELD.getName())); } if (element == Element.ASCENDING) { hctQuery.addOrderByAscending(atts.getValue(Attribute.FIELD.getName())); } } if (element == Element.RETURN) { if (state != State.INSIDE_QUERY) { throw new InvalidHCTRequestException(localName, state); } state = State.INSIDE_RETURN; } if (element == Element.FIELD) { if (state != State.INSIDE_RETURN) { throw new InvalidHCTRequestException(localName, state); } if (StringUtils.isBlank(atts.getValue(Attribute.NAME.getName()))) { throw new InvalidHCTRequestException(Attribute.NAME.getName() + " must be specified for " + localName); } hctQuery.addReturnField(atts.getValue(Attribute.NAME.getName())); } if (element == Element.TAGS) { if (state == State.INSIDE_RETURN) { hctQuery.setReturnTags(true); } else if (state == State.OUTSIDE) { LOG.debug("Requiring tags, no processing needed here"); } else { throw new InvalidHCTRequestException(localName, state); } } if (element == Element.TAXONOMIES) { if (state != State.INSIDE_RETURN) { throw new InvalidHCTRequestException(localName, state); } hctQuery.setReturnTaxonomies(true); } if (element == Element.IMAGES) { if (state != State.INSIDE_RETURN) { throw new InvalidHCTRequestException(localName, state); } hctQuery.setReturnImages(true); } if (element == Element.RELATED_DOCS) { if (state != State.INSIDE_RETURN) { throw new InvalidHCTRequestException(localName, state); } hctQuery.setReturnRelatedDocs(true); } } @Override public void endElement(final String uri, final String localName, final String name) throws SAXException { if (!Constants.NS_HCT.equals(uri)) { super.endElement(uri, localName, name); return; } Element element; try { element = Element.fromName(localName); } catch (IllegalArgumentException e) { throw new SAXException("Invalid element found", e); } if (element == Element.RETURN) { if (state != State.INSIDE_RETURN) { throw new InvalidHCTRequestException(localName, state); } state = State.INSIDE_QUERY; LOG.debug("Fields to be returned: {}; images: {}; relatedDocs: {}", new Object[] { hctQuery.getReturnFields(), hctQuery.isReturnImages(), hctQuery.isReturnRelatedDocs() }); } if (element == Element.ORDERBY) { if (state != State.INSIDE_ORDERBY) { throw new InvalidHCTRequestException(localName, state); } state = State.INSIDE_QUERY; } if ((element == Element.AND || element == Element.OR)) { if (state != State.INSIDE_FILTER_AND && state != State.INSIDE_FILTER_OR) { throw new InvalidHCTRequestException(localName, state); } state = State.INSIDE_FILTER; } if (element == Element.FILTER) { if (state != State.INSIDE_FILTER) { throw new InvalidHCTRequestException(localName, state); } state = State.INSIDE_QUERY; } if (element == Element.QUERY) { if (state != State.INSIDE_QUERY) { throw new InvalidHCTRequestException(localName, state); } state = State.OUTSIDE; final HCTConnManager connManager = HCTConnManager.getContentInstance(); try { query(connManager); } catch (Exception e) { throw new ProcessingException("While performing query " + hctQuery.getSQLQuery(), e); } finally { connManager.logout(); } } if (element == Element.FOLDERS) { if (state != State.OUTSIDE) { throw new InvalidHCTRequestException(localName, state); } final HCTConnManager connManager = HCTConnManager.getContentInstance(); try { traverse(connManager); } catch (Exception e) { throw new ProcessingException("While performing traversal " + hctTraversal, e); } finally { connManager.logout(); } } if (element == Element.TAGS) { if (state != State.OUTSIDE) { throw new InvalidHCTRequestException(localName, state); } final HCTConnManager connManager = HCTConnManager.getContentInstance(); try { tags(connManager); } catch (Exception e) { throw new ProcessingException("While fetching tags", e); } finally { connManager.logout(); } } if (element == Element.DOCUMENT) { if (state != State.OUTSIDE) { throw new InvalidHCTRequestException(localName, state); } final HCTConnManager connManager = HCTConnManager.getContentInstance(); try { document(connManager); } catch (Exception e) { throw new ProcessingException("While fetching document", e); } finally { connManager.logout(); } } } }