001/* 002 * (C) Copyright 2006-2011 Nuxeo SA (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.ArrayList; 027import java.util.Arrays; 028import java.util.LinkedList; 029import java.util.List; 030import java.util.regex.Pattern; 031 032import org.apache.commons.lang.StringUtils; 033import org.apache.commons.logging.Log; 034import org.apache.commons.logging.LogFactory; 035import org.nuxeo.ecm.core.schema.types.ComplexType; 036import org.nuxeo.ecm.core.schema.types.Field; 037import org.nuxeo.ecm.core.schema.types.ListType; 038import org.nuxeo.ecm.core.schema.types.Schema; 039import org.nuxeo.ecm.core.schema.types.Type; 040import org.nuxeo.runtime.api.Framework; 041 042/** 043 * Information about what's to be prefetched: individual properties and whole schemas. 044 */ 045public class PrefetchInfo implements Serializable { 046 047 private static final long serialVersionUID = -6495547095819614741L; 048 049 private static final Log log = LogFactory.getLog(PrefetchInfo.class); 050 051 private final String expr; 052 053 private transient String[] fields; 054 055 private transient String[] schemas; 056 057 public PrefetchInfo(String expr) { 058 this.expr = expr; 059 } 060 061 public String[] getSchemas() { 062 parseExpression(); 063 return schemas; 064 } 065 066 public String[] getFields() { 067 parseExpression(); 068 return fields; 069 } 070 071 private static final Pattern STAR_OR_DIGITS = Pattern.compile("\\*|(\\d+)"); 072 073 private void parseExpression() { 074 if (fields != null || expr == null) { 075 return; 076 } 077 SchemaManager schemaManager = Framework.getService(SchemaManager.class); 078 List<String> fields = new ArrayList<String>(); 079 List<String> schemas = new ArrayList<String>(); 080 081 for (String s : expr.split("[ \t\n\r,]")) { 082 if (s.isEmpty()) { 083 continue; 084 } 085 s = canonicalXPath(s); 086 087 // maybe a schema? 088 Schema schema = schemaManager.getSchema(s); 089 if (schema != null) { 090 schemas.add(s); 091 continue; 092 } 093 094 // isolate first field of property if complex 095 String[] props = s.split("/"); 096 if (props.length == 0) { 097 continue; 098 } 099 String prop = props[0]; 100 List<String> complex = Arrays.asList(props).subList(1, props.length); 101 102 // get the field 103 List<String> parts = new LinkedList<String>(); 104 Field field; 105 int i = prop.indexOf('.'); 106 if (i != -1) { 107 // try schemaName.fieldName 108 String schemaName = prop.substring(0, i); 109 String fieldName = prop.substring(i + 1); 110 schema = schemaManager.getSchema(schemaName); 111 field = schema == null ? null : schema.getField(fieldName); 112 } else { 113 // otherwise try prefixed name 114 field = schemaManager.getField(prop); 115 // TODO must deal with prefix-less props like "size" 116 } 117 if (field == null) { 118 logNotFound(s); 119 continue; 120 } 121 parts.add(field.getName().getPrefixedName()); 122 123 // got a field, check its complex properties 124 for (String t : complex) { 125 Type fieldType = field.getType(); 126 if (fieldType.isComplexType()) { 127 // complex type, get subfield 128 ComplexType fieldComplexType = (ComplexType) fieldType; 129 field = fieldComplexType.getField(t); 130 parts.add(t); 131 continue; 132 } else if (fieldType.isListType()) { 133 ListType listType = (ListType) fieldType; 134 if (!listType.getFieldType().isSimpleType()) { 135 // complex list 136 // should be * or an integer 137 if (!STAR_OR_DIGITS.matcher(t).matches()) { 138 field = null; 139 break; 140 } 141 field = listType.getField(); 142 parts.add("*"); 143 } else { 144 // array, cannot have subproperties 145 field = null; 146 break; 147 } 148 } else { 149 // primitive type, cannot have subproperties 150 field = null; 151 break; 152 } 153 } 154 if (field == null) { 155 logNotFound(s); 156 continue; 157 } 158 if (!isScalarField(field)) { 159 log.error("Prefetch field '" + s + "' is not scalar"); 160 continue; 161 } 162 fields.add(StringUtils.join(parts, '/')); 163 } 164 165 this.fields = fields.toArray(new String[fields.size()]); 166 this.schemas = schemas.toArray(new String[schemas.size()]); 167 } 168 169 /** 170 * Checks if a field is a primitive type or array. 171 */ 172 private static boolean isScalarField(Field field) { 173 Type fieldType = field.getType(); 174 if (fieldType.isComplexType()) { 175 // complex type 176 return false; 177 } 178 if (!fieldType.isListType()) { 179 // primitive type 180 return true; 181 } 182 // array or complex list? 183 return ((ListType) fieldType).getFieldType().isSimpleType(); 184 } 185 186 private static void logNotFound(String s) { 187 log.error("Prefetch field or schema '" + s + "' not found"); 188 } 189 190}