package org.orienteer.core.service.impl; import com.google.common.base.Function; import com.google.common.base.Predicate; import com.google.common.base.Predicates; import com.google.common.collect.Collections2; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import com.google.common.collect.Ordering; import com.orientechnologies.common.collection.OCollection; import com.orientechnologies.orient.core.db.record.OIdentifiable; import com.orientechnologies.orient.core.id.ORID; import com.orientechnologies.orient.core.metadata.schema.OClass; import com.orientechnologies.orient.core.metadata.schema.OProperty; import com.orientechnologies.orient.core.metadata.schema.OSchema; import com.orientechnologies.orient.core.metadata.schema.OType; import com.orientechnologies.orient.core.metadata.security.OSecurityShared; import com.orientechnologies.orient.core.record.ORecord; import com.orientechnologies.orient.core.record.impl.ODocument; import org.apache.wicket.Application; import org.apache.wicket.extensions.markup.html.repeater.data.sort.SortOrder; import org.apache.wicket.extensions.markup.html.repeater.data.table.IColumn; import org.apache.wicket.extensions.markup.html.repeater.data.table.ISortableDataProvider; import org.apache.wicket.extensions.markup.html.repeater.util.SortableDataProvider; import org.apache.wicket.model.IModel; import org.apache.wicket.model.ResourceModel; import org.apache.wicket.util.string.Strings; import org.orienteer.core.CustomAttribute; import org.orienteer.core.OrienteerWebApplication; import org.orienteer.core.OrienteerWebSession; import org.orienteer.core.component.property.DisplayMode; import org.orienteer.core.component.table.CheckBoxColumn; import org.orienteer.core.component.table.ODocumentClassColumn; import org.orienteer.core.component.table.OEntityColumn; import org.orienteer.core.component.table.OPropertyValueColumn; import org.orienteer.core.component.table.OUnknownEntityColumn; import org.orienteer.core.component.visualizer.IVisualizer; import org.orienteer.core.component.visualizer.UIVisualizersRegistry; import org.orienteer.core.module.OrienteerLocalizationModule; import org.orienteer.core.service.IOClassIntrospector; import org.orienteer.core.util.CommonUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import ru.ydn.wicket.wicketorientdb.model.ODocumentLinksDataProvider; import ru.ydn.wicket.wicketorientdb.model.OQueryDataProvider; import ru.ydn.wicket.wicketorientdb.proto.OClassPrototyper; import ru.ydn.wicket.wicketorientdb.proto.OPropertyPrototyper; import ru.ydn.wicket.wicketorientdb.utils.ODocumentORIDConverter; import java.util.*; /** * Implementation of {@link IOClassIntrospector} */ public class OClassIntrospector implements IOClassIntrospector { private static final Logger LOG = LoggerFactory.getLogger(OClassIntrospector.class); /** * {@link OFilter} that checks displayable of an specified {@link OProperty} */ public static class PropertyDisplayablePredicate implements Predicate<OProperty> { public static final PropertyDisplayablePredicate INSTANCE = new PropertyDisplayablePredicate(); @Override public boolean apply(OProperty input) { Boolean value = CustomAttribute.DISPLAYABLE.getValue(input); return value!=null?value:false; } } /** * {@link Function} to take an order of an specified {@link OProperty} */ public static class GetOrderOfPropertyFunction implements Function<OProperty, Integer> { public static final GetOrderOfPropertyFunction INSTANCE = new GetOrderOfPropertyFunction(); @Override public Integer apply(OProperty input) { return CustomAttribute.ORDER.getValue(input); } } public static final Ordering<OProperty> ORDER_PROPERTIES_BY_ORDER = Ordering.<Integer>natural().nullsLast().onResultOf(GetOrderOfPropertyFunction.INSTANCE); @Override public List<OProperty> getDisplayableProperties(OClass oClass) { Collection<OProperty> properties = oClass.properties(); Collection<OProperty> filteredProperties = Collections2.filter(properties, PropertyDisplayablePredicate.INSTANCE); if(filteredProperties==null || filteredProperties.isEmpty()) filteredProperties = properties; return ORDER_PROPERTIES_BY_ORDER.sortedCopy(filteredProperties); } @Override public List<IColumn<ODocument, String>> getColumnsFor(OClass oClass, boolean withCheckbox, IModel<DisplayMode> modeModel) { List<IColumn<ODocument, String>> columns = new ArrayList<IColumn<ODocument,String>>(); if(oClass!=null) { List<OProperty> properties = getDisplayableProperties(oClass); if(withCheckbox) columns.add(new CheckBoxColumn<ODocument, ORID, String>(ODocumentORIDConverter.INSTANCE)); OProperty nameProperty = getNameProperty(oClass); OEntityColumn entityColumn = new OEntityColumn(nameProperty, true, modeModel); columns.add(entityColumn); if (!oClass.getSubclasses().isEmpty()) { columns.add(new ODocumentClassColumn<String>()); } for (OProperty oProperty : properties) { if(nameProperty==null || !nameProperty.equals(oProperty)) { Class<?> javaType = oProperty.getType().getDefaultJavaType(); if(javaType!=null && Comparable.class.isAssignableFrom(javaType)) { columns.add(new OPropertyValueColumn(oProperty.getName(), oProperty, modeModel)); } else { columns.add(new OPropertyValueColumn(oProperty, modeModel)); } } } } else { columns.add(new OUnknownEntityColumn(new ResourceModel("document.name"))); columns.add(new ODocumentClassColumn<String>()); } return columns; } @Override public List<ODocument> getNavigationPath(ODocument doc, boolean fromUpToDown) { List<ODocument> path = new ArrayList<ODocument>(); ODocument current = doc; boolean cycle; while(current!=null) { cycle = path.contains(current); path.add(current); if(cycle) break; current = getParent(current); } return fromUpToDown?Lists.reverse(path):path; } @Override public ODocument getParent(ODocument doc) { if(doc==null || doc.getSchemaClass()==null) return null; OClass oClass = doc.getSchemaClass(); OProperty parent = CustomAttribute.PROP_PARENT.getValue(oClass); if(parent!=null) { OType type = parent.getType(); Object value = doc.field(parent.getName()); if(value!=null) { switch (type) { case LINK: return ((OIdentifiable)value).getRecord(); case LINKLIST: case LINKBAG: case LINKSET: Collection<OIdentifiable> collection = (Collection<OIdentifiable>)value; return !collection.isEmpty()?(ODocument)collection.iterator().next().getRecord():null; case LINKMAP: Map<?, ?> map = (Map<?, ?>)value; value = map.isEmpty()?null:map.values().iterator().next(); return value instanceof OIdentifiable ? (ODocument)((OIdentifiable)value).getRecord():null; default: return null; } } } return null; } @Override public List<String> listTabs(OClass oClass) { Set<String> tabs = new HashSet<String>(); for(OProperty property: oClass.properties()) { String tab = CustomAttribute.TAB.getValue(property); if(tab==null) tab = DEFAULT_TAB; tabs.add(tab); } return new ArrayList<String>(tabs); } @Override @SuppressWarnings("unchecked") public List<OProperty> listProperties(OClass oClass, String tab, final Boolean extended) { final String safeTab = tab!=null?tab:DEFAULT_TAB; final UIVisualizersRegistry registry = OrienteerWebApplication.get().getUIVisualizersRegistry(); return listProperties(oClass, new Predicate<OProperty>() { @Override public boolean apply(OProperty input) { boolean ret = safeTab.equals(CustomAttribute.TAB.getValue(input, DEFAULT_TAB)); ret = ret && !CustomAttribute.HIDDEN.getValue(input, false); if(!ret || extended==null) return ret; else { String component = CustomAttribute.VISUALIZATION_TYPE.getValue(input); if(component==null) return !extended; IVisualizer visualizer = registry.getComponentFactory(input.getType(), component); return (visualizer!=null?visualizer.isExtended():false) == extended; } } }); } @Override public List<OProperty> listProperties(OClass oClass, Predicate<OProperty>... predicates) { if(oClass==null) return Collections.EMPTY_LIST; Collection<OProperty> properties = oClass.properties(); Predicate<OProperty> predicate = predicates==null || predicates.length==0? null :(predicates.length==1? predicates[0] :Predicates.and(predicates)); Collection<OProperty> filteredProperties = predicate!=null?Collections2.filter(properties, predicate):properties; return ORDER_PROPERTIES_BY_ORDER.sortedCopy(filteredProperties); } @Override public SortableDataProvider<ODocument, String> prepareDataProviderForProperty( OProperty property, IModel<ODocument> documentModel) { SortableDataProvider<ODocument, String> provider; if(CustomAttribute.CALCULABLE.getValue(property, false)) { String sql = CustomAttribute.CALC_SCRIPT.getValue(property); sql = sql.replace("?", ":doc"); provider = new OQueryDataProvider<ODocument>(sql).setParameter("doc", documentModel); } else { provider = new ODocumentLinksDataProvider(documentModel, property); } OClass linkedClass = property.getLinkedClass(); defineDefaultSorting(provider, linkedClass); return provider; } @Override public OProperty getNameProperty(OClass oClass) { if(oClass==null) return null; OProperty ret = CustomAttribute.PROP_NAME.getValue(oClass); if(ret!=null) return ret; ret = oClass.getProperty("name"); if(ret!=null) return ret; for(OProperty p: oClass.properties()) { if(!p.getType().isMultiValue()) { ret = p; if(OType.STRING.equals(p.getType())) break; } } return ret; } @Override public String getDocumentName(ODocument doc) { return getDocumentName(doc, null); } @Override public String getDocumentName(ODocument doc, OProperty nameProp) { if(doc==null) return Application.get().getResourceSettings().getLocalizer().getString("nodoc", null); else { if(nameProp==null) nameProp = getNameProperty(doc.getSchemaClass()); if(nameProp!=null) { Object value = doc.field(nameProp.getName()); if(value==null) return Application.get().getResourceSettings().getLocalizer().getString("noname", null); OType type = nameProp.getType(); Locale locale = OrienteerWebSession.get().getLocale(); switch (type) { case DATE: return OrienteerWebApplication.DATE_CONVERTER.convertToString((Date)value, locale); case DATETIME: return OrienteerWebApplication.DATE_TIME_CONVERTER.convertToString((Date)value, locale); case LINK: return value instanceof ODocument?getDocumentName((ODocument)value):null; case EMBEDDEDMAP: Map<String, Object> localizations = (Map<String, Object>)value; Object localized = CommonUtils.localizeByMap(localizations, true, locale.getLanguage(), Locale.getDefault().getLanguage()); if(localized!=null) return localized.toString(); default: return value.toString(); } } else { return doc.toString(); } } } @Override public OProperty virtualizeField(ODocument doc, String field) { OProperty property = OPropertyPrototyper.newPrototype(doc.getClassName()); property.setName(field); OType oType = doc.fieldType(field); if(oType==null) oType=OType.ANY; property.setType(oType); switch (oType) { case LINK: OIdentifiable link = doc.field(field); if(link!=null && link instanceof ODocument) property.setLinkedClass(((ODocument)link).getSchemaClass()); break; case LINKBAG: OCollection<OIdentifiable> bag = doc.field(field); if(bag!=null && bag.size()>0) { OIdentifiable linkIdentifiable = bag.iterator().next(); ORecord record = linkIdentifiable!=null?linkIdentifiable.getRecord():null; if(record!=null && record instanceof ODocument) property.setLinkedClass(((ODocument)record).getSchemaClass()); } break; case LINKLIST: case LINKSET: Collection<ODocument> collection = doc.field(field); if(collection!=null && !collection.isEmpty()) { link = collection.iterator().next(); if(link!=null && link instanceof ODocument) property.setLinkedClass(((ODocument)link).getSchemaClass()); } break; case LINKMAP: Map<String, ODocument> map = doc.field(field); if(map!=null && !map.isEmpty()) { link = map.values().iterator().next(); if(link!=null && link instanceof ODocument) property.setLinkedClass(((ODocument)link).getSchemaClass()); } break; case EMBEDDED: Object value = doc.field(field); OType linkedType = OType.getTypeByValue(value); if(OType.EMBEDDED.equals(linkedType)) property.setLinkedClass(((ODocument)value).getSchemaClass()); else property.setLinkedType(linkedType); break; case EMBEDDEDSET: case EMBEDDEDLIST: Collection<Object> objectCollection = doc.field(field); if(objectCollection!=null && !objectCollection.isEmpty()) { value = objectCollection.iterator().next(); property.setLinkedType(OType.getTypeByValue(value)); } break; case EMBEDDEDMAP: Map<String, Object> objectMap = doc.field(field); if(objectMap!=null && !objectMap.isEmpty()) { value = objectMap.values().iterator().next(); property.setLinkedType(OType.getTypeByValue(value)); } break; default: break; } return property; } @Override public void defineDefaultSorting(SortableDataProvider<ODocument, String> provider, OClass oClass) { if(oClass==null) return; OProperty property = CustomAttribute.SORT_BY.getValue(oClass); Boolean order = CustomAttribute.SORT_ORDER.getValue(oClass); SortOrder sortOrder = order==null?SortOrder.ASCENDING:(order?SortOrder.ASCENDING:SortOrder.DESCENDING); if(property==null) { if(order==null) provider.setSort(null); else provider.setSort("@rid", sortOrder); } else { provider.setSort(property.getName(), sortOrder); } } @Override public OQueryDataProvider<ODocument> getDataProviderForGenericSearch(OClass oClass, IModel<String> queryModel) { String searchSql = CustomAttribute.SEARCH_QUERY.getValue(oClass); String sql=null; if(!Strings.isEmpty(searchSql)) { String upper = searchSql.toUpperCase().trim(); if(upper.startsWith("SELECT")) sql = searchSql; else if(upper.startsWith("WHERE")) sql = "select from "+oClass.getName()+" "+searchSql; else { LOG.error("Unrecognized search sql: "+searchSql); } } if(sql==null) sql = "select from "+oClass.getName()+" where any() containstext :query"; return new OQueryDataProvider<ODocument>(sql).setParameter("query", queryModel); } }