/* Copyright (c) 2008 Google Inc. * * 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 com.google.gdata.model.atompub; import com.google.common.collect.Lists; import com.google.gdata.client.CoreErrorDomain; import com.google.gdata.client.Service; import com.google.gdata.client.Service.Versions; import com.google.gdata.data.Reference; import com.google.gdata.data.introspection.ICollection; import com.google.gdata.model.AttributeKey; import com.google.gdata.model.Element; import com.google.gdata.model.ElementCreator; import com.google.gdata.model.ElementKey; import com.google.gdata.model.ElementMetadata; import com.google.gdata.model.ElementMetadata.Cardinality; import com.google.gdata.model.MetadataRegistry; import com.google.gdata.model.QName; import com.google.gdata.model.ValidationContext; import com.google.gdata.model.atom.Source; import com.google.gdata.model.atom.TextContent; import com.google.gdata.util.Namespaces; import com.google.gdata.util.Version; import java.util.List; /** * The Collection class defines the basic Java object model * representation and XML parsing/generation support for an * APP collection. * * <p>The implementation is versioned to support the AtomPub draft version 9 * introspection format (used for the GData v1 implementation) as well * as the final RFC5023 format (used for all other versions). The key * difference between the two is that draft used an attribute for the * collection title and a comma-delimited list for accepted MIME types, * where the final version uses atom:title and repeating app:accept * elements. * * */ public class Collection extends Element implements Reference, ICollection { private final Version coreVersion = Service.getVersion(); /** * The key for this element. */ public static final ElementKey<Void, Collection> KEY = ElementKey.of( new QName(Namespaces.atomPubStandardNs, "collection"), Collection.class); /** * The href attribute. */ public static final AttributeKey<String> HREF = AttributeKey.of( new QName("href")); /** * Qualified name of title attribute. */ public static final AttributeKey<String> TITLE = AttributeKey.of( new QName("title")); /** * Registers the metadata for this element. */ public static void registerMetadata(MetadataRegistry registry) { if (registry.isRegistered(KEY)) { return; } ElementCreator builder = registry.build(KEY); builder.addAttribute(TITLE).setVisible(false); builder.addAttribute(HREF); builder.addElement(Accept.KEY).setCardinality(Cardinality.MULTIPLE); builder.addElement(Categories.KEY).setCardinality(Cardinality.MULTIPLE); builder.addElement(Source.TITLE).setRequired(true); } /** * Default mutable constructor. */ public Collection() { super(KEY); } /** * Lets subclasses create an instance using a custom key. */ protected Collection(ElementKey<?, ? extends Collection> key) { super(key); } /** * Constructs a new instance by doing a shallow copy of data from an existing * {@link Element} instance. Will use the given {@link ElementMetadata} as the * metadata for the element. * * @param key the element key to use for this element * @param source source element */ protected Collection(ElementKey<?, ? extends Collection> key, Element source) { super(key, source); } /** * Construct a collection with the given href. * * @param href href. */ public Collection(String href) { this(); setHref(href); } /** * Construct a collection with all fields. */ public Collection(String href, TextContent title, String... accepts) { this(); setHref(href); setTitle(title); for (String accept : accepts) { addAccept(accept); } } /** * Returns the accept elements. * * @return accept elements */ public List<Accept> getAccepts() { List<Accept> accepts = super.getElements(Accept.KEY); if (coreVersion.isCompatible(Versions.V1)) { // Check for any accepts with a comma, in which case we split those into // multiple accept elements before returning. List<Accept> result = Lists.newArrayList(); for (Accept accept : accepts) { String acceptValue = accept.getValue(); if (acceptValue != null && acceptValue.indexOf(',') != -1) { String[] split = acceptValue.split(","); for (String part : split) { result.add(new Accept(part)); } } else { result.add(accept); } } accepts = result; } return accepts; } /** * Returns a list of accept strings. */ public List<String> getAcceptList() { List<Accept> accepts = getAccepts(); List<String> result = Lists.newArrayListWithCapacity(accepts.size()); for (Accept accept : accepts) { result.add(accept.getValue()); } return result; } /** * Adds a new accept element. * * @param accept accept element */ public Collection addAccept(Accept accept) { super.addElement(Accept.KEY, accept); return this; } /** * Adds a new accept string. * * @param accept accept string */ public Collection addAccept(String accept) { super.addElement(Accept.KEY, new Accept(accept)); return this; } /** * Removes an accept element. * * @param accept accept element * @return true if the accept was removed */ public boolean removeAccept(Accept accept) { return super.removeElement(Accept.KEY, accept); } /** * Removes an accept string. * * @param acceptStr the string to remove * @return true if the acceptStr was removed. */ public boolean removeAccept(String acceptStr) { boolean modified = false; for (Accept accept : getAccepts()) { if (acceptStr.equals(accept.getValue())) { super.removeElement(Accept.KEY, accept); modified = true; } } return modified; } /** * Returns whether it has the accept elements. * * @return whether it has the accept elements */ public boolean hasAccepts() { return super.hasElement(Accept.KEY); } /** * Returns the app categories documents. * * @return app categories documents */ public List<Categories> getCategorieses() { return super.getElements(Categories.KEY); } /** * Adds a new app categories document. * * @param categories app categories document */ public Collection addCategories(Categories categories) { super.addElement(Categories.KEY, categories); return this; } /** * Returns whether it has the app categories documents. * * @return whether it has the app categories documents */ public boolean hasCategorieses() { return super.hasElement(Categories.KEY); } /** * Returns the href. * * @return href */ public String getHref() { return super.getAttributeValue(HREF); } /** * Sets the href. * * @param href href or <code>null</code> to reset */ public void setHref(String href) { setAttributeValue(HREF, href); } /** * Returns whether it has the href. * * @return whether it has the href */ public boolean hasHref() { return getHref() != null; } /** * Returns the title. * * @return title */ public TextContent getTitle() { return super.getElement(Source.TITLE); } /** * Sets the title. * * @param title title or <code>null</code> to reset */ public Collection setTitle(TextContent title) { setAttributeValue(TITLE, (title == null ? null : title.getPlainText())); super.setElement(Source.TITLE, title); return this; } /** * Returns whether it has the title. * * @return whether it has the title */ public boolean hasTitle() { return super.hasElement(Source.TITLE); } @Override public Element resolve(ElementMetadata<?, ?> metadata, ValidationContext vc) { String titleAttribute = getAttributeValue(TITLE); TextContent title = getElement(Source.TITLE); // Make sure that the title is in both the attribute and element. if (titleAttribute != null) { if (title == null) { title = TextContent.plainText(titleAttribute); addElement(Source.TITLE, title); } else { String titleContent = title.getPlainText(); // Verify that the attribute and element have the same value. if (!titleAttribute.equals(titleContent)) { vc.addError(this, CoreErrorDomain.ERR.duplicateTitle); } } } else if (title != null) { titleAttribute = title.getPlainText(); setAttributeValue(TITLE, titleAttribute); } // HACK(sven): In v1 accept was a comma separated list, in v2 it is // multiple elements. For v1 output we need to merge all elements into one // single element, so do that during resolve. When parsing V1 Collection // objects, we should only have a single accept so it should be fine. if (coreVersion.isCompatible(Service.Versions.V1)) { List<Accept> accepts = getAccepts(); if (accepts.size() > 1) { StringBuilder sb = new StringBuilder(); for (Accept accept : accepts) { if (sb.length() > 0) { sb.append(','); } sb.append(accept.getValue()); } removeElement(Accept.KEY); addAccept(sb.toString()); } } return super.resolve(metadata, vc); } @Override public String toString() { return "{Collection href=" + getAttributeValue(HREF) + "}"; } }