| /***************************************************************** |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you 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 |
| * |
| * https://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 org.apache.cayenne.modeler.editor; |
| |
| import java.awt.BorderLayout; |
| import java.util.Arrays; |
| import java.util.Iterator; |
| |
| import javax.swing.DefaultComboBoxModel; |
| import javax.swing.JCheckBox; |
| import javax.swing.JTextField; |
| |
| import org.apache.cayenne.configuration.event.QueryEvent; |
| import org.apache.cayenne.exp.Expression; |
| import org.apache.cayenne.exp.ExpressionException; |
| import org.apache.cayenne.exp.parser.ASTPath; |
| import org.apache.cayenne.map.DataMap; |
| import org.apache.cayenne.map.Entity; |
| import org.apache.cayenne.map.ObjEntity; |
| import org.apache.cayenne.map.QueryDescriptor; |
| import org.apache.cayenne.map.SelectQueryDescriptor; |
| import org.apache.cayenne.query.SelectQuery; |
| import org.apache.cayenne.swing.components.JCayenneCheckBox; |
| import org.apache.cayenne.modeler.ProjectController; |
| import org.apache.cayenne.modeler.util.Comparators; |
| import org.apache.cayenne.modeler.util.ExpressionConvertor; |
| import org.apache.cayenne.modeler.util.TextAdapter; |
| import org.apache.cayenne.modeler.util.ValidatorTextAdapter; |
| import org.apache.cayenne.project.extension.info.ObjectInfo; |
| import org.apache.cayenne.util.CayenneMapEntry; |
| import org.apache.cayenne.util.Util; |
| import org.apache.cayenne.validation.ValidationException; |
| |
| import com.jgoodies.forms.builder.PanelBuilder; |
| import com.jgoodies.forms.layout.CellConstraints; |
| import com.jgoodies.forms.layout.FormLayout; |
| |
| /** |
| * A tabbed pane that contains editors for various SelectQuery parts. |
| * |
| */ |
| public class SelectQueryMainTab extends BaseQueryMainTab { |
| |
| protected TextAdapter comment; |
| protected TextAdapter qualifier; |
| protected JCheckBox distinct; |
| protected ObjectQueryPropertiesPanel properties; |
| |
| public SelectQueryMainTab(ProjectController mediator) { |
| super(mediator); |
| |
| initQueryRoot(); |
| initView(); |
| initController(); |
| } |
| |
| private void initView() { |
| // create widgets |
| name = new TextAdapter(new JTextField()) { |
| |
| @Override |
| protected void updateModel(String text) { |
| setQueryName(text); |
| } |
| }; |
| |
| qualifier = new ValidatorTextAdapter(new JTextField()) { |
| |
| @Override |
| protected void updateModel(String text) { |
| setQueryQualifier(text); |
| } |
| |
| @Override |
| protected void validate(String text) throws ValidationException { |
| createQualifier(text); |
| } |
| }; |
| |
| comment = new TextAdapter(new JTextField()) { |
| @Override |
| protected void updateModel(String text) { |
| setQueryComment(text); |
| } |
| }; |
| |
| distinct = new JCayenneCheckBox(); |
| |
| properties = new ObjectQueryPropertiesPanel(mediator); |
| |
| // assemble |
| CellConstraints cc = new CellConstraints(); |
| FormLayout layout = new FormLayout( |
| "right:max(80dlu;pref), 3dlu, fill:200dlu", |
| "p, 3dlu, p, 3dlu, p, 3dlu, p, 3dlu, p, 3dlu, p"); |
| PanelBuilder builder = new PanelBuilder(layout); |
| builder.setDefaultDialogBorder(); |
| |
| builder.addSeparator("SelectQuery Settings", cc.xywh(1, 1, 3, 1)); |
| builder.addLabel("Query Name:", cc.xy(1, 3)); |
| builder.add(name.getComponent(), cc.xy(3, 3)); |
| builder.addLabel("Query Root:", cc.xy(1, 5)); |
| builder.add(queryRoot, cc.xy(3, 5)); |
| builder.addLabel("Qualifier:", cc.xy(1, 7)); |
| builder.add(qualifier.getComponent(), cc.xy(3, 7)); |
| builder.addLabel("Distinct:", cc.xy(1, 9)); |
| builder.add(distinct, cc.xy(3, 9)); |
| builder.addLabel("Comment:", cc.xy(1, 11)); |
| builder.add(comment.getComponent(), cc.xy(3, 11)); |
| |
| this.setLayout(new BorderLayout()); |
| this.add(builder.getPanel(), BorderLayout.NORTH); |
| this.add(properties, BorderLayout.CENTER); |
| } |
| |
| private void initController() { |
| distinct.addItemListener(e -> { |
| QueryDescriptor query = getQuery(); |
| if (query != null) { |
| query.setProperty(SelectQuery.DISTINCT_PROPERTY, Boolean.toString(distinct.isSelected())); |
| mediator.fireQueryEvent(new QueryEvent(this, query)); |
| } |
| }); |
| } |
| |
| /** |
| * Updates the view from the current model state. Invoked when a currently displayed |
| * query is changed. |
| */ |
| void initFromModel() { |
| QueryDescriptor descriptor = mediator.getCurrentQuery(); |
| |
| if (descriptor == null || !QueryDescriptor.SELECT_QUERY.equals(descriptor.getType())) { |
| setVisible(false); |
| return; |
| } |
| |
| SelectQueryDescriptor query = (SelectQueryDescriptor) descriptor; |
| |
| name.setText(query.getName()); |
| distinct.setSelected(Boolean.valueOf(query.getProperties().get(SelectQuery.DISTINCT_PROPERTY))); |
| qualifier.setText(query.getQualifier() != null ? query |
| .getQualifier() |
| .toString() : null); |
| comment.setText(getQueryComment(query)); |
| |
| // init root choices and title label.. |
| |
| // - SelectQuery supports ObjEntity roots |
| |
| // TODO: now we only allow roots from the current map, |
| // since query root is fully resolved during map loading, |
| // making it impossible to reference other DataMaps. |
| |
| DataMap map = mediator.getCurrentDataMap(); |
| ObjEntity[] roots = map.getObjEntities().toArray(new ObjEntity[0]); |
| |
| if (roots.length > 1) { |
| Arrays.sort(roots, Comparators.getDataMapChildrenComparator()); |
| } |
| |
| DefaultComboBoxModel<ObjEntity> model = new DefaultComboBoxModel<>(roots); |
| model.setSelectedItem(query.getRoot()); |
| queryRoot.setModel(model); |
| |
| properties.initFromModel(query); |
| |
| setVisible(true); |
| } |
| |
| @Override |
| protected SelectQueryDescriptor getQuery() { |
| if(mediator.getCurrentQuery() == null) { |
| return null; |
| } |
| return QueryDescriptor.SELECT_QUERY.equals(mediator.getCurrentQuery().getType()) |
| ? (SelectQueryDescriptor) mediator.getCurrentQuery() |
| : null; |
| } |
| |
| /** |
| * Initializes Query qualifier from string. |
| */ |
| void setQueryQualifier(String text) { |
| if (text != null && text.trim().length() == 0) { |
| text = null; |
| } |
| |
| Expression qualifier = createQualifier(text); |
| |
| //getQuery() is not null if we reached here |
| getQuery().setQualifier((qualifier)); |
| mediator.fireQueryEvent(new QueryEvent(this, getQuery())); |
| } |
| |
| /** |
| * Method to create and check an expression |
| * @param text String to be converted as Expression |
| * @return Expression if a new expression was created, null otherwise. |
| * @throws ValidationException if <code>text</code> can't be converted |
| */ |
| Expression createQualifier(String text) throws ValidationException |
| { |
| SelectQueryDescriptor query = getQuery(); |
| if (query == null) { |
| return null; |
| } |
| |
| ExpressionConvertor convertor = new ExpressionConvertor(); |
| try { |
| String oldQualifier = convertor.valueAsString(query.getQualifier()); |
| if (!Util.nullSafeEquals(oldQualifier, text)) { |
| Expression exp = (Expression) convertor.stringAsValue(text); |
| |
| /* |
| * Advanced checking. See CAY-888 #1 |
| */ |
| if (query.getRoot() instanceof Entity) { |
| checkExpression((Entity) query.getRoot(), exp); |
| } |
| |
| return exp; |
| } |
| |
| return null; |
| } |
| catch (IllegalArgumentException ex) { |
| // unparsable qualifier |
| throw new ValidationException(ex.getMessage()); |
| } |
| } |
| |
| /** |
| * Advanced checking of an expression, needed because Expression.fromString() |
| * might terminate normally, but returned Expression will not be appliable |
| * for real Entities. |
| * Current implementation assures all attributes in expression are present in |
| * Entity |
| * @param root Root of a query |
| * @param ex Expression to check |
| * @throws ValidationException when something's wrong |
| */ |
| static void checkExpression(Entity root, Expression ex) throws ValidationException { |
| try { |
| if (ex instanceof ASTPath) { |
| /* |
| * Try to iterate through path, if some attributes are not present, |
| * exception will be raised |
| */ |
| |
| Iterator<CayenneMapEntry> path = root.resolvePathComponents(ex); |
| while (path.hasNext()) { |
| path.next(); |
| } |
| } |
| |
| if (ex != null) { |
| for (int i = 0; i < ex.getOperandCount(); i++) { |
| if (ex.getOperand(i) instanceof Expression) { |
| checkExpression(root, (Expression)ex.getOperand(i)); |
| } |
| } |
| } |
| } |
| catch (ExpressionException eex) { |
| throw new ValidationException(eex.getUnlabeledMessage()); |
| } |
| } |
| |
| private void setQueryComment(String text) { |
| QueryDescriptor query = getQuery(); |
| if (query == null) { |
| return; |
| } |
| ObjectInfo.putToMetaData(mediator.getApplication().getMetaData(), query, ObjectInfo.COMMENT, text); |
| mediator.fireQueryEvent(new QueryEvent(this, query)); |
| } |
| |
| private String getQueryComment(QueryDescriptor queryDescriptor) { |
| return ObjectInfo.getFromMetaData(mediator.getApplication().getMetaData(), queryDescriptor, ObjectInfo.COMMENT); |
| } |
| } |