blob: 3e99a779ffbd463063a30363a0bdd31587af3df8 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
*
* 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.apache.chemistry.opencmis.server.impl.browser;
import static org.apache.chemistry.opencmis.commons.impl.CollectionsHelper.*;
import java.io.IOException;
import java.io.PrintWriter;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Collections;
import java.util.GregorianCalendar;
import java.util.List;
import java.util.Map;
import java.util.TimeZone;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.chemistry.opencmis.commons.PropertyIds;
import org.apache.chemistry.opencmis.commons.data.Ace;
import org.apache.chemistry.opencmis.commons.data.Acl;
import org.apache.chemistry.opencmis.commons.data.ContentStream;
import org.apache.chemistry.opencmis.commons.data.ObjectData;
import org.apache.chemistry.opencmis.commons.data.Properties;
import org.apache.chemistry.opencmis.commons.data.PropertyData;
import org.apache.chemistry.opencmis.commons.definitions.PropertyDefinition;
import org.apache.chemistry.opencmis.commons.definitions.TypeDefinition;
import org.apache.chemistry.opencmis.commons.enums.IncludeRelationships;
import org.apache.chemistry.opencmis.commons.exceptions.CmisInvalidArgumentException;
import org.apache.chemistry.opencmis.commons.impl.Base64;
import org.apache.chemistry.opencmis.commons.impl.Constants;
import org.apache.chemistry.opencmis.commons.impl.DateTimeHelper;
import org.apache.chemistry.opencmis.commons.impl.IOUtils;
import org.apache.chemistry.opencmis.commons.impl.TypeCache;
import org.apache.chemistry.opencmis.commons.impl.UrlBuilder;
import org.apache.chemistry.opencmis.commons.impl.dataobjects.AccessControlEntryImpl;
import org.apache.chemistry.opencmis.commons.impl.dataobjects.AccessControlListImpl;
import org.apache.chemistry.opencmis.commons.impl.dataobjects.AccessControlPrincipalDataImpl;
import org.apache.chemistry.opencmis.commons.impl.dataobjects.ContentStreamImpl;
import org.apache.chemistry.opencmis.commons.impl.dataobjects.PropertiesImpl;
import org.apache.chemistry.opencmis.commons.impl.dataobjects.PropertyBooleanImpl;
import org.apache.chemistry.opencmis.commons.impl.dataobjects.PropertyDateTimeImpl;
import org.apache.chemistry.opencmis.commons.impl.dataobjects.PropertyDecimalImpl;
import org.apache.chemistry.opencmis.commons.impl.dataobjects.PropertyHtmlImpl;
import org.apache.chemistry.opencmis.commons.impl.dataobjects.PropertyIdImpl;
import org.apache.chemistry.opencmis.commons.impl.dataobjects.PropertyIntegerImpl;
import org.apache.chemistry.opencmis.commons.impl.dataobjects.PropertyStringImpl;
import org.apache.chemistry.opencmis.commons.impl.dataobjects.PropertyUriImpl;
import org.apache.chemistry.opencmis.commons.impl.json.JSONObject;
import org.apache.chemistry.opencmis.commons.impl.json.JSONStreamAware;
import org.apache.chemistry.opencmis.commons.server.CmisService;
import org.apache.chemistry.opencmis.server.shared.AbstractServiceCall;
import org.apache.chemistry.opencmis.server.shared.Dispatcher;
public abstract class AbstractBrowserServiceCall extends AbstractServiceCall {
public static final String JSON_MIME_TYPE = "application/json";
public static final String HTML_MIME_TYPE = "text/html";
public static final String ROOT_PATH_FRAGMENT = "root";
public static final String REPOSITORY_PLACEHOLDER = "{repositoryId}";
/**
* Compiles the base URL for links, collections and templates.
*/
public UrlBuilder compileBaseUrl(HttpServletRequest request, String repositoryId) {
String baseUrl = (String) request.getAttribute(Dispatcher.BASE_URL_ATTRIBUTE);
if (baseUrl != null) {
int repIdPos = baseUrl.indexOf(REPOSITORY_PLACEHOLDER);
if (repIdPos < 0) {
return new UrlBuilder(baseUrl);
} else {
return new UrlBuilder(baseUrl.substring(0, repIdPos) + repositoryId
+ baseUrl.substring(repIdPos + REPOSITORY_PLACEHOLDER.length()));
}
}
UrlBuilder url = new UrlBuilder(request.getScheme(), request.getServerName(), request.getServerPort(), null);
url.addPath(request.getContextPath());
url.addPath(request.getServletPath());
url.addPathSegment(repositoryId);
return url;
}
public UrlBuilder compileRepositoryUrl(HttpServletRequest request, String repositoryId) {
return compileBaseUrl(request, repositoryId);
}
public UrlBuilder compileRootUrl(HttpServletRequest request, String repositoryId) {
return compileRepositoryUrl(request, repositoryId).addPathSegment(ROOT_PATH_FRAGMENT);
}
public String compileObjectLocationUrl(HttpServletRequest request, String repositoryId, String objectId) {
return compileRootUrl(request, repositoryId).addParameter(Constants.PARAM_OBJECT_ID, objectId).toString();
}
public String compileTypeLocationUrl(HttpServletRequest request, String repositoryId, String typeId) {
return compileRepositoryUrl(request, repositoryId).addParameter(Constants.PARAM_TYPE_ID, typeId).toString();
}
/**
* Writes JSON to the servlet response and adds a callback wrapper if
* requested.
*/
public void writeJSON(JSONStreamAware json, HttpServletRequest request, HttpServletResponse response)
throws IOException {
String token = getStringParameter(request, Constants.PARAM_TOKEN);
if (token != null && "POST".equals(request.getMethod())) {
response.setContentType(HTML_MIME_TYPE);
response.setContentLength(0);
} else {
response.setContentType(JSON_MIME_TYPE);
response.setCharacterEncoding(IOUtils.UTF8);
PrintWriter pw = response.getWriter();
String callback = getStringParameter(request, Constants.PARAM_CALLBACK);
if (callback != null) {
if (!callback.matches("[A-Za-z0-9._\\[\\]]*")) {
throw new CmisInvalidArgumentException("Invalid callback name!");
}
pw.print(callback + "(");
}
json.writeJSONString(pw);
if (callback != null) {
pw.print(");");
}
pw.flush();
}
}
public void writeEmpty(HttpServletRequest request, HttpServletResponse response) throws IOException {
response.setContentLength(0);
response.setContentType(HTML_MIME_TYPE);
response.getWriter().flush();
}
public ObjectData getSimpleObject(CmisService service, String repositoryId, String objectId) {
return service.getObject(repositoryId, objectId, null, false, IncludeRelationships.NONE, "cmis:none", false,
false, null);
}
/**
* Sets the given HTTP status code if the surpessResponseCodes parameter is
* not set to true; otherwise sets HTTP status code 200 (OK).
*/
public void setStatus(HttpServletRequest request, HttpServletResponse response, int statusCode) {
if (getBooleanParameter(request, Constants.PARAM_SUPPRESS_RESPONSE_CODES, false)) {
statusCode = HttpServletResponse.SC_OK;
}
response.setStatus(statusCode);
}
/**
* Transforms the transaction into a cookie name.
*/
public String getCookieName(String token) {
if (token == null || token.length() == 0) {
return "cmis%";
}
return "cmis_" + Base64.encodeBytes(IOUtils.toUTF8Bytes(token)).replace('=', '%');
}
/**
* Sets a transaction cookie.
*/
public void setCookie(HttpServletRequest request, HttpServletResponse response, String repositoryId, String token,
String value) {
setCookie(request, response, repositoryId, token, value, 3600);
}
/**
* Deletes a transaction cookie.
*/
public void deleteCookie(HttpServletRequest request, HttpServletResponse response, String repositoryId, String token) {
setCookie(request, response, repositoryId, token, "", 0);
}
/**
* Sets a transaction cookie.
*/
public void setCookie(HttpServletRequest request, HttpServletResponse response, String repositoryId, String token,
String value, int expiry) {
if (token != null && token.length() > 0) {
String cookieValue = IOUtils.encodeURL(value);
Cookie transactionCookie = new Cookie(getCookieName(token), cookieValue);
transactionCookie.setMaxAge(expiry);
transactionCookie.setPath(request.getContextPath() + request.getServletPath() + "/" + repositoryId);
transactionCookie.setSecure(request.isSecure());
response.addCookie(transactionCookie);
}
}
public String createCookieValue(int code, String objectId, String ex, String message) {
JSONObject result = new JSONObject();
result.put("code", code);
result.put("objectId", objectId == null ? "" : objectId);
result.put("exception", ex == null ? "" : ex);
result.put("message", message == null ? "" : message);
return result.toJSONString();
}
public Properties createNewProperties(ControlParser controlParser, TypeCache typeCache) {
Map<String, List<String>> properties = controlParser.getProperties();
if (properties == null) {
return null;
}
// load primary type
List<String> objectTypeIdsValues = properties.get(PropertyIds.OBJECT_TYPE_ID);
if (isNotEmpty(objectTypeIdsValues)) {
TypeDefinition typeDef = typeCache.getTypeDefinition(objectTypeIdsValues.get(0));
if (typeDef == null) {
throw new CmisInvalidArgumentException("Invalid type: " + objectTypeIdsValues.get(0));
}
}
// load secondary types
List<String> secondaryObjectTypeIdsValues = properties.get(PropertyIds.SECONDARY_OBJECT_TYPE_IDS);
if (isNotEmpty(secondaryObjectTypeIdsValues)) {
for (String secTypeId : secondaryObjectTypeIdsValues) {
TypeDefinition typeDef = typeCache.getTypeDefinition(secTypeId);
if (typeDef == null) {
throw new CmisInvalidArgumentException("Invalid type: " + secTypeId);
}
}
}
// create properties
PropertiesImpl result = new PropertiesImpl();
for (Map.Entry<String, List<String>> property : properties.entrySet()) {
PropertyDefinition<?> propDef = typeCache.getPropertyDefinition(property.getKey());
if (propDef == null) {
throw new CmisInvalidArgumentException(property.getKey() + " is unknown!");
}
result.addProperty(createPropertyData(propDef, property.getValue()));
}
return result;
}
public Properties createUpdateProperties(ControlParser controlParser, String typeId, List<String> secondaryTypeIds,
List<String> objectIds, TypeCache typeCache) {
Map<String, List<String>> properties = controlParser.getProperties();
if (properties == null) {
return null;
}
// load primary type
if (typeId != null) {
TypeDefinition typeDef = typeCache.getTypeDefinition(typeId);
if (typeDef == null) {
throw new CmisInvalidArgumentException("Invalid type: " + typeId);
}
}
// load secondary types
List<String> secondaryObjectTypeIdsValues = properties.get(PropertyIds.SECONDARY_OBJECT_TYPE_IDS);
if (isNotEmpty(secondaryObjectTypeIdsValues)) {
for (String secTypeId : secondaryObjectTypeIdsValues) {
TypeDefinition typeDef = typeCache.getTypeDefinition(secTypeId);
if (typeDef == null) {
throw new CmisInvalidArgumentException("Invalid type: " + secTypeId);
}
}
}
if (secondaryTypeIds != null) {
for (String secTypeId : secondaryTypeIds) {
TypeDefinition typeDef = typeCache.getTypeDefinition(secTypeId);
if (typeDef == null) {
throw new CmisInvalidArgumentException("Invalid secondary type: " + secTypeId);
}
}
}
// create properties
PropertiesImpl result = new PropertiesImpl();
for (Map.Entry<String, List<String>> property : properties.entrySet()) {
PropertyDefinition<?> propDef = typeCache.getPropertyDefinition(property.getKey());
if (propDef == null && objectIds != null) {
for (String objectId : objectIds) {
typeCache.getTypeDefinitionForObject(objectId);
propDef = typeCache.getPropertyDefinition(property.getKey());
if (propDef != null) {
break;
}
}
}
if (propDef == null) {
throw new CmisInvalidArgumentException(property.getKey() + " is unknown!");
}
result.addProperty(createPropertyData(propDef, property.getValue()));
}
return result;
}
@SuppressWarnings("unchecked")
private PropertyData<?> createPropertyData(PropertyDefinition<?> propDef, Object value) {
List<String> strValues;
if (value == null) {
strValues = Collections.emptyList();
} else if (value instanceof String) {
strValues = new ArrayList<String>();
strValues.add((String) value);
} else {
strValues = (List<String>) value;
}
PropertyData<?> propertyData = null;
switch (propDef.getPropertyType()) {
case STRING:
propertyData = new PropertyStringImpl(propDef.getId(), strValues);
break;
case ID:
propertyData = new PropertyIdImpl(propDef.getId(), strValues);
break;
case BOOLEAN:
List<Boolean> boolValues = new ArrayList<Boolean>(strValues.size());
for (String s : strValues) {
boolValues.add(Boolean.valueOf(s));
}
propertyData = new PropertyBooleanImpl(propDef.getId(), boolValues);
break;
case INTEGER:
List<BigInteger> intValues = new ArrayList<BigInteger>(strValues.size());
try {
for (String s : strValues) {
intValues.add(new BigInteger(s));
}
} catch (NumberFormatException e) {
throw new CmisInvalidArgumentException(propDef.getId() + " value is not an integer value!", e);
}
propertyData = new PropertyIntegerImpl(propDef.getId(), intValues);
break;
case DECIMAL:
List<BigDecimal> decValues = new ArrayList<BigDecimal>(strValues.size());
try {
for (String s : strValues) {
decValues.add(new BigDecimal(s));
}
} catch (NumberFormatException e) {
throw new CmisInvalidArgumentException(propDef.getId() + " value is not an integer value!", e);
}
propertyData = new PropertyDecimalImpl(propDef.getId(), decValues);
break;
case DATETIME:
List<GregorianCalendar> calValues = new ArrayList<GregorianCalendar>(strValues.size());
for (String s : strValues) {
GregorianCalendar cal;
try {
long timestamp = Long.parseLong(s);
cal = new GregorianCalendar(TimeZone.getTimeZone("GMT"));
cal.setTimeInMillis(timestamp);
} catch (NumberFormatException e) {
cal = DateTimeHelper.parseXmlDateTime(s);
}
if (cal == null) {
throw new CmisInvalidArgumentException(propDef.getId() + " value is not an datetime value!");
}
calValues.add(cal);
}
propertyData = new PropertyDateTimeImpl(propDef.getId(), calValues);
break;
case HTML:
propertyData = new PropertyHtmlImpl(propDef.getId(), strValues);
break;
case URI:
propertyData = new PropertyUriImpl(propDef.getId(), strValues);
break;
default:
assert false;
}
return propertyData;
}
public List<String> createPolicies(ControlParser controlParser) {
return controlParser.getValues(Constants.CONTROL_POLICY);
}
public Acl createAddAcl(ControlParser controlParser) {
List<String> principals = controlParser.getValues(Constants.CONTROL_ADD_ACE_PRINCIPAL);
if (principals == null) {
return null;
}
List<Ace> aces = new ArrayList<Ace>();
int i = 0;
for (String principalId : principals) {
aces.add(new AccessControlEntryImpl(new AccessControlPrincipalDataImpl(principalId), controlParser
.getValues(Constants.CONTROL_ADD_ACE_PERMISSION, i)));
i++;
}
return new AccessControlListImpl(aces);
}
public Acl createRemoveAcl(ControlParser controlParser) {
List<String> principals = controlParser.getValues(Constants.CONTROL_REMOVE_ACE_PRINCIPAL);
if (principals == null) {
return null;
}
List<Ace> aces = new ArrayList<Ace>();
int i = 0;
for (String principalId : principals) {
aces.add(new AccessControlEntryImpl(new AccessControlPrincipalDataImpl(principalId), controlParser
.getValues(Constants.CONTROL_REMOVE_ACE_PERMISSION, i)));
i++;
}
return new AccessControlListImpl(aces);
}
public ContentStream createContentStream(HttpServletRequest request) {
ContentStreamImpl result = null;
if (request instanceof POSTHttpServletRequestWrapper) {
POSTHttpServletRequestWrapper post = (POSTHttpServletRequestWrapper) request;
if (post.getStream() != null) {
result = new ContentStreamImpl(post.getFilename(), post.getSize(), post.getContentType(),
post.getStream());
}
}
return result;
}
}