/*
* Copyright (C) 2014 Civilian Framework.
*
* Licensed under the Civilian License (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.civilian-framework.org/license.txt
*
* 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.civilian.content;
import org.civilian.util.Check;
/**
* CombinedContentType is a content type which represents
* a merger of an acceptable content type of the client and a producible
* content type of the server. CombinedContentTypes are used by content negotiation
* to find the best suiting server content type to respond to a client request.<br>
* The combined content type consists of:
* <ul>
* <li>the merged content-types (see below)
* <li>the quality of the accepted type
* <li>the quality of the produced type
* <li>the distance of accepted type and produced type (see below).
* </ul>
* Given two content-types <code>main1/sub1</code> and <code>main2/sub2</code> the merged content-type
* is <code>merged(main1,main2)/merged(sub1,sub2)</code>. Certain combinations increase
* the distance of the combined type which is 0,1 or 2 and is a measure how many parts changed
* in the merger.
* <ul>
* <li><code>merged(*,*) = *</code>
* <li><code>merged(*,x) = x</code>, distance++
* <li><code>merged(x,*) = x</code>, distance++
* <li><code>merged(x,x) = x</code>
* <li><code>merged(x,y) = error</code>, combined type does not exist
* </ul>
* The score of a combined content-type c is the array
* { specificity(c), client-quality(c), server-quality(c), 2-distance }.<br>
* For combined types c1 and c2 we define the absolute order
* c1 < c2, if exist i in 0..3 with score(c1)[i] < score(c2)[i] and score(c1)[j] == score(c2)[j] for j in 0..(i-1)
*/
public class CombinedContentType extends ContentType
{
/**
* Returns a CombinedContentType object built from the accepted type (e.g. given via a HTTP Accept-Header)
* and a produced type (e.g. given by the @Produces annotation on a controller action method).
* If the content types are not compatible (i.e. their types or subtypes are not compatible)
* null is returned.
*/
public static CombinedContentType create(ContentType acceptedType, ContentType producedType)
{
return negotiate(acceptedType, producedType, null);
}
/**
* Returns a CombinedContentType object built from the accepted and produced type, but only if it is higher
* ranked than the actual best combined type. If such a type exists the new combined type is returned,
* else null is returned.
*/
public static CombinedContentType negotiate(ContentType acceptedType, ContentType producedType, CombinedContentType actualBest)
{
Combined combined = new Combined(acceptedType, producedType);
if (combined.distance < 0)
return null; // incompatible
boolean isBetter = actualBest == null;
if (!isBetter)
{
int combinedSpecificity = getSpecificity(combined.type, combined.subType);
int actualSpecificity = actualBest.getSpecificity();
isBetter = combinedSpecificity > actualSpecificity;
if (!isBetter && (combinedSpecificity == actualSpecificity))
{
double qdiff = acceptedType.getQuality() - actualBest.getQuality();
isBetter = qdiff > 0;
if (!isBetter && (qdiff == 0))
{
double qsdiff = producedType.getQuality() - actualBest.getServerQuality();
isBetter = qsdiff > 0;
if (!isBetter && (qsdiff == 0))
isBetter = combined.distance < actualBest.getDistance();
}
}
}
return isBetter ?
new CombinedContentType(combined.type, combined.subType, acceptedType.getQuality(), producedType.getQuality(), combined.distance) :
null;
}
/**
* Creates a new CombinedContentType.
* @param type the type
* @param subType the subtype
* @param clientQuality the client quality
* @param serverQuality the server quality
* @param distance the distance
*/
public CombinedContentType(String type, String subType, double clientQuality, double serverQuality, int distance)
{
super(type, subType, clientQuality);
serverQuality_ = checkQuality(serverQuality);
distance_ = Check.between(distance, 0, 2, "distance");
}
/**
* Returns the server quality parameter.
*/
public double getServerQuality()
{
return serverQuality_;
}
/**
* Returns the distance of the combined content type. It is the
* the number of type and subtype wildcards replaced with a concrete
* value (therefore distance is 0, 1 or 2).
*/
public int getDistance()
{
return distance_;
}
static class Combined
{
public Combined(ContentType acceptedType, ContentType producedType)
{
this.type = combinePart(acceptedType.getMainPart(), producedType.getMainPart());
if (distance >= 0)
subType = combinePart(acceptedType.getSubPart(), producedType.getSubPart());
}
private String combinePart(String part1, String part2)
{
if (part1 == null)
{
if (part2 == null)
return null;
else
{
distance++;
return part2;
}
}
// now: part1 != null
if (part2 == null)
{
distance++;
return part1;
}
// now: part1 != null && part2 != null
if (!part1.equals(part2))
{
distance = -1;
return null;
}
return part1;
}
String type;
String subType;
int distance;
}
private double serverQuality_;
private int distance_;
}