/*
* 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.shindig.gadgets.spec;
import org.apache.shindig.common.uri.Uri;
import org.apache.shindig.common.xml.XmlUtil;
import org.apache.shindig.gadgets.AuthType;
import org.apache.shindig.gadgets.variables.Substitutions;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Maps;
import java.util.Collections;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* Represents a Content section, but normalized into an individual
* view value after views are split on commas.
*/
public class View implements RequestAuthenticationInfo {
private static final Set<String> KNOWN_ATTRIBUTES = ImmutableSet.of(
"type", "view", "href", "preferred_height", "preferred_width", "authz", "quirks",
"sign_owner", "sign_viwer"
);
private final Uri base;
/**
* @param name The name of this view.
* @param elements List of all views, in order, that make up this view.
* An ordered list is required per the spec, since values must
* overwrite one another.
* @param base The base url to resolve href against.
* @throws SpecParserException
*/
public View(String name, List<Element> elements, Uri base) throws SpecParserException {
this.name = name;
this.base = base;
boolean quirks = true;
Uri href = null;
String contentType = null;
ContentType type = null;
int preferredHeight = 0;
int preferredWidth = 0;
String auth = null;
boolean signOwner = true;
boolean signViewer = true;
Map<String, String> attributes = Maps.newHashMap();
StringBuilder content = new StringBuilder();
for (Element element : elements) {
contentType = XmlUtil.getAttribute(element, "type");
if (contentType != null) {
ContentType newType = ContentType.parse(contentType);
if (type != null && newType != type) {
throw new SpecParserException("You may not mix content types in the same view.");
} else {
type = newType;
}
}
href = XmlUtil.getUriAttribute(element, "href", href);
quirks = XmlUtil.getBoolAttribute(element, "quirks", quirks);
preferredHeight = XmlUtil.getIntAttribute(element, "preferred_height");
preferredWidth = XmlUtil.getIntAttribute(element, "preferred_width");
auth = XmlUtil.getAttribute(element, "authz", auth);
signOwner = XmlUtil.getBoolAttribute(element, "sign_owner", signOwner);
signViewer = XmlUtil.getBoolAttribute(element, "sign_viewer", signViewer);
content.append(element.getTextContent());
NamedNodeMap attrs = element.getAttributes();
for (int i = 0; i < attrs.getLength(); ++i) {
Node attr = attrs.item(i);
if (!KNOWN_ATTRIBUTES.contains(attr.getNodeName())) {
attributes.put(attr.getNodeName(), attr.getNodeValue());
}
}
}
this.content = content.toString();
this.needsUserPrefSubstitution = this.content.contains("__UP_");
this.quirks = quirks;
this.href = href;
this.rawType = contentType == null ? "html" : contentType;
this.type = type == null ? ContentType.HTML : type;
this.preferredHeight = preferredHeight;
this.preferredWidth = preferredWidth;
this.attributes = Collections.unmodifiableMap(attributes);
this.authType = AuthType.parse(auth);
this.signOwner = signOwner;
this.signViewer = signViewer;
if (type == ContentType.URL && this.href == null) {
throw new SpecParserException("Content@href must be set when Content@type is \"url\".");
}
}
/**
* Allows the creation of a view from an existing view so that localization
* can be performed.
*/
private View(View view, Substitutions substituter) {
needsUserPrefSubstitution = view.needsUserPrefSubstitution;
name = view.name;
rawType = view.rawType;
type = view.type;
quirks = view.quirks;
preferredHeight = view.preferredHeight;
preferredWidth = view.preferredWidth;
authType = view.authType;
signOwner = view.signOwner;
signViewer = view.signViewer;
content = substituter.substituteString(view.content);
base = view.base;
href = base.resolve(substituter.substituteUri(view.href));
Map<String, String> attributes = Maps.newHashMap();
for (Map.Entry<String, String> entry : view.attributes.entrySet()) {
attributes.put(entry.getKey(), substituter.substituteString(entry.getValue()));
}
this.attributes = Collections.unmodifiableMap(attributes);
}
/**
* Content@view
*/
private final String name;
public String getName() {
return name;
}
/**
* Content@type
*/
private final ContentType type;
public ContentType getType() {
return type;
}
/**
* Content@type - the raw, possibly non-standard string
*/
private final String rawType;
public String getRawType() {
return rawType;
}
/**
* Content@href
*
* All substitutions
*/
private Uri href;
public Uri getHref() {
return href;
}
/**
* Content@quirks
*/
private final boolean quirks;
public boolean getQuirks() {
return quirks;
}
/**
* Content@preferred_height
*/
private final int preferredHeight;
public int getPreferredHeight() {
return preferredHeight;
}
/**
* Content@preferred_width
*/
private final int preferredWidth;
public int getPreferredWidth() {
return preferredWidth;
}
/**
* Content#CDATA
*
* All substitutions
*/
private String content;
public String getContent() {
return content;
}
/**
* Set content for a type=html, href=URL style gadget.
* This is the last bastion of GadgetSpec mutability,
* and should only be used for the described case.
* Call nulls out href in order to indicate content was
* successfully retrieved.
* @param content New gadget content retrieved from href.
*/
public void setHrefContent(String content) {
this.content = content;
this.href = null;
}
/**
* Whether or not the content section has any __UP_ hangman variables.
*/
private final boolean needsUserPrefSubstitution;
public boolean needsUserPrefSubstitution() {
return needsUserPrefSubstitution;
}
/**
* Content/@authz
*/
private final AuthType authType;
public AuthType getAuthType() {
return authType;
}
/**
* Content/@sign_owner
*/
private final boolean signOwner;
public boolean isSignOwner() {
return signOwner;
}
/**
* Content/@sign_viewer
*/
private final boolean signViewer;
public boolean isSignViewer() {
return signViewer;
}
/**
* All attributes.
*/
private final Map<String, String> attributes;
public Map<String, String> getAttributes() {
return attributes;
}
/**
* Creates a new view by performing hangman substitution. See field comments
* for details on what gets substituted.
*
* @param substituter
* @return The substituted view.
*/
public View substitute(Substitutions substituter) {
return new View(this, substituter);
}
@Override
public String toString() {
StringBuilder buf = new StringBuilder();
buf.append("<Content")
.append(" type='").append(rawType).append('\'')
.append(" href='").append(href).append('\'')
.append(" view='").append(name).append('\'')
.append(" quirks='").append(quirks).append('\'')
.append(" preferred_height='").append(preferredHeight).append('\'')
.append(" preferred_width='").append(preferredWidth).append('\'')
.append(" authz=").append(authType.toString().toLowerCase()).append('\'');
for (Map.Entry<String, String> entry : attributes.entrySet()) {
buf.append(entry.getKey()).append("='").append(entry.getValue()).append('\'');
}
buf.append("'>")
.append(content)
.append("</Content>");
return buf.toString();
}
/**
* Possible values for Content/@type
*/
public enum ContentType {
HTML, URL;
/**
* @param value
* @return The parsed value (defaults to html)
*/
public static ContentType parse(String value) {
return "url".equals(value) ? URL : HTML;
}
}
}