001/* 002 * (C) Copyright 2006-2018 Nuxeo (http://nuxeo.com/) and others. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 * 016 * Contributors: 017 * Bogdan Stefanescu 018 * Florent Guillaume 019 */ 020 021package org.nuxeo.ecm.core.schema; 022 023import static org.nuxeo.ecm.core.schema.types.ComplexTypeImpl.canonicalXPath; 024 025import java.io.Serializable; 026import java.util.Arrays; 027import java.util.HashSet; 028import java.util.LinkedList; 029import java.util.List; 030import java.util.Set; 031import java.util.regex.Pattern; 032 033import org.apache.commons.lang3.StringUtils; 034import org.apache.commons.logging.Log; 035import org.apache.commons.logging.LogFactory; 036import org.nuxeo.ecm.core.schema.types.ComplexType; 037import org.nuxeo.ecm.core.schema.types.Field; 038import org.nuxeo.ecm.core.schema.types.ListType; 039import org.nuxeo.ecm.core.schema.types.Schema; 040import org.nuxeo.ecm.core.schema.types.Type; 041import org.nuxeo.runtime.api.Framework; 042 043/** 044 * Information about what's to be prefetched: individual properties and whole schemas. 045 */ 046public class PrefetchInfo implements Serializable { 047 048 private static final long serialVersionUID = -6495547095819614741L; 049 050 private static final Log log = LogFactory.getLog(PrefetchInfo.class); 051 052 private final String expr; 053 054 /** 055 * @deprecated since 11.1. Not used anymore. 056 */ 057 @Deprecated(since = "11.1") 058 private transient String[] fields; 059 060 private transient String[] schemas; 061 062 public PrefetchInfo(String expr) { 063 this.expr = expr; 064 } 065 066 public String[] getSchemas() { 067 parseExpression(); 068 return schemas; 069 } 070 071 /** 072 * @deprecated since 11.1. Not used anymore. 073 */ 074 @Deprecated(since = "11.1") 075 public String[] getFields() { 076 parseExpression(); 077 return fields; 078 } 079 080 private static final Pattern STAR_OR_DIGITS = Pattern.compile("\\*|(\\d+)"); 081 082 private void parseExpression() { 083 if (fields != null || expr == null) { 084 return; 085 } 086 SchemaManager schemaManager = Framework.getService(SchemaManager.class); 087 Set<String> fields = new HashSet<>(); 088 Set<String> schemas = new HashSet<>(); 089 090 for (String s : expr.split("[ \t\n\r,]")) { 091 if (s.isEmpty()) { 092 continue; 093 } 094 s = canonicalXPath(s); 095 096 // maybe a schema? 097 Schema schema = schemaManager.getSchema(s); 098 if (schema != null) { 099 schemas.add(s); 100 continue; 101 } 102 103 // isolate first field of property if complex 104 String[] props = s.split("/"); 105 if (props.length == 0) { 106 continue; 107 } 108 String prop = props[0]; 109 List<String> complex = Arrays.asList(props).subList(1, props.length); 110 111 // get the field 112 List<String> parts = new LinkedList<>(); 113 Field field; 114 int i = prop.indexOf('.'); 115 if (i != -1) { 116 // try schemaName.fieldName 117 String schemaName = prop.substring(0, i); 118 String fieldName = prop.substring(i + 1); 119 schema = schemaManager.getSchema(schemaName); 120 field = schema == null ? null : schema.getField(fieldName); 121 } else { 122 // otherwise try prefixed name 123 field = schemaManager.getField(prop); 124 // TODO must deal with prefix-less props like "size" 125 } 126 if (field == null) { 127 logNotFound(s); 128 continue; 129 } 130 parts.add(field.getName().getPrefixedName()); 131 132 // we have a field, add its schema 133 schemas.add(field.getDeclaringType().getName()); 134 135 // got a field, check its complex properties 136 for (String t : complex) { 137 Type fieldType = field.getType(); 138 if (fieldType.isComplexType()) { 139 // complex type, get subfield 140 ComplexType fieldComplexType = (ComplexType) fieldType; 141 field = fieldComplexType.getField(t); 142 parts.add(t); 143 continue; 144 } else if (fieldType.isListType()) { 145 ListType listType = (ListType) fieldType; 146 if (!listType.getFieldType().isSimpleType()) { 147 // complex list 148 // should be * or an integer 149 if (!STAR_OR_DIGITS.matcher(t).matches()) { 150 field = null; 151 break; 152 } 153 field = listType.getField(); 154 parts.add("*"); 155 } else { 156 // array, cannot have subproperties 157 field = null; 158 break; 159 } 160 } else { 161 // primitive type, cannot have subproperties 162 field = null; 163 break; 164 } 165 } 166 if (field == null) { 167 logNotFound(s); 168 continue; 169 } 170 if (!isScalarField(field)) { 171 log.error("Prefetch field '" + s + "' is not scalar"); 172 continue; 173 } 174 fields.add(StringUtils.join(parts, '/')); 175 } 176 177 this.fields = fields.toArray(new String[fields.size()]); 178 this.schemas = schemas.toArray(new String[schemas.size()]); 179 } 180 181 /** 182 * Checks if a field is a primitive type or array. 183 */ 184 private static boolean isScalarField(Field field) { 185 Type fieldType = field.getType(); 186 if (fieldType.isComplexType()) { 187 // complex type 188 return false; 189 } 190 if (!fieldType.isListType()) { 191 // primitive type 192 return true; 193 } 194 // array or complex list? 195 return ((ListType) fieldType).getFieldType().isSimpleType(); 196 } 197 198 private static void logNotFound(String s) { 199 log.error("Prefetch field or schema '" + s + "' not found"); 200 } 201 202}