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