| /* |
| * 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; |
| } |
| } |