1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.chemistry.opencmis.client.bindings.spi.browser;
20
21 import static org.apache.chemistry.opencmis.commons.impl.CollectionsHelper.isNotEmpty;
22 import static org.apache.chemistry.opencmis.commons.impl.CollectionsHelper.isNullOrEmpty;
23
24 import java.io.IOException;
25 import java.io.InputStream;
26 import java.io.OutputStream;
27 import java.net.URLEncoder;
28 import java.util.GregorianCalendar;
29 import java.util.LinkedHashMap;
30 import java.util.List;
31 import java.util.Map;
32
33 import org.apache.chemistry.opencmis.commons.data.Ace;
34 import org.apache.chemistry.opencmis.commons.data.Acl;
35 import org.apache.chemistry.opencmis.commons.data.BulkUpdateObjectIdAndChangeToken;
36 import org.apache.chemistry.opencmis.commons.data.ContentStream;
37 import org.apache.chemistry.opencmis.commons.data.Properties;
38 import org.apache.chemistry.opencmis.commons.data.PropertyData;
39 import org.apache.chemistry.opencmis.commons.enums.DateTimeFormat;
40 import org.apache.chemistry.opencmis.commons.impl.Constants;
41 import org.apache.chemistry.opencmis.commons.impl.DateTimeHelper;
42 import org.apache.chemistry.opencmis.commons.impl.IOUtils;
43 import org.apache.chemistry.opencmis.commons.impl.MimeHelper;
44 import org.apache.chemistry.opencmis.commons.impl.UrlBuilder;
45
46 public final class FormDataWriter {
47
48 private static final String CONTENT_TYPE_URLENCODED = "application/x-www-form-urlencoded;charset=utf-8";
49 private static final String CONTENT_TYPE_FORMDATA = "multipart/form-data; boundary=";
50 private static final String CRLF = "\r\n";
51 private static final int BUFFER_SIZE = 64 * 1024;
52
53 private final String boundary;
54 private final Map<String, String> parameters = new LinkedHashMap<String, String>();
55 private ContentStream contentStream;
56
57 public FormDataWriter(String action) {
58 this(action, null);
59 }
60
61 public FormDataWriter(String action, ContentStream contentStream) {
62 addParameter(Constants.CONTROL_CMISACTION, action);
63 this.contentStream = contentStream;
64 boundary = "aPacHeCheMIStryoPEncmiS" + Long.toHexString(action.hashCode()) + action
65 + Long.toHexString(System.currentTimeMillis()) + Long.toHexString(this.hashCode());
66 }
67
68 public void addParameter(String name, Object value) {
69 if (name == null || value == null) {
70 return;
71 }
72
73 parameters.put(name, UrlBuilder.normalizeParameter(value));
74 }
75
76 public void addPropertiesParameters(Properties properties, DateTimeFormat dateTimeFormat) {
77 if (properties == null) {
78 return;
79 }
80
81 int idx = 0;
82 for (PropertyData<?> prop : properties.getPropertyList()) {
83 if (prop == null) {
84 continue;
85 }
86
87 String idxStr = "[" + idx + "]";
88 addParameter(Constants.CONTROL_PROP_ID + idxStr, prop.getId());
89
90 if (isNotEmpty(prop.getValues())) {
91 if (prop.getValues().size() == 1) {
92 addParameter(Constants.CONTROL_PROP_VALUE + idxStr,
93 convertPropertyValue(prop.getFirstValue(), dateTimeFormat));
94 } else {
95 int vidx = 0;
96 for (Object obj : prop.getValues()) {
97 String vidxStr = "[" + vidx + "]";
98 addParameter(Constants.CONTROL_PROP_VALUE + idxStr + vidxStr,
99 convertPropertyValue(obj, dateTimeFormat));
100 vidx++;
101 }
102 }
103 }
104
105 idx++;
106 }
107 }
108
109 public void addSuccinctFlag(boolean succinct) {
110 if (succinct) {
111 addParameter(Constants.CONTROL_SUCCINCT, "true");
112 }
113 }
114
115 public void addPoliciesParameters(List<String> policies) {
116 if (policies == null) {
117 return;
118 }
119
120 int idx = 0;
121 for (String policy : policies) {
122 if (policy != null) {
123 String idxStr = "[" + idx + "]";
124 addParameter(Constants.CONTROL_POLICY + idxStr, policy);
125 idx++;
126 }
127 }
128 }
129
130 public void addPolicyIdParameter(String policyId) {
131 if (policyId == null) {
132 return;
133 }
134
135 addParameter(Constants.CONTROL_POLICY_ID, policyId);
136 }
137
138 public void addAddAcesParameters(Acl acl) {
139 addAcesParameters(acl, Constants.CONTROL_ADD_ACE_PRINCIPAL, Constants.CONTROL_ADD_ACE_PERMISSION);
140 }
141
142 public void addRemoveAcesParameters(Acl acl) {
143 addAcesParameters(acl, Constants.CONTROL_REMOVE_ACE_PRINCIPAL, Constants.CONTROL_REMOVE_ACE_PERMISSION);
144 }
145
146 private void addAcesParameters(Acl acl, String principalControl, String permissionControl) {
147 if (acl == null || acl.getAces() == null) {
148 return;
149 }
150
151 int idx = 0;
152 for (Ace ace : acl.getAces()) {
153 if (ace.getPrincipalId() != null && isNotEmpty(ace.getPermissions())) {
154 String idxStr = "[" + idx + "]";
155 addParameter(principalControl + idxStr, ace.getPrincipalId());
156
157 int permIdx = 0;
158 for (String perm : ace.getPermissions()) {
159 if (perm != null) {
160 String permIdxStr = "[" + permIdx + "]";
161 addParameter(permissionControl + idxStr + permIdxStr, perm);
162 permIdx++;
163 }
164 }
165 idx++;
166 }
167 }
168 }
169
170 public void addObjectIdsAndChangeTokens(List<BulkUpdateObjectIdAndChangeToken> objectIdsAndChangeTokens) {
171 if (isNullOrEmpty(objectIdsAndChangeTokens)) {
172 return;
173 }
174
175 int idx = 0;
176 for (BulkUpdateObjectIdAndChangeToken oc : objectIdsAndChangeTokens) {
177 if (oc == null || oc.getId() == null || oc.getId().length() == 0) {
178 continue;
179 }
180
181 String idxStr = "[" + idx + "]";
182 addParameter(Constants.CONTROL_OBJECT_ID + idxStr, oc.getId());
183 addParameter(Constants.CONTROL_CHANGE_TOKEN + idxStr,
184 (oc.getChangeToken() == null ? "" : oc.getChangeToken()));
185
186 idx++;
187 }
188 }
189
190 public void addSecondaryTypeIds(List<String> secondaryTypeIds) {
191 addSecondaryTypeIdParameters(secondaryTypeIds, Constants.CONTROL_ADD_SECONDARY_TYPE);
192 }
193
194 public void removeSecondaryTypeIds(List<String> secondaryTypeIds) {
195 addSecondaryTypeIdParameters(secondaryTypeIds, Constants.CONTROL_REMOVE_SECONDARY_TYPE);
196 }
197
198 private void addSecondaryTypeIdParameters(List<String> secondaryTypeIds, String secondaryTypeIdControl) {
199 if (isNullOrEmpty(secondaryTypeIds)) {
200 return;
201 }
202
203 int idx = 0;
204 for (String typeId : secondaryTypeIds) {
205 if (typeId == null || typeId.length() == 0) {
206 continue;
207 }
208
209 String idxStr = "[" + idx + "]";
210 addParameter(secondaryTypeIdControl + idxStr, typeId);
211
212 idx++;
213 }
214 }
215
216 private String convertPropertyValue(Object value, DateTimeFormat dateTimeFormat) {
217 if (value == null) {
218 return null;
219 }
220
221 if (value instanceof GregorianCalendar) {
222 if (dateTimeFormat == DateTimeFormat.EXTENDED) {
223 return DateTimeHelper.formatXmlDateTime((GregorianCalendar) value);
224 } else {
225 return String.valueOf(((GregorianCalendar) value).getTimeInMillis());
226 }
227 }
228
229 return value.toString();
230 }
231
232 public String getContentType() {
233 return contentStream == null ? CONTENT_TYPE_URLENCODED : CONTENT_TYPE_FORMDATA + boundary;
234 }
235
236 public void write(OutputStream out) throws IOException {
237 InputStream stream = contentStream == null ? null : contentStream.getStream();
238
239 if (stream == null) {
240 boolean first = true;
241 byte[] amp = IOUtils.toUTF8Bytes("&");
242
243 for (Map.Entry<String, String> param : parameters.entrySet()) {
244 if (first) {
245 first = false;
246 } else {
247 out.write(amp);
248 }
249
250 out.write(IOUtils.toUTF8Bytes(param.getKey() + "=" + URLEncoder.encode(param.getValue(), IOUtils.UTF8)));
251 }
252 } else {
253 writeLine(out);
254
255
256 for (Map.Entry<String, String> param : parameters.entrySet()) {
257 writeLine(out, "--" + boundary);
258 writeLine(out, "Content-Disposition: form-data; name=\"" + param.getKey() + "\"");
259 writeLine(out, "Content-Type: text/plain; charset=utf-8");
260 writeLine(out);
261 writeLine(out, param.getValue());
262 }
263
264
265 String filename = contentStream.getFileName();
266 if (filename == null || filename.length() == 0) {
267 filename = "content";
268 }
269
270 String mediaType = contentStream.getMimeType();
271 if (mediaType == null || mediaType.indexOf('/') < 1 || mediaType.indexOf('\n') > -1
272 || mediaType.indexOf('\r') > -1) {
273 mediaType = Constants.MEDIATYPE_OCTETSTREAM;
274 }
275
276 writeLine(out, "--" + boundary);
277 writeLine(
278 out,
279 "Content-Disposition: "
280 + MimeHelper.encodeContentDisposition(MimeHelper.DISPOSITION_FORM_DATA_CONTENT, filename));
281 writeLine(out, "Content-Type: " + mediaType);
282 writeLine(out, "Content-Transfer-Encoding: binary");
283 writeLine(out);
284
285 IOUtils.copy(stream, out, BUFFER_SIZE);
286
287 writeLine(out);
288 writeLine(out, "--" + boundary + "--");
289 }
290 }
291
292 private void writeLine(OutputStream out) throws IOException {
293 writeLine(out, null);
294 }
295
296 private void writeLine(OutputStream out, String s) throws IOException {
297 out.write(IOUtils.toUTF8Bytes(s == null ? CRLF : s + CRLF));
298 }
299 }