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