Java tutorial
/******************************************************************************* * Copyright 2012 Fatminds, Inc * * Licensed 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 com.fatminds.cmis; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStreamReader; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.math.BigDecimal; import java.net.URLEncoder; import java.util.ArrayList; import java.util.GregorianCalendar; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import org.alfresco.cmis.client.AlfrescoAspects; import org.apache.chemistry.opencmis.client.api.CmisObject; import org.apache.chemistry.opencmis.client.api.Document; import org.apache.chemistry.opencmis.client.api.Folder; import org.apache.chemistry.opencmis.client.api.ObjectId; import org.apache.chemistry.opencmis.client.api.ObjectType; import org.apache.chemistry.opencmis.client.api.OperationContext; import org.apache.chemistry.opencmis.client.api.Property; import org.apache.chemistry.opencmis.client.api.Relationship; import org.apache.chemistry.opencmis.client.api.Session; import org.apache.chemistry.opencmis.client.runtime.ObjectIdImpl; import org.apache.chemistry.opencmis.commons.PropertyIds; import org.apache.chemistry.opencmis.commons.data.ContentStream; import org.apache.chemistry.opencmis.commons.definitions.PropertyDefinition; import org.apache.chemistry.opencmis.commons.enums.Cardinality; import org.apache.chemistry.opencmis.commons.enums.IncludeRelationships; import org.apache.chemistry.opencmis.commons.enums.PropertyType; import org.apache.chemistry.opencmis.commons.enums.VersioningState; import org.apache.chemistry.opencmis.commons.impl.dataobjects.ContentStreamImpl; import org.apache.http.HttpResponse; import org.apache.http.auth.AuthScope; import org.apache.http.auth.UsernamePasswordCredentials; import org.apache.http.client.ClientProtocolException; import org.apache.http.client.methods.HttpDelete; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpPost; import org.apache.http.impl.client.DefaultHttpClient; import org.apache.http.params.HttpParams; import org.codehaus.jackson.JsonNode; import org.codehaus.jackson.map.ObjectMapper; import org.joda.time.DateTime; import org.joda.time.LocalDate; import org.joda.time.format.DateTimeFormat; import org.joda.time.format.ISODateTimeFormat; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.http.HttpEntity; import org.springframework.util.StringUtils; public class AlfrescoCmisHelper { private static final Logger log = LoggerFactory.getLogger(AlfrescoCmisHelper.class); protected static final ObjectMapper mapper = new ObjectMapper(); /** * Cache typeinfo, it's expensive to retrieve */ protected static final Map<String, AlfrescoCmisTypeInfo> typeInfo = new HashMap<String, AlfrescoCmisTypeInfo>(); public static final String ALF_DICT_API_BASE = "/alfresco/service/api/classes/"; public static final String DASH_SEP_STRING = "---"; public static final String ALF_CATEGORY_API_BASE = "/alfresco/service/fatminds/categories/"; public static final String ALF_CATEGORY_CRUD_API_BASE = "/alfresco/service/fatminds/category"; /** * * @param f Excel file * @return * @throws FileNotFoundException */ public static ContentStream getContentStream(File f) throws FileNotFoundException { if (null == f) return null; FileInputStream fis = new FileInputStream(f); ContentStream cs = new ContentStreamImpl(f.getName(), null, "application/excel", fis); return cs; } /** * Split and return an array of trimmed tokens * @param in * @param splitBy regex * @return */ public static String[] splitTrimString(String in, String splitBy) { if (!StringUtils.hasText(in) || !StringUtils.hasText(splitBy)) { return new String[] {}; } String[] tokens = in.split(splitBy); if (null == tokens) { return new String[] {}; } for (int i = 0; i < tokens.length; i++) { tokens[i] = tokens[i].trim(); } return tokens; } /*********************************************************************************************\ * CMIS object (and tree) cloning * * * \*********************************************************************************************/ public static Document cloneDocument(AlfrescoCmisSessionDataSource dataSource, Document source, Folder destination, Map<String, Object> replaceProps) { return (Document) cloneObject(dataSource, source, destination, replaceProps, false); } public static Folder cloneFolder(AlfrescoCmisSessionDataSource dataSource, Folder source, Folder destination, Map<String, Object> replaceProps) { return (Folder) cloneObject(dataSource, source, destination, replaceProps, false); } /* * @param source - the Folder or Document you wish to clone * @param replacementProps - properties that will replace values from the source object in the cloned * object. Note ***MUST*** contain an entry for cmis:name if you are copying the object into the same space as its source, * and that entry must be unique according to the rules for cmis:name. * @return the cloned object, or null if any of the inputs were null * */ protected static CmisObject cloneObject(AlfrescoCmisSessionDataSource dataSource, CmisObject source, Folder destFolder, Map<String, Object> replacementProps, boolean recurse) { if (null == source || null == replacementProps || null == dataSource || null == destFolder) { return null; } // Set up properties for new object. Start by copying the old. Map<String, Object> newProps = cloneProps(source); // Now copy over replacement properties supplied by caller (which had best include // a (different) value for cmis:name, unless this is a recursed call for a child of some // copied node). for (String k : replacementProps.keySet()) { newProps.put(k, replacementProps.get(k)); } // Finally, calculate and apply the correct Alfresco-oriented cmis:objectTypeId set AlfrescoCmisTypeInfo info = getTypeAndMandatoryAspectProperyDefinitions(dataSource, (String) newProps.get(PropertyIds.OBJECT_TYPE_ID)); newProps.put(PropertyIds.OBJECT_TYPE_ID, info.getCmisTypeIdWithMandatoryAspects()); CmisObject newObject; if (source instanceof Document) { Document srcDoc = (Document) source; newObject = destFolder.createDocument(newProps, null, VersioningState.MAJOR); } else { // source is a Folder Folder srcFolder = (Folder) source; newObject = destFolder.createFolder(newProps); if (recurse) { for (CmisObject obj : srcFolder.getChildren()) { Map<String, Object> emptyProps = new HashMap<String, Object>(); cloneObject(dataSource, obj, ((Folder) newObject), emptyProps, true); } } Folder newFolder = (Folder) newObject; } for (Relationship r : source.getRelationships()) { if (r.getSource() == source) { // Copy only source relationships Map<String, Object> props = new HashMap<String, Object>(); props.put(PropertyIds.SOURCE_ID, newObject.getId()); props.put(PropertyIds.TARGET_ID, r.getTargetId().toString()); props.put(PropertyIds.OBJECT_TYPE_ID, r.getPropertyValue(PropertyIds.OBJECT_TYPE_ID)); ObjectId newRelationId = dataSource.getSession().createRelationship(props); } } return newObject; } /** * * @param toBeCloned * @return shallow clone of the map */ protected static Map<String, Object> cloneProps(CmisObject obj) { Map<String, Object> cloned = new HashMap<String, Object>(); if (null == obj) { return cloned; } for (Property p : obj.getProperties()) { if (null != p.getValue()) { cloned.put(p.getId(), p.getValue()); log.debug("Cloned property " + p.getId() + ", val = " + p.getValueAsString()); } } return cloned; } /** * Try several different date formats (in order of descending specificity) to parse a potential * date string. * @param inVal * @return * @throws IllegalArgumentException if the string cannot be successfully parsed into a date. */ public static GregorianCalendar attemptDateParse(String inVal) { if (null == inVal || inVal.length() < 1) throw new IllegalArgumentException("Cannot parse null date/time string"); // Attempt formats in order of reducing specificity try { DateTime d = ISODateTimeFormat.dateTime().parseDateTime(inVal); return d.toGregorianCalendar(); } catch (IllegalArgumentException e) { try { LocalDate d = DateTimeFormat.forPattern("yyyy-MM-dd").parseLocalDate(inVal); return d.toDateTimeAtMidnight().toGregorianCalendar(); } catch (IllegalArgumentException e2) { try { LocalDate d = DateTimeFormat.forPattern("MM/dd/yyyy").parseLocalDate(inVal); return d.toDateTimeAtMidnight().toGregorianCalendar(); } catch (IllegalArgumentException e3) { try { LocalDate d = DateTimeFormat.forPattern("MM/dd/yy").parseLocalDate(inVal); return d.toDateTimeAtMidnight().toGregorianCalendar(); } catch (IllegalArgumentException e4) { } } } } log.error("Cannot parse date/time string " + inVal); throw new IllegalArgumentException("Cannot parse date/time string " + inVal); } public static String[] parseList(String inVal) { if (null == inVal) throw new IllegalArgumentException("Empty input"); return inVal.split("---"); } public static Object cmisConvert(String inVal, PropertyDefinition<?> targetType) { if (null == inVal || null == targetType) { //log.info("inVal " + inVal + " or outClass " + outClass + " are null"); throw new IllegalArgumentException("All convert arguments must be non-null"); } List<Object> retVals = new ArrayList<Object>(); PropertyType type = targetType.getPropertyType(); String[] inVals; boolean multiple = targetType.getCardinality().equals(Cardinality.MULTI); if (multiple) { inVals = parseList(inVal); } else { inVals = new String[] { inVal }; } for (String token : inVals) { if (PropertyType.BOOLEAN.value() == type.value()) { retVals.add(Boolean.valueOf(token)); } else if (PropertyType.DATETIME.value() == type.value()) { retVals.add(attemptDateParse(token)); } else if (PropertyType.DECIMAL.value() == type.value()) { retVals.add(new BigDecimal(token)); } else if (PropertyType.HTML.value() == type.value()) { retVals.add(token); } else if (PropertyType.ID.value() == type.value()) { retVals.add(new ObjectIdImpl(token)); } else if (PropertyType.INTEGER.value() == type.value()) { retVals.add(new Long(new BigDecimal(token).longValue())); // Handles dotted "integers" (sigh) like "15.0" } else if (PropertyType.STRING.value() == type.value()) { retVals.add(token); } else if (PropertyType.URI.value() == type.value()) { retVals.add(token); } } if (retVals.size() < 1) throw new IllegalArgumentException( "Couldn't convert field of type " + type.name() + ", value = " + inVal); if (multiple) return retVals; return retVals.get(0); } public static Object convert(Object inVal, Class<?> outClass) { if (null == inVal || null == outClass) { //log.info("inVal " + inVal + " or outClass " + outClass + " are null"); return null; } //log.info("Converting " + inVal.getClass() + " to " + outClass); if (outClass.isAssignableFrom(inVal.getClass())) return inVal; Object outVal; try { Constructor<?> c = outClass.getConstructor(inVal.getClass()); outVal = c.newInstance(inVal); } catch (SecurityException e) { throw new RuntimeException(e); } catch (NoSuchMethodException e) { //log.info("Failed to find constructor of " + outClass.toString() // + " with input parameter " + inVal.getClass().toString() // + ", returning unconverted value"); outVal = inVal; } catch (IllegalArgumentException e) { throw new RuntimeException(e); } catch (InstantiationException e) { throw new RuntimeException(e); } catch (IllegalAccessException e) { throw new RuntimeException(e); } catch (InvocationTargetException e) { throw new RuntimeException(e); } //log.info("Returning an instance of " + outVal.getClass()); return outVal; } @SuppressWarnings("rawtypes") public static Class getPropertyClass(PropertyDefinition pd) { PropertyType type = pd.getPropertyType(); //boolean multiple = pd.getCardinality().equals(Cardinality.MULTI); Class c = null; if (PropertyType.BOOLEAN.value() == type.value()) { c = Boolean.class; } else if (PropertyType.DATETIME.value() == type.value()) { c = GregorianCalendar.class; } else if (PropertyType.DECIMAL.value() == type.value()) { c = BigDecimal.class; } else if (PropertyType.HTML.value() == type.value()) { c = String.class; } else if (PropertyType.ID.value() == type.value()) { c = ObjectId.class; } else if (PropertyType.INTEGER.value() == type.value()) { c = Long.class; } else if (PropertyType.STRING.value() == type.value()) { c = String.class; } else if (PropertyType.URI.value() == type.value()) { c = String.class; } if (null != c) { return c; } log.error("Unsupported property type " + type.value()); throw new RuntimeException("Unsupported property type " + type.value()); } /** * * @param alfrescoHostAndPort (i.e. dev2-instance.fatminds.com:8080) * @param cmisType (i.e. F:fminstitution:institution) * @return the set of all properties carried by each mandatory aspect defined in the alfresco * data model for the given cmisType */ public static Set<String> getMandatoryAspects(JsonNode root) { if (null == root || !root.has("defaultAspects")) { throw new RuntimeException("root must not be null, and must contain /defaultAspects path"); } HashSet<String> aspects = new HashSet<String>(); Iterator<String> i = root.path("defaultAspects").getFieldNames(); while (i.hasNext()) { String aspect = i.next(); // TODO: clean this up if (aspect.startsWith("sys")) continue; if (aspect.startsWith("cm:auditable")) continue; aspects.add(aspect); } return aspects; } public static String sanitizeForCmisName(String in) { if (null == in || in.isEmpty()) { return null; } in = in.replaceAll("[^a-zA-Z_0-9]", "_").replaceAll("_{2,}+", "_"); return in; } public static String cmisToAlfrescoTypename(String cmisTypename) { if (null == cmisTypename) return null; // Mangle name according to alf API rules: [FDPA]:nsprefix:localname -> nsprefix_localname StringBuilder sb = new StringBuilder(); int secondColon = cmisTypename.indexOf(':', 2); sb.append(cmisTypename.substring(2, secondColon)); // sb == nsprefix sb.append('_'); sb.append(cmisTypename.substring(secondColon + 1)); return sb.toString(); } public static String stripCmisTypeSpecifier(String typeSpec) { if (null == typeSpec || typeSpec.isEmpty()) { return null; } int pos = typeSpec.indexOf(':', 2); if (pos > 0) { return typeSpec.substring(2); } return typeSpec; } public static String alfModelToApiClassname(String modelClassname) { return modelClassname.replace(':', '_'); } /** * This is here and needed because Alfresco CMIS and Chemistry don't play nice together for relationship deletes * as of Alfresco 3.4.2-5 & Chemistry 0.3.0. The problem is that for whatever reason Chemistry strips the "assoc:" prefix * off of the relationship ID that ends up on the end of the Delete URL, like this - /alfresco/service/cmis/rel/assoc:363. * Left to its own devices, Relationship.delete() will end up issuing a URL like /alfresco/service/cmis/rel/363, and Alfresco * will barf. This will hopefully be fixed in Alfesco 4.x. * @param proto * @param host * @param port * @param user * @param pass * @param assocId * @return */ public static boolean deleteRelationship(String proto, String host, int port, String user, String pass, String assocId) { DefaultHttpClient client = new DefaultHttpClient(); try { client.getCredentialsProvider().setCredentials(new AuthScope(host, port), new UsernamePasswordCredentials(user, pass)); HttpDelete delete = new HttpDelete( proto + "://" + host + ":" + port + "/alfresco/service/cmis/rel/" + assocId); log.info("Sending " + delete.toString()); HttpResponse resp = client.execute(delete); if (resp.getStatusLine().getStatusCode() > 299) { // Alf returns "204 No Content" for success... :-( throw new RuntimeException("Get failed (" + resp.getStatusLine().getStatusCode() + ") because " + resp.getStatusLine().getReasonPhrase()); } } catch (ClientProtocolException e) { e.printStackTrace(); throw new RuntimeException(e); } catch (IOException e) { e.printStackTrace(); throw new RuntimeException(e); } finally { client.getConnectionManager().shutdown(); } return true; } public static JsonNode getJsonNodeFromHttpGetResponse(String proto, String host, int port, String user, String pass, String path, HashMap params) throws ClientProtocolException, IOException { DefaultHttpClient client = new DefaultHttpClient(); try { client.getCredentialsProvider().setCredentials(new AuthScope(host, port), new UsernamePasswordCredentials(user, pass)); String paramStr = ""; if (params != null && params.size() > 0) { Iterator<String> iter = params.keySet().iterator(); while (iter.hasNext()) { String key = iter.next(); //get.getParams().setParameter(key, params.get(key)); if (!paramStr.equals("")) { paramStr += "&"; } paramStr += key + "=" + params.get(key); } } if (!paramStr.equals("")) { path += "?" + URLEncoder.encode(paramStr, "UTF-8").replace("+", "%20"); } HttpGet get = new HttpGet(proto + host + ":" + port + path); log.info("Getting JsonNode for " + get.toString()); HttpResponse resp = client.execute(get); if (resp.getStatusLine().getStatusCode() != 200) { throw new RuntimeException( "Get failed, can't build JsonNode because: " + resp.getStatusLine().getReasonPhrase()); } org.apache.http.HttpEntity entity = resp.getEntity(); return mapper.readValue(entity.getContent(), JsonNode.class); } finally { client.getConnectionManager().shutdown(); } } public static JsonNode getJsonNodeFromHttpGetResponse(String proto, String host, int port, String user, String pass, String path) throws ClientProtocolException, IOException { return getJsonNodeFromHttpGetResponse(proto, host, port, user, pass, path, null); } public static void main(String[] args) { String user = "admin"; String pass = "admin"; String host = "localhost"; int port = 8080; Set<String> props = getMandatoryAspects("F:fmbase:educationiteminstance", "http://", host, port, user, pass); System.out.println("Mandatory aspects"); for (String s : props) { System.out.println("\t " + s); } } public static JsonNode getFatmindsCategories(AlfrescoCmisSessionDataSource cmisDataSource) { System.out.println("Getting Fatminds Category Tree"); try { return getJsonNodeFromHttpGetResponse("http://", cmisDataSource.getHostname(), cmisDataSource.getPort(), cmisDataSource.getUsername(), cmisDataSource.getPassword(), ALF_CATEGORY_API_BASE); } catch (Exception e) { throw new RuntimeException("Could not retrieve Fatminds Category Tree.", e); } } public static JsonNode crudFatmindsCategory(AlfrescoCmisSessionDataSource cmisDataSource, String path, String action) { System.out.println("CRUD Fatminds Category"); try { HashMap params = new HashMap(); params.put("path", path); params.put("action", action); return getJsonNodeFromHttpGetResponse("http://", cmisDataSource.getHostname(), cmisDataSource.getPort(), cmisDataSource.getUsername(), cmisDataSource.getPassword(), ALF_CATEGORY_CRUD_API_BASE, params); } catch (Exception e) { throw new RuntimeException("Could not CRUD Fatminds Category", e); } } public static Set<String> getMandatoryAspects(AlfrescoCmisSessionDataSource cmisDataSource, String cmisTypename) { return getMandatoryAspects(cmisTypename, "http://", cmisDataSource.getHostname(), cmisDataSource.getPort(), cmisDataSource.getUsername(), cmisDataSource.getPassword()); } public static Set<String> getMandatoryAspects(String cmisTypename, String proto, String host, int port, String user, String pass) { String apiCall = ALF_DICT_API_BASE + cmisToAlfrescoTypename(cmisTypename); System.out.println("Getting " + apiCall); try { JsonNode root = getJsonNodeFromHttpGetResponse("http://", host, port, user, pass, apiCall, null); log.info("Got JSON root " + root.path("name").getTextValue()); return getMandatoryAspects(root); } catch (Exception e) { throw new RuntimeException("Could not retrieve mandatory aspect properties.", e); } } public static Set<String> getMandatoryAspectProperties(AlfrescoCmisSessionDataSource cmisDataSource, String cmisTypename) { return getMandatoryAspectProperties(cmisTypename, "http://", cmisDataSource.getHostname(), cmisDataSource.getPort(), cmisDataSource.getUsername(), cmisDataSource.getPassword()); } public static Set<String> getMandatoryAspectProperties(String cmisTypename, String proto, String host, int port, String user, String pass) { String apiCall = ALF_DICT_API_BASE + cmisToAlfrescoTypename(cmisTypename); System.out.println("Getting " + apiCall); try { JsonNode root = getJsonNodeFromHttpGetResponse("http://", host, port, user, pass, apiCall); log.info("Got JSON root " + root.path("name").getTextValue()); HashSet<String> mandatoryAspectProperties = new HashSet<String>(); // add properties to this as they're found Set<String> aspects = getMandatoryAspects(root); for (String s : aspects) { String propCall = ALF_DICT_API_BASE + alfModelToApiClassname(s) + "/properties"; log.info("Retrieving properties for mandatory aspect " + s + " at API url " + propCall); JsonNode propArray = getJsonNodeFromHttpGetResponse("http://", host, port, user, pass, propCall); for (JsonNode n : propArray) { String propName = n.path("name").getTextValue(); log.info("Found aspect property " + propName + " in aspect " + s); mandatoryAspectProperties.add(propName); } } return mandatoryAspectProperties; } catch (Exception e) { throw new RuntimeException("Could not retrieve mandatory aspect properties.", e); } } /** * @param objectId of an existing CmisObject * @param aspects - a list of aspect names that should be checked for existence on the retrieved object; * @return The object, with ALL MANDATORY ASPECTS APPLIED if not found to exist already on each object. This will not * have any effect in normal operation, but you will really appreciate it if and when you change a content model to include * a new mandatory aspect that isn't already applied to existing content. Hello, CmisExceptions on commit. */ public static CmisObject getObjectByIdAndAddAspects(Session session, String objectId, Set<String> aspects) { OperationContext oc = session.createOperationContext(); oc.setIncludeRelationships(IncludeRelationships.SOURCE); CmisObject obj = session.getObject(session.createObjectId(objectId), oc); if (!obj.getRelationships().isEmpty()) { log.info("Found relationships on " + obj.getName()); } if (null == obj) return null; if (obj instanceof AlfrescoAspects) { AlfrescoAspects as = (AlfrescoAspects) obj; if (null != aspects) { for (String aspect : aspects) { if (!as.hasAspect(aspect)) { as.addAspect("P:" + aspect); } } } } return obj; } /** * Look up the CMIS type or policy that owns the specified property ID * @param session - Valid CMIS Session * @param propId - Name of the property for which we want the owning (base) class * @param baseTypeCandidates -- List of candidate types to search - only leaf nodes in the type hierarchy need be specified * @return */ public static ObjectType findPropertyOwnerClass(Session session, String propId, String[] baseTypeCandidates) { if (null == propId) { throw new IllegalArgumentException("Can't pass null propId"); } if (null == baseTypeCandidates || 0 == baseTypeCandidates.length) { throw new IllegalArgumentException("Must pass at least one base type candidate"); } for (String candidate : baseTypeCandidates) { ObjectType ot = session.getTypeDefinition(candidate); if (null == ot) { throw new IllegalArgumentException("Cannot find type definition for candidate type " + candidate); } if (ot.getPropertyDefinitions().containsKey(propId)) { // Find defining class if (ot.getPropertyDefinitions().get(propId).isInherited()) { while (ot.getPropertyDefinitions().get(propId).isInherited()) { ot = ot.getParentType(); } } return ot; } } log.warn("No property owner class found for " + propId); return null; } /** * Represent complete CMIS type ID (inclusive of comma-separated mandatory aspect list) suitable for passing to * Session.create*, and full property definition set for this complete ID (i.e. also includes aspect properties). * * throws RuntimeException if it fails, otherwise is guaranteed to return non-null result. * * @author aaronlee * */ public static class AlfrescoCmisTypeInfo { protected String baseCmisTypeId = ""; protected String cmisTypeIdWithMandatoryAspects = ""; protected Map<String, PropertyDefinition<?>> propertyDefinitions = new HashMap<String, PropertyDefinition<?>>(); public AlfrescoCmisTypeInfo() { } public AlfrescoCmisTypeInfo(String baseTypeId, String typeId, Map<String, PropertyDefinition<?>> props) { this.baseCmisTypeId = baseTypeId; this.cmisTypeIdWithMandatoryAspects = typeId; this.propertyDefinitions = props; } /** * @return the cmisTypeIdWithMandatoryAspects */ public String getCmisTypeIdWithMandatoryAspects() { return cmisTypeIdWithMandatoryAspects; } /** * @return the propertyDefinitions */ public Map<String, PropertyDefinition<?>> getPropertyDefinitions() { return propertyDefinitions; } /** * @param cmisTypeIdWithMandatoryAspects the cmisTypeIdWithMandatoryAspects to set */ public void setCmisTypeIdWithMandatoryAspects(String cmisTypeIdWithMandatoryAspects) { this.cmisTypeIdWithMandatoryAspects = cmisTypeIdWithMandatoryAspects; } /** * @param propertyDefinitions the propertyDefinitions to set */ public void setPropertyDefinitions(Map<String, PropertyDefinition<?>> propertyDefinitions) { this.propertyDefinitions = propertyDefinitions; } /** * @return the baseCmisTypeId */ public String getBaseCmisType() { return baseCmisTypeId; } /** * @param baseCmisTypeId the baseCmisTypeId to set */ public void setBaseCmisType(String baseCmisType) { this.baseCmisTypeId = baseCmisType; } } /** * * @param cmisDataSource * @param cmisType * @return properties and the Alfresco-formatted cmis type Id, including mandatory aspects. i.e. * - input cmisType: F:myapp:mytype * - output cmisType: F:myapp:mytype,P:myapp:mymandatoryaspect */ public static AlfrescoCmisTypeInfo getTypeAndMandatoryAspectProperyDefinitions( AlfrescoCmisSessionDataSource cmisDataSource, String cmisType) { if (null == cmisDataSource || null == cmisType) throw new RuntimeException("cmisDataSource or cmisType was null"); if (null != typeInfo.get(cmisType)) { return typeInfo.get(cmisType); } Session session = cmisDataSource.getSession(); ObjectType typeDef = session.getTypeDefinition(cmisType); if (null == typeDef) { throw new RuntimeException("No type definition found for cmisType " + cmisType); } String cmisTypeIdWithAspects = cmisType; Map<String, PropertyDefinition<?>> returnProps = new HashMap<String, PropertyDefinition<?>>(); Map<String, PropertyDefinition<?>> sourceDefs = typeDef.getPropertyDefinitions(); for (String propId : sourceDefs.keySet()) { PropertyDefinition pd = sourceDefs.get(propId); returnProps.put(propId, pd); //log.info("\t" + propId + " = " + (null == pd.getChoices() ? "null" : pd.getChoices())); } // Get list of mandatory aspects applied to this type Set<String> aspects = AlfrescoCmisHelper.getMandatoryAspects(cmisDataSource, cmisType); for (String aspect : aspects) { String cmisPolicy = "P:" + aspect; // Add to object creation argument value (should be F:your:type[,P:some:aspect,P:another:aspect]) cmisTypeIdWithAspects += "," + cmisPolicy; log.debug("Retrieving type definition for " + cmisPolicy); try { ObjectType policyDef = session.getTypeDefinition(cmisPolicy); Map<String, PropertyDefinition<?>> aspectProps = policyDef.getPropertyDefinitions(); /** * So, guess what? If, in your Alfresco content model you have * <aspect name="blah:blah"> <properties></properties>...</aspect> (i.e. no properties on the aspect) * then policyDef.getPropertyDefinitions() returns null. Fair enough, I guess. * */ if (null == aspectProps) continue; for (String prop : aspectProps.keySet()) { log.trace("Found property " + prop + " on policy " + cmisPolicy + ", adding to CmisContainer property list"); returnProps.put(prop, aspectProps.get(prop)); } } catch (Exception e) { // This means we requested the type definition for an aspect that // is not exposed through CMIS. This means that properties // in that aspect are not available to the container (or any CMIS // request). This is not an error, so continue. } } AlfrescoCmisTypeInfo info = new AlfrescoCmisTypeInfo(cmisType, cmisTypeIdWithAspects, returnProps); typeInfo.put(cmisType, info); return info; } }