001/* 002 * The contents of this file are subject to the license and copyright 003 * detailed in the LICENSE and NOTICE files at the root of the source 004 * tree. 005 */ 006package org.fcrepo.http.commons.domain; 007 008import static java.util.Arrays.asList; 009import static java.util.Optional.ofNullable; 010import static org.fcrepo.kernel.api.RdfLexicon.PREFER_CONTAINMENT; 011import static org.fcrepo.kernel.api.RdfLexicon.PREFER_MEMBERSHIP; 012import static org.fcrepo.kernel.api.RdfLexicon.PREFER_MINIMAL_CONTAINER; 013import static org.fcrepo.kernel.api.RdfLexicon.PREFER_SERVER_MANAGED; 014import static org.fcrepo.kernel.api.RdfLexicon.EMBED_CONTAINED; 015import static org.fcrepo.kernel.api.RdfLexicon.INBOUND_REFERENCES; 016 017import org.glassfish.jersey.message.internal.HttpHeaderReader; 018 019import javax.servlet.http.HttpServletResponse; 020 021import java.text.ParseException; 022import java.util.HashMap; 023import java.util.List; 024import java.util.Map; 025import java.util.Optional; 026 027/** 028 * Parse a single prefer tag, value and any optional parameters 029 * 030 * @author cabeer 031 */ 032public class PreferTag implements Comparable<PreferTag> { 033 private final String tag; 034 private String value = ""; 035 private Map<String, String> params = new HashMap<>(); 036 037 /** 038 * Create an empty PreferTag 039 * @return the empty PreferTag 040 */ 041 public static PreferTag emptyTag() { 042 return new PreferTag((String)null); 043 } 044 045 /** 046 * Create a new PreferTag from an existing tag 047 * @param preferTag the preferTag 048 */ 049 protected PreferTag(final PreferTag preferTag) { 050 tag = preferTag.getTag(); 051 value = preferTag.getValue(); 052 params = preferTag.getParams(); 053 } 054 055 /** 056 * Parse the prefer tag and parameters out of the header 057 * @param reader the reader 058 */ 059 private PreferTag(final HttpHeaderReader reader) { 060 061 // Skip any white space 062 reader.hasNext(); 063 064 if (reader.hasNext()) { 065 try { 066 tag = Optional.ofNullable(reader.nextToken()) 067 .map(CharSequence::toString).orElse(null); 068 069 if (reader.hasNextSeparator('=', true)) { 070 reader.next(); 071 072 value = Optional.ofNullable(reader.nextTokenOrQuotedString()) 073 . map(CharSequence::toString) 074 .orElse(null); 075 } 076 077 if (reader.hasNext()) { 078 params = HttpHeaderReader.readParameters(reader); 079 if ( params == null ) { 080 params = new HashMap<>(); 081 } 082 } 083 } catch (final ParseException e) { 084 throw new IllegalArgumentException("Could not parse 'Prefer' header", e); 085 } 086 } else { 087 tag = ""; 088 } 089 } 090 091 /** 092 * Create a blank prefer tag 093 * @param inputTag the input tag 094 */ 095 public PreferTag(final String inputTag) { 096 this(HttpHeaderReader.newInstance(inputTag)); 097 } 098 099 /** 100 * Get the tag name 101 * @return tag name 102 */ 103 public String getTag() { 104 return tag; 105 } 106 107 /** 108 * Get the default value for the tag 109 * @return default value for the tag 110 */ 111 public String getValue() { 112 return value; 113 } 114 115 /** 116 * Get any additional parameters for the prefer tag 117 * @return additional parameters for the prefer tag 118 */ 119 public Map<String,String> getParams() { 120 return params; 121 } 122 123 /** 124 * Add appropriate response headers to indicate that the incoming preferences were acknowledged 125 * @param servletResponse the servlet response 126 */ 127 public void addResponseHeaders(final HttpServletResponse servletResponse) { 128 129 final String receivedParam = ofNullable(params.get("received")).orElse(""); 130 final List<String> includes = asList(ofNullable(params.get("include")).orElse(" ").split(" ")); 131 final List<String> omits = asList(ofNullable(params.get("omit")).orElse(" ").split(" ")); 132 133 final StringBuilder includeBuilder = new StringBuilder(); 134 final StringBuilder omitBuilder = new StringBuilder(); 135 136 if (!(value.equals("minimal") || receivedParam.equals("minimal"))) { 137 final List<String> appliedPrefs = asList(PREFER_SERVER_MANAGED.toString(), 138 PREFER_MINIMAL_CONTAINER.toString(), 139 PREFER_MEMBERSHIP.toString(), 140 PREFER_CONTAINMENT.toString()); 141 final List<String> includePrefs = asList(EMBED_CONTAINED.toString(), 142 INBOUND_REFERENCES.toString()); 143 includes.forEach(param -> includeBuilder.append( 144 (appliedPrefs.contains(param) || includePrefs.contains(param)) ? param + " " : "")); 145 146 // Note: include params prioritized over omits during implementation 147 omits.forEach(param -> omitBuilder.append( 148 (appliedPrefs.contains(param) && !includes.contains(param)) ? param + " " : "")); 149 } 150 151 // build the header for Preference Applied 152 final String appliedReturn = value.equals("minimal") ? "return=minimal" : "return=representation"; 153 final String appliedReceived = receivedParam.equals("minimal") ? "received=minimal" : ""; 154 155 final StringBuilder preferenceAppliedBuilder = new StringBuilder(appliedReturn); 156 preferenceAppliedBuilder.append(appliedReceived.length() > 0 ? "; " + appliedReceived : ""); 157 appendHeaderParam(preferenceAppliedBuilder, "include", includeBuilder.toString().trim()); 158 appendHeaderParam(preferenceAppliedBuilder, "omit", omitBuilder.toString().trim()); 159 160 servletResponse.addHeader("Preference-Applied", preferenceAppliedBuilder.toString().trim()); 161 162 servletResponse.addHeader("Vary", "Prefer"); 163 } 164 165 private void appendHeaderParam(final StringBuilder builder, final String paramName, final String paramValue) { 166 if (paramValue.length() > 0) { 167 builder.append("; " + paramName + "=\"" + paramValue.trim() + "\""); 168 } 169 } 170 171 /** 172 * We consider tags with the same name to be equal, because <a 173 * href="http://tools.ietf.org/html/rfc7240#page-4">the definition of Prefer headers</a> does not permit that tags 174 * with the same name be consumed except by selecting for the first appearing tag. 175 * 176 * @see java.lang.Comparable#compareTo(java.lang.Object) 177 */ 178 @Override 179 public int compareTo(final PreferTag otherTag) { 180 return getTag().compareTo(otherTag.getTag()); 181 } 182 183 @Override 184 public boolean equals(final Object obj) { 185 if ((obj instanceof PreferTag)) { 186 return getTag().equals(((PreferTag) obj).getTag()); 187 } 188 return false; 189 } 190 191 @Override 192 public int hashCode() { 193 if (getTag() == null) { 194 return 0; 195 } 196 return getTag().hashCode(); 197 } 198}