package jp.aegif.nemaki.rest; import java.io.InputStream; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map.Entry; import javax.ws.rs.Consumes; import javax.ws.rs.POST; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; import javax.ws.rs.core.MediaType; import jp.aegif.nemaki.businesslogic.TypeService; import jp.aegif.nemaki.cmis.aspect.type.TypeManager; import jp.aegif.nemaki.model.Choice; import jp.aegif.nemaki.model.NemakiPropertyDefinition; import jp.aegif.nemaki.model.NemakiPropertyDefinitionCore; import jp.aegif.nemaki.model.NemakiPropertyDefinitionDetail; import jp.aegif.nemaki.model.NemakiTypeDefinition; import jp.aegif.nemaki.util.constant.NodeType; import org.apache.chemistry.opencmis.commons.enums.BaseTypeId; import org.apache.chemistry.opencmis.commons.enums.Cardinality; import org.apache.chemistry.opencmis.commons.enums.PropertyType; import org.apache.chemistry.opencmis.commons.enums.Updatability; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.dom4j.Attribute; import org.dom4j.Document; import org.dom4j.DocumentException; import org.dom4j.Element; import org.dom4j.io.SAXReader; import org.json.simple.JSONArray; import org.json.simple.JSONObject; import com.sun.jersey.multipart.FormDataParam; @Path("/repo/{repositoryId}/type") public class TypeResource extends ResourceBase{ private TypeService typeService; private TypeManager typeManager; private final Log log = LogFactory.getLog(TypeResource.class); private final HashMap<String, NemakiTypeDefinition> typeMaps = new HashMap<String, NemakiTypeDefinition>(); private final HashMap<String, NemakiPropertyDefinitionCore> coreMaps = new HashMap<String, NemakiPropertyDefinitionCore>(); private final HashMap<String, NemakiPropertyDefinitionDetail> detailMaps = new HashMap<String, NemakiPropertyDefinitionDetail>(); private final HashMap<String, List<String>> typeProperties = new HashMap<String, List<String>>(); @POST @Path("/register") @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.MULTIPART_FORM_DATA) public String register(@PathParam("repositoryId") String repositoryId, @FormDataParam("data") InputStream is) { boolean status = true; JSONObject result = new JSONObject(); JSONArray errMsg = new JSONArray(); try{ parse(repositoryId, is); create(repositoryId); typeManager.refreshTypes(); status = true; }catch(Exception e){ log.warn("Type registrations fails", e); addErrMsg(errMsg, "types", "failsToRegister"); } result = makeResult(status, result, errMsg); return result.toJSONString(); } private void parse(String repositoryId, InputStream is){ SAXReader saxReader = new SAXReader(); Document document; try { document = saxReader.read(is); Element model = document.getRootElement(); //Types Element _types = getElement(model, "types"); List<Element> types = getElements(_types, "type"); parseTypes(repositoryId, types); //Aspects Element _aspects = getElement(model, "aspects"); List<Element> aspects = getElements(_aspects, "aspect"); parseTypes(repositoryId, aspects); } catch (DocumentException e) { // TODO Auto-generated catch block e.printStackTrace(); } } private void parseTypes(String repositoryId, List<Element> types) { for (Element type : types) { // Extract values // TODO "enabled" // //// // type // //// NemakiTypeDefinition tdf = new NemakiTypeDefinition(); // typeId String typeId = getAttributeValue(type, "name"); if (StringUtils.isEmpty(typeId)) { log.warn("typeId should be specified. SKIP."); } else { if (existType(repositoryId, typeId)) { log.warn("typeId:" + typeId + " already exists in DB! SKIP."); continue; } } tdf.setTypeId(typeId); tdf.setLocalName(typeId); // title String title = getElementValue(type, "title"); if (StringUtils.isEmpty(title)) { log.warn("typeId:" + typeId + " 'title' is nos specified. Default to typeId."); } tdf.setLocalNameSpace(""); tdf.setDisplayName(title); tdf.setDescription(title); // parent and baseType String parent = getElementValue(type, "parent"); if ("type".equals(type.getName())) { if(StringUtils.isEmpty(parent)){ log.warn("typeId:" + typeId + " 'parent' should be specified. SKIP."); continue; } if ("cm:content".equals(parent)) { tdf.setBaseId(BaseTypeId.CMIS_DOCUMENT); tdf.setParentId(BaseTypeId.CMIS_DOCUMENT.value()); } else if ("cm:folder".equals(parent)) { tdf.setBaseId(BaseTypeId.CMIS_FOLDER); tdf.setParentId(BaseTypeId.CMIS_FOLDER.value()); } else { // TODO association(relationship) } }else if("aspect".equals(type.getName())){ tdf.setBaseId(BaseTypeId.CMIS_SECONDARY); if(StringUtils.isBlank(parent)){ tdf.setParentId(BaseTypeId.CMIS_SECONDARY.value()); }else{ tdf.setParentId(parent); } } // properties Element _properties = getElement(type, "properties"); List<Element> properties = getElements(_properties, "property"); if(CollectionUtils.isNotEmpty(properties)){ parseProperties(repositoryId, typeId, properties); } // Put to map typeMaps.put(typeId, tdf); } } private void parseProperties(String repositoryId, String typeId, List<Element> properties){ List<String> propertyIds = new ArrayList<String>(); for (Element property : properties) { NemakiPropertyDefinitionCore core = new NemakiPropertyDefinitionCore(); NemakiPropertyDefinitionDetail detail = new NemakiPropertyDefinitionDetail(); // propertyId String propName = getAttributeValue(property, "name"); // Check existing property definitions if (existProperty(repositoryId, propName)) { log.warn("propertyId:" + propName + " already exists in DB! SKIP."); continue; } propertyIds.add(propName); // //// // core // //// // propertyId core.setPropertyId(propName); // queryName core.setQueryName(propName); // data type String dataType = getElementValue(property, "type"); if ("d:text".equals(dataType) || "d:mltext".equals(dataType) || "d:content".equals(dataType)) { core.setPropertyType(PropertyType.STRING); } else if ("d:int".equals(dataType) || "d:long".equals(dataType)) { core.setPropertyType(PropertyType.INTEGER); } else if ("d:float".equals(dataType) || "d:double".equals(dataType)) { // FIXME is this mapping OK? core.setPropertyType(PropertyType.DECIMAL); } else if ("d:date".equals(dataType) || "d:datetime".equals(dataType)) { // TODO implement datePrecision core.setPropertyType(PropertyType.DATETIME); } else if ("d:boolean".equals(dataType)) { core.setPropertyType(PropertyType.BOOLEAN); } else if ("d:any".equals(dataType)) { log.info(buildMsg(typeId, propName, "'d:any data' types is not allowed. Defaults to STRING.")); core.setPropertyType(PropertyType.STRING); } else { log.info(buildMsg(typeId, propName, "'Unknown data type. Defaults to STRING.")); core.setPropertyType(PropertyType.STRING); } // cardinality String multiple = getElementValue(property, "multiple"); if ("true".equals(multiple)) { core.setCardinality(Cardinality.MULTI); } else { if (StringUtils.isBlank(multiple)) { log.info(buildMsg(typeId, propName, "'multiple' is not specified. Default to false")); } core.setCardinality(Cardinality.SINGLE); } coreMaps.put(propName, core); // ////// // detail // ////// detail.setType(NodeType.PROPERTY_DEFINITION_DETAIL.value()); // defaultValue String defaultValue = getElementValue(property, "default"); // TODO multiple default values are allowed? if(!StringUtils.isBlank(defaultValue)){ List<Object> defaults = new ArrayList<Object>(); defaults.add(defaultValue); detail.setDefaultValue(defaults); } // if defaultValue not set, it should be null for WSConverter // constraints Element _constraints = getElement(property, "constraints"); setConstraints(detail, _constraints); //updatability detail.setUpdatability(Updatability.READWRITE); // required if (existElement(property, "mandatory")) { detail.setRequired(true); } else { log.info(buildMsg(typeId, propName, "'mandatory' is not specified. Default to false")); detail.setRequired(false); } // queryable Element index = getElement(property, "index"); String _indexEnabled = getAttributeValue(index, "enabled"); boolean indexEnabled = ("true".equals(_indexEnabled)) ? true : false; if (indexEnabled) { detail.setQueryable(true); } else { log.info(buildMsg(typeId, propName, "'index' is not specified. Default to false")); detail.setQueryable(false); } // FIXME openChoice is default to false? detail.setOpenChoice(false); detailMaps.put(propName, detail); } typeProperties.put(typeId, propertyIds); } private void setConstraints(NemakiPropertyDefinitionDetail detail, Element _constraints) { List<Element> constraints = getElements(_constraints, "constraint"); for (Element constraint : constraints) { String type = getAttributeValue(constraint, "type"); if (type != null) { if ("LENGTH".equals(type)) { String minLength = getElementValue(constraint, "minLength"); String maxLength = getElementValue(constraint, "maxLength"); if (StringUtils.isNotBlank(maxLength)) { detail.setMaxLength(Long.valueOf(maxLength)); } } else if ("MINMAX".equals(type)) { String minValue = getElementValue(constraint, "minValue"); if (StringUtils.isNotBlank(minValue)) { detail.setMaxValue(Long.valueOf(minValue)); } String maxValue = getElementValue(constraint, "maxValue"); if (StringUtils.isNotBlank(maxValue)) { detail.setMaxValue(Long.valueOf(maxValue)); } } else if ("LIST".equals(type)) { Element _allowed = getElement(constraint, "parameter"); if (_allowed != null) { Element list = getElement(_allowed, "list"); List<String> values = getElementsValues(_allowed, "value"); Choice choice = new Choice(); List<Object> _values = new ArrayList<Object>(); for (String s : values) { _values.add(s); } choice.setValue(_values); List<Choice> choices = new ArrayList<Choice>(); detail.setChoices(choices); } } } } } private void create(String repositoryId) { // First, create properties for (Entry<String, NemakiPropertyDefinitionCore> coreEntry : coreMaps .entrySet()) { NemakiPropertyDefinition p = new NemakiPropertyDefinition( coreEntry.getValue(), detailMaps.get(coreEntry.getKey())); typeService.createPropertyDefinition(repositoryId, p); NemakiPropertyDefinitionCore createdCore = typeService .getPropertyDefinitionCoreByPropertyId(repositoryId, p.getPropertyId()); coreEntry.getValue().setId(createdCore.getId()); } //Prepare types for (Entry<String, NemakiTypeDefinition> typeEntry : typeMaps .entrySet()) { NemakiTypeDefinition t = typeEntry.getValue(); // TODO Set property detail ids List<String> propertyNodeIds = new ArrayList<String>(); List<String> propertyIds = typeProperties.get(t.getTypeId()); if(CollectionUtils.isNotEmpty(propertyIds)){ for (String propertyId : typeProperties.get(t.getTypeId())) { NemakiPropertyDefinitionCore core = typeService .getPropertyDefinitionCoreByPropertyId(repositoryId, propertyId); //propertyNodeIds.add(core.getId()); List<NemakiPropertyDefinitionDetail> details = typeService.getPropertyDefinitionDetailByCoreNodeId(repositoryId, core.getId()); if(CollectionUtils.isEmpty(details)){ log.warn(buildMsg(t.getTypeId(), propertyId, "Skipped to add this property because of incorrect data in DB.")); }else{ //Presuppose there is no multiple detail for each core NemakiPropertyDefinitionDetail detail = details.get(0); propertyNodeIds.add(detail.getId()); } } t.setProperties(propertyNodeIds); } //Remove orphan types if(typeMaps.get(t.getParentId()) == null && !isBaseType(t.getParentId())){ log.warn(buildMsg(t.getId(), null, "Skipped to create this type because it has an unknown parent type.")); }else{ typeService.createTypeDefinition(repositoryId, t); } } } private Element getElement(Element parent, String name) { Element result = null; if(existElement(parent, name)){ for (Iterator<Element> iterator = parent.elementIterator(name); iterator .hasNext();) { result = iterator.next(); } return result; }else{ log.info("Cannot parse " + "'" + name + "'."); return result; } } private List<Element> getElements(Element parent, String name) { List<Element> results = new ArrayList<Element>(); if(existElement(parent, name)){ for (Iterator<Element> iterator = parent.elementIterator(name); iterator .hasNext();) { results.add(iterator.next()); } return results; }else{ log.info("Cannot parse " + "'" + name + "'."); return results; } } private String getElementValue(Element parent, String name) { Element elm = getElement(parent, name); if (elm != null) { return elm.getStringValue(); } else { log.info("Cannot parse " + "'" + name + "'."); return null; } } private List<String> getElementsValues(Element parent, String name) { List<Element> elements = getElements(parent, name); List<String> result = new ArrayList<String>(); for (Element element : elements) { result.add(element.getStringValue()); } return result; } private String getAttributeValue(Element element, String name){ if(existAttribute(element, name)){ Attribute attr = element.attribute(name); return attr.getStringValue(); }else{ return null; } } private boolean isBaseType(String typeId) { if (BaseTypeId.CMIS_DOCUMENT.value().equals(typeId) || BaseTypeId.CMIS_FOLDER.value().equals(typeId) || BaseTypeId.CMIS_RELATIONSHIP.value().equals(typeId) || BaseTypeId.CMIS_POLICY.value().equals(typeId) || BaseTypeId.CMIS_ITEM.value().equals(typeId) || BaseTypeId.CMIS_SECONDARY.value().equals(typeId)) { return true; } else { return false; } } private boolean existType(String repositoryId, String typeId) { NemakiTypeDefinition existing = typeService.getTypeDefinition(repositoryId, typeId); if (existing == null) { return false; } else { return true; } } private boolean existProperty(String repositoryId, String propertyId) { NemakiPropertyDefinitionCore existing = typeService .getPropertyDefinitionCoreByPropertyId(repositoryId, propertyId); if (existing == null) { return false; } else { return true; } } private boolean existElement(Element parent, String name){ try{ parent.element(name); return true; }catch(Exception e){ return false; } } private boolean existAttribute(Element element, String name){ try{ element.attribute(name); return true; }catch(Exception e){ return false; } } private String buildMsg(String typeId, String propertyId, String msg){ List<String> header = new ArrayList<String>(); String _typeId = ""; if(StringUtils.isNotBlank(typeId)){ _typeId = "typeId=" + typeId; header.add(_typeId); } String _proeprtyId = ""; if(StringUtils.isNotBlank(propertyId)){ _proeprtyId = "propertyId=" + propertyId; header.add(_proeprtyId); } String _header = StringUtils.join(header, ","); _header = "[" + _header + "]"; return _header + msg; } public void setTypeService(TypeService typeService) { this.typeService = typeService; } public void setTypeManager(TypeManager typeManager) { this.typeManager = typeManager; } }