svn commit: r1430330 - in /lenya/branches/BRANCH_2_1_X/src/modules/resource: config/sitemap/ java/src/org/apache/lenya/cms/cocoon/ java/src/org/apache/lenya/cms/cocoon/selection/ java/src/org/apache/lenya/modules/resource/
[email protected] Tue, 08 Jan 2013 15:42:07 -0000
| Newsgroups | gmane.comp.cms.lenya.cvs |
|---|---|
| Message-ID | <[email protected]> |
Author: andreas
Date: Tue Jan 8 15:42:07 2013
New Revision: 1430330
URL: http://svn.apache.org/viewvc?rev=1430330&view=rev
Log:
Provide selector for using Accept header to determine resource representation.
Added:
lenya/branches/BRANCH_2_1_X/src/modules/resource/config/sitemap/
lenya/branches/BRANCH_2_1_X/src/modules/resource/config/sitemap/media-type-selector.xmap
lenya/branches/BRANCH_2_1_X/src/modules/resource/java/src/org/apache/lenya/cms/cocoon/
lenya/branches/BRANCH_2_1_X/src/modules/resource/java/src/org/apache/lenya/cms/cocoon/selection/
lenya/branches/BRANCH_2_1_X/src/modules/resource/java/src/org/apache/lenya/cms/cocoon/selection/MediaTypeSelector.java
lenya/branches/BRANCH_2_1_X/src/modules/resource/java/src/org/apache/lenya/modules/resource/MediaTypeParser.java
Added: lenya/branches/BRANCH_2_1_X/src/modules/resource/config/sitemap/media-type-selector.xmap
URL: http://svn.apache.org/viewvc/lenya/branches/BRANCH_2_1_X/src/modules/resource/config/sitemap/media-type-selector.xmap?rev=1430330&view=auto
==============================================================================
--- lenya/branches/BRANCH_2_1_X/src/modules/resource/config/sitemap/media-type-selector.xmap (added)
+++ lenya/branches/BRANCH_2_1_X/src/modules/resource/config/sitemap/media-type-selector.xmap Tue Jan 8 15:42:07 2013
@@ -0,0 +1,24 @@
+<?xml version="1.0"?>
+<!--
+ 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.
+-->
+
+<xmap xpath="/sitemap/components/selectors"
+ unless="/sitemap/components/selectors/selector[@name = 'media-type']"
+ xmlns:map="http://apache.org/cocoon/sitemap/1.0">
+ <map:selector name="media-type" logger="lenya.sitemap.selector.mediatype"
+ src="org.apache.lenya.cms.cocoon.selection.MediaTypeSelector"/>
+</xmap>
Added: lenya/branches/BRANCH_2_1_X/src/modules/resource/java/src/org/apache/lenya/cms/cocoon/selection/MediaTypeSelector.java
URL: http://svn.apache.org/viewvc/lenya/branches/BRANCH_2_1_X/src/modules/resource/java/src/org/apache/lenya/cms/cocoon/selection/MediaTypeSelector.java?rev=1430330&view=auto
==============================================================================
--- lenya/branches/BRANCH_2_1_X/src/modules/resource/java/src/org/apache/lenya/cms/cocoon/selection/MediaTypeSelector.java (added)
+++ lenya/branches/BRANCH_2_1_X/src/modules/resource/java/src/org/apache/lenya/cms/cocoon/selection/MediaTypeSelector.java Tue Jan 8 15:42:07 2013
@@ -0,0 +1,58 @@
+/*
+ * 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.lenya.cms.cocoon.selection;
+
+import java.util.Arrays;
+import java.util.Map;
+
+import org.apache.avalon.framework.logger.AbstractLogEnabled;
+import org.apache.avalon.framework.parameters.Parameters;
+import org.apache.cocoon.environment.ObjectModelHelper;
+import org.apache.cocoon.environment.Request;
+import org.apache.cocoon.selection.Selector;
+import org.apache.lenya.modules.resource.MediaTypeParser;
+
+import com.google.common.base.Optional;
+
+/**
+ * Compares the media type passed as expression to the Accept header of the HTTP request.
+ * Returns true if the media type matches. Matches with the double-wildcard (universal)
+ * media type are ignored.
+ */
+public class MediaTypeSelector extends AbstractLogEnabled implements Selector {
+
+ @Override
+ public boolean select(
+ final String expression,
+ @SuppressWarnings("rawtypes") final Map objectModel,
+ final Parameters parameters) {
+
+ final Request request = ObjectModelHelper.getRequest(objectModel);
+ final Optional<String> type = MediaTypeParser.bestMatch(
+ Arrays.asList(expression.split("\\s,\\s")),
+ request.getHeader("Accept"),
+ true);
+ getLogger().info(
+ "Accepted: " + expression
+ + "\nHeader: " + request.getHeader("Accept")
+ + "\nDetected: " + type.or("-"));
+ return type.isPresent() && !type.equals("*/*");
+ }
+
+}
Added: lenya/branches/BRANCH_2_1_X/src/modules/resource/java/src/org/apache/lenya/modules/resource/MediaTypeParser.java
URL: http://svn.apache.org/viewvc/lenya/branches/BRANCH_2_1_X/src/modules/resource/java/src/org/apache/lenya/modules/resource/MediaTypeParser.java?rev=1430330&view=auto
==============================================================================
--- lenya/branches/BRANCH_2_1_X/src/modules/resource/java/src/org/apache/lenya/modules/resource/MediaTypeParser.java (added)
+++ lenya/branches/BRANCH_2_1_X/src/modules/resource/java/src/org/apache/lenya/modules/resource/MediaTypeParser.java Tue Jan 8 15:42:07 2013
@@ -0,0 +1,250 @@
+/*
+ * 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.lenya.modules.resource;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.commons.lang.math.NumberUtils;
+
+import com.google.common.base.Optional;
+
+/**
+ * MIME-Type Parser
+ *
+ * This class provides basic functions for handling mime-types. It can handle matching mime-types
+ * against a list of media-ranges. See section 14.1 of the HTTP specification [RFC 2616] for a
+ * complete explanation.
+ *
+ * http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.1
+ *
+ * A port to Java of Joe Gregorio's MIME-Type Parser:
+ *
+ * http://code.google.com/p/mimeparse/
+ *
+ * Ported by Tom Zellman <[email protected]>.
+ */
+public final class MediaTypeParser {
+
+ /**
+ * Parse results container
+ */
+ protected static class ParseResults {
+ String type;
+
+ String subType;
+
+ // !a dictionary of all the parameters for the media range
+ Map<String, String> params;
+
+ @Override
+ public String toString() {
+ StringBuffer s = new StringBuffer("('" + type + "', '" + subType + "', {");
+ for (String k : params.keySet())
+ s.append("'" + k + "':'" + params.get(k) + "',");
+ return s.append("})").toString();
+ }
+ }
+
+ /**
+ * Carves up a mime-type and returns a ParseResults object
+ *
+ * For example, the media range 'application/xhtml;q=0.5' would get parsed into:
+ *
+ * ('application', 'xhtml', {'q', '0.5'})
+ */
+ protected static ParseResults parseMimeType(String mimeType) {
+ String[] parts = StringUtils.split(mimeType, ";");
+ ParseResults results = new ParseResults();
+ results.params = new HashMap<String, String>();
+
+ for (int i = 1; i < parts.length; ++i) {
+ String p = parts[i];
+ String[] subParts = StringUtils.split(p, '=');
+ if (subParts.length == 2)
+ results.params.put(subParts[0].trim(), subParts[1].trim());
+ }
+ String fullType = parts[0].trim();
+
+ // Java URLConnection class sends an Accept header that includes a
+ // single "*" - Turn it into a legal wildcard.
+ if (fullType.equals("*"))
+ fullType = "*/*";
+ String[] types = StringUtils.split(fullType, "/");
+ results.type = types[0].trim();
+ results.subType = types[1].trim();
+ return results;
+ }
+
+ /**
+ * Carves up a media range and returns a ParseResults.
+ *
+ * For example, the media range 'application/*;q=0.5' would get parsed into:
+ *
+ * ('application', '*', {'q', '0.5'})
+ *
+ * In addition this function also guarantees that there is a value for 'q' in the params
+ * dictionary, filling it in with a proper default if necessary.
+ *
+ * @param range
+ */
+ protected static ParseResults parseMediaRange(String range) {
+ ParseResults results = parseMimeType(range);
+ String q = results.params.get("q");
+ float f = NumberUtils.toFloat(q, 1);
+ if (StringUtils.isBlank(q) || f < 0 || f > 1)
+ results.params.put("q", "1");
+ return results;
+ }
+
+ /**
+ * Structure for holding a fitness/quality combo
+ */
+ protected static class FitnessAndQuality implements Comparable<FitnessAndQuality> {
+ int fitness;
+
+ float quality;
+
+ String mimeType; // optionally used
+
+ public FitnessAndQuality(int fitness, float quality) {
+ this.fitness = fitness;
+ this.quality = quality;
+ }
+
+ public int compareTo(FitnessAndQuality o) {
+ if (fitness == o.fitness) {
+ if (quality == o.quality)
+ return 0;
+ else
+ return quality < o.quality ? -1 : 1;
+ } else
+ return fitness < o.fitness ? -1 : 1;
+ }
+ }
+
+ /**
+ * Find the best match for a given mimeType against a list of media_ranges that have already
+ * been parsed by MimeParse.parseMediaRange(). Returns a tuple of the fitness value and the
+ * value of the 'q' quality parameter of the best match, or (-1, 0) if no match was found. Just
+ * as for quality_parsed(), 'parsed_ranges' must be a list of parsed media ranges.
+ *
+ * @param mimeType
+ * @param parsedRanges
+ */
+ protected static FitnessAndQuality fitnessAndQualityParsed(String mimeType,
+ Collection<ParseResults> parsedRanges) {
+ int bestFitness = -1;
+ float bestFitQ = 0;
+ ParseResults target = parseMediaRange(mimeType);
+
+ for (ParseResults range : parsedRanges) {
+ if ((target.type.equals(range.type) || range.type.equals("*") || target.type
+ .equals("*"))
+ && (target.subType.equals(range.subType) || range.subType.equals("*") || target.subType
+ .equals("*"))) {
+ for (String k : target.params.keySet()) {
+ int paramMatches = 0;
+ if (!k.equals("q") && range.params.containsKey(k)
+ && target.params.get(k).equals(range.params.get(k))) {
+ paramMatches++;
+ }
+ int fitness = (range.type.equals(target.type)) ? 100 : 0;
+ fitness += (range.subType.equals(target.subType)) ? 10 : 0;
+ fitness += paramMatches;
+ if (fitness > bestFitness) {
+ bestFitness = fitness;
+ bestFitQ = NumberUtils.toFloat(range.params.get("q"), 0);
+ }
+ }
+ }
+ }
+ return new FitnessAndQuality(bestFitness, bestFitQ);
+ }
+
+ /**
+ * Find the best match for a given mime-type against a list of ranges that have already been
+ * parsed by parseMediaRange(). Returns the 'q' quality parameter of the best match, 0 if no
+ * match was found. This function bahaves the same as quality() except that 'parsed_ranges' must
+ * be a list of parsed media ranges.
+ *
+ * @param mimeType
+ * @param parsedRanges
+ * @return
+ */
+ protected static float qualityParsed(String mimeType, Collection<ParseResults> parsedRanges) {
+ return fitnessAndQualityParsed(mimeType, parsedRanges).quality;
+ }
+
+ /**
+ * Returns the quality 'q' of a mime-type when compared against the mediaRanges in ranges. For
+ * example:
+ *
+ * @param mimeType
+ * @param parsedRanges
+ */
+ public static float quality(String mimeType, String ranges) {
+ List<ParseResults> results = new LinkedList<ParseResults>();
+ for (String r : StringUtils.split(ranges, ','))
+ results.add(parseMediaRange(r));
+ return qualityParsed(mimeType, results);
+ }
+
+ /**
+ * Takes a list of supported mime-types and finds the best match for all the media-ranges listed
+ * in header. The value of header must be a string that conforms to the format of the HTTP
+ * Accept: header. The value of 'supported' is a list of mime-types.
+ *
+ * MimeParse.bestMatch(Arrays.asList(new String[]{"application/xbel+xml", "text/xml"}),
+ * "text/*;q=0.5,*; q=0.1") 'text/xml'
+ *
+ * @param supported
+ * @param header
+ * @return
+ */
+ public static Optional<String> bestMatch(Collection<String> supported, String header, boolean excludeAllType) {
+ List<ParseResults> parseResults = new LinkedList<ParseResults>();
+ List<FitnessAndQuality> weightedMatches = new LinkedList<FitnessAndQuality>();
+ for (String r : StringUtils.split(header, ',')) {
+ ParseResults result = parseMediaRange(r);
+ if (!(excludeAllType && result.type.equals("*") && result.subType.equals("*"))) {
+ parseResults.add(result);
+ }
+ }
+
+ for (String s : supported) {
+ FitnessAndQuality fitnessAndQuality = fitnessAndQualityParsed(s, parseResults);
+ fitnessAndQuality.mimeType = s;
+ weightedMatches.add(fitnessAndQuality);
+ }
+ Collections.sort(weightedMatches);
+
+ FitnessAndQuality lastOne = weightedMatches.get(weightedMatches.size() - 1);
+ return Optional.fromNullable(NumberUtils.compare(lastOne.quality, 0) != 0 ? lastOne.mimeType : null);
+ }
+
+ // hidden
+ private MediaTypeParser() {
+ }
+}