You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

FieldGroup.java 42KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263
  1. /*
  2. * Copyright 2000-2016 Vaadin Ltd.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  5. * use this file except in compliance with the License. You may obtain a copy of
  6. * the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  12. * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  13. * License for the specific language governing permissions and limitations under
  14. * the License.
  15. */
  16. package com.vaadin.v7.data.fieldgroup;
  17. import java.io.Serializable;
  18. import java.lang.reflect.InvocationTargetException;
  19. import java.util.ArrayList;
  20. import java.util.Collection;
  21. import java.util.Collections;
  22. import java.util.HashMap;
  23. import java.util.LinkedHashMap;
  24. import java.util.List;
  25. import java.util.Map;
  26. import com.vaadin.annotations.PropertyId;
  27. import com.vaadin.util.ReflectTools;
  28. import com.vaadin.v7.data.Item;
  29. import com.vaadin.v7.data.Property;
  30. import com.vaadin.v7.data.Validator.InvalidValueException;
  31. import com.vaadin.v7.data.util.TransactionalPropertyWrapper;
  32. import com.vaadin.v7.ui.AbstractField;
  33. import com.vaadin.v7.ui.DefaultFieldFactory;
  34. import com.vaadin.v7.ui.Field;
  35. /**
  36. * FieldGroup provides an easy way of binding fields to data and handling
  37. * commits of these fields.
  38. * <p>
  39. * The typical use case is to create a layout outside the FieldGroup and then
  40. * use FieldGroup to bind the fields to a data source.
  41. * </p>
  42. * <p>
  43. * {@link FieldGroup} is not a UI component so it cannot be added to a layout.
  44. * Using the buildAndBind methods {@link FieldGroup} can create fields for you
  45. * using a FieldGroupFieldFactory but you still have to add them to the correct
  46. * position in your layout.
  47. * </p>
  48. *
  49. * @author Vaadin Ltd
  50. * @since 7.0
  51. */
  52. @Deprecated
  53. public class FieldGroup implements Serializable {
  54. private Item itemDataSource;
  55. private boolean buffered = true;
  56. private boolean enabled = true;
  57. private boolean readOnly = false;
  58. private HashMap<Object, Field<?>> propertyIdToField = new HashMap<>();
  59. private LinkedHashMap<Field<?>, Object> fieldToPropertyId = new LinkedHashMap<>();
  60. private List<CommitHandler> commitHandlers = new ArrayList<>();
  61. /**
  62. * The field factory used by builder methods.
  63. */
  64. private FieldGroupFieldFactory fieldFactory = DefaultFieldGroupFieldFactory
  65. .get();
  66. /**
  67. * Constructs a field binder. Use {@link #setItemDataSource(Item)} to set a
  68. * data source for the field binder.
  69. *
  70. */
  71. public FieldGroup() {
  72. }
  73. /**
  74. * Constructs a field binder that uses the given data source.
  75. *
  76. * @param itemDataSource
  77. * The data source to bind the fields to
  78. */
  79. public FieldGroup(Item itemDataSource) {
  80. setItemDataSource(itemDataSource);
  81. }
  82. /**
  83. * Updates the item that is used by this FieldBinder. Rebinds all fields to
  84. * the properties in the new item.
  85. *
  86. * @param itemDataSource
  87. * The new item to use
  88. */
  89. public void setItemDataSource(Item itemDataSource) {
  90. this.itemDataSource = itemDataSource;
  91. for (Field<?> f : fieldToPropertyId.keySet()) {
  92. bind(f, fieldToPropertyId.get(f));
  93. }
  94. }
  95. /**
  96. * Gets the item used by this FieldBinder. Note that you must call
  97. * {@link #commit()} for the item to be updated unless buffered mode has
  98. * been switched off.
  99. *
  100. * @see #setBuffered(boolean)
  101. * @see #commit()
  102. *
  103. * @return The item used by this FieldBinder
  104. */
  105. public Item getItemDataSource() {
  106. return itemDataSource;
  107. }
  108. /**
  109. * Checks the buffered mode for the bound fields.
  110. * <p>
  111. *
  112. * @see #setBuffered(boolean) for more details on buffered mode
  113. *
  114. * @see Field#isBuffered()
  115. * @return true if buffered mode is on, false otherwise
  116. *
  117. */
  118. public boolean isBuffered() {
  119. return buffered;
  120. }
  121. /**
  122. * Sets the buffered mode for the bound fields.
  123. * <p>
  124. * When buffered mode is on the item will not be updated until
  125. * {@link #commit()} is called. If buffered mode is off the item will be
  126. * updated once the fields are updated.
  127. * </p>
  128. * <p>
  129. * The default is to use buffered mode.
  130. * </p>
  131. *
  132. * @see Field#setBuffered(boolean)
  133. * @param buffered
  134. * true to turn on buffered mode, false otherwise
  135. */
  136. public void setBuffered(boolean buffered) {
  137. if (buffered == this.buffered) {
  138. return;
  139. }
  140. this.buffered = buffered;
  141. for (Field<?> field : getFields()) {
  142. field.setBuffered(buffered);
  143. }
  144. }
  145. /**
  146. * Returns the enabled status for the fields.
  147. * <p>
  148. * Note that this will not accurately represent the enabled status of all
  149. * fields if you change the enabled status of the fields through some other
  150. * method than {@link #setEnabled(boolean)}.
  151. *
  152. * @return true if the fields are enabled, false otherwise
  153. */
  154. public boolean isEnabled() {
  155. return enabled;
  156. }
  157. /**
  158. * Updates the enabled state of all bound fields.
  159. *
  160. * @param fieldsEnabled
  161. * true to enable all bound fields, false to disable them
  162. */
  163. public void setEnabled(boolean fieldsEnabled) {
  164. enabled = fieldsEnabled;
  165. for (Field<?> field : getFields()) {
  166. field.setEnabled(fieldsEnabled);
  167. }
  168. }
  169. /**
  170. * Returns the read only status that is used by default with all fields that
  171. * have a writable data source.
  172. * <p>
  173. * Note that this will not accurately represent the read only status of all
  174. * fields if you change the read only status of the fields through some
  175. * other method than {@link #setReadOnly(boolean)}.
  176. *
  177. * @return true if the fields are set to read only, false otherwise
  178. */
  179. public boolean isReadOnly() {
  180. return readOnly;
  181. }
  182. /**
  183. * Sets the read only state to the given value for all fields with writable
  184. * data source. Fields with read only data source will always be set to read
  185. * only.
  186. *
  187. * @param fieldsReadOnly
  188. * true to set the fields with writable data source to read only,
  189. * false to set them to read write
  190. */
  191. public void setReadOnly(boolean fieldsReadOnly) {
  192. readOnly = fieldsReadOnly;
  193. for (Field<?> field : getFields()) {
  194. if (field.getPropertyDataSource() == null
  195. || !field.getPropertyDataSource().isReadOnly()) {
  196. field.setReadOnly(fieldsReadOnly);
  197. } else {
  198. field.setReadOnly(true);
  199. }
  200. }
  201. }
  202. /**
  203. * Returns a collection of all fields that have been bound.
  204. * <p>
  205. * The fields are not returned in any specific order.
  206. * </p>
  207. *
  208. * @return A collection with all bound Fields
  209. */
  210. public Collection<Field<?>> getFields() {
  211. return fieldToPropertyId.keySet();
  212. }
  213. /**
  214. * Binds the field with the given propertyId from the current item. If an
  215. * item has not been set then the binding is postponed until the item is set
  216. * using {@link #setItemDataSource(Item)}.
  217. * <p>
  218. * This method also adds validators when applicable.
  219. * </p>
  220. *
  221. * @param field
  222. * The field to bind
  223. * @param propertyId
  224. * The propertyId to bind to the field
  225. * @throws BindException
  226. * If the field is null or the property id is already bound to
  227. * another field by this field binder
  228. */
  229. public void bind(Field<?> field, Object propertyId) throws BindException {
  230. throwIfFieldIsNull(field, propertyId);
  231. throwIfPropertyIdAlreadyBound(field, propertyId);
  232. fieldToPropertyId.put(field, propertyId);
  233. propertyIdToField.put(propertyId, field);
  234. if (itemDataSource == null) {
  235. // Clear any possible existing binding to clear the field
  236. field.setPropertyDataSource(null);
  237. boolean fieldReadOnly = field.isReadOnly();
  238. if (!fieldReadOnly) {
  239. field.clear();
  240. } else {
  241. // Temporarily make the field read-write so we can clear the
  242. // value. Needed because setPropertyDataSource(null) does not
  243. // currently clear the field
  244. // (https://dev.vaadin.com/ticket/14733)
  245. field.setReadOnly(false);
  246. field.clear();
  247. field.setReadOnly(true);
  248. }
  249. // Will be bound when data source is set
  250. return;
  251. }
  252. field.setPropertyDataSource(
  253. wrapInTransactionalProperty(getItemProperty(propertyId)));
  254. configureField(field);
  255. }
  256. /**
  257. * Wrap property to transactional property.
  258. */
  259. protected <T> Property.Transactional<T> wrapInTransactionalProperty(
  260. Property<T> itemProperty) {
  261. return new TransactionalPropertyWrapper<>(itemProperty);
  262. }
  263. private void throwIfFieldIsNull(Field<?> field, Object propertyId) {
  264. if (field == null) {
  265. throw new BindException(String.format(
  266. "Cannot bind property id '%s' to a null field.",
  267. propertyId));
  268. }
  269. }
  270. private void throwIfPropertyIdAlreadyBound(Field<?> field,
  271. Object propertyId) {
  272. if (propertyIdToField.containsKey(propertyId)
  273. && propertyIdToField.get(propertyId) != field) {
  274. throw new BindException("Property id " + propertyId
  275. + " is already bound to another field");
  276. }
  277. }
  278. /**
  279. * Gets the property with the given property id from the item.
  280. *
  281. * @param propertyId
  282. * The id if the property to find
  283. * @return The property with the given id from the item
  284. * @throws BindException
  285. * If the property was not found in the item or no item has been
  286. * set
  287. */
  288. protected Property getItemProperty(Object propertyId) throws BindException {
  289. Item item = getItemDataSource();
  290. if (item == null) {
  291. throw new BindException("Could not lookup property with id "
  292. + propertyId + " as no item has been set");
  293. }
  294. Property<?> p = item.getItemProperty(propertyId);
  295. if (p == null) {
  296. throw new BindException("A property with id " + propertyId
  297. + " was not found in the item");
  298. }
  299. return p;
  300. }
  301. /**
  302. * Detaches the field from its property id and removes it from this
  303. * FieldBinder.
  304. * <p>
  305. * Note that the field is not detached from its property data source if it
  306. * is no longer connected to the same property id it was bound to using this
  307. * FieldBinder.
  308. *
  309. * @param field
  310. * The field to detach
  311. * @throws BindException
  312. * If the field is not bound by this field binder or not bound
  313. * to the correct property id
  314. */
  315. public void unbind(Field<?> field) throws BindException {
  316. Object propertyId = fieldToPropertyId.get(field);
  317. if (propertyId == null) {
  318. throw new BindException(
  319. "The given field is not part of this FieldBinder");
  320. }
  321. TransactionalPropertyWrapper<?> wrapper = null;
  322. Property fieldDataSource = field.getPropertyDataSource();
  323. if (fieldDataSource instanceof TransactionalPropertyWrapper) {
  324. wrapper = (TransactionalPropertyWrapper<?>) fieldDataSource;
  325. fieldDataSource = ((TransactionalPropertyWrapper<?>) fieldDataSource)
  326. .getWrappedProperty();
  327. }
  328. if (getItemDataSource() != null
  329. && fieldDataSource == getItemProperty(propertyId)) {
  330. if (null != wrapper) {
  331. wrapper.detachFromProperty();
  332. }
  333. field.setPropertyDataSource(null);
  334. }
  335. fieldToPropertyId.remove(field);
  336. propertyIdToField.remove(propertyId);
  337. }
  338. /**
  339. * Configures a field with the settings set for this FieldBinder.
  340. * <p>
  341. * By default this updates the buffered, read only and enabled state of the
  342. * field. Also adds validators when applicable. Fields with read only data
  343. * source are always configured as read only.
  344. *
  345. * @param field
  346. * The field to update
  347. */
  348. protected void configureField(Field<?> field) {
  349. field.setBuffered(isBuffered());
  350. field.setEnabled(isEnabled());
  351. if (field.getPropertyDataSource().isReadOnly()) {
  352. field.setReadOnly(true);
  353. } else {
  354. field.setReadOnly(isReadOnly());
  355. }
  356. }
  357. /**
  358. * Gets the type of the property with the given property id.
  359. *
  360. * @param propertyId
  361. * The propertyId. Must be find
  362. * @return The type of the property
  363. */
  364. protected Class<?> getPropertyType(Object propertyId) throws BindException {
  365. if (getItemDataSource() == null) {
  366. throw new BindException("Property type for '" + propertyId
  367. + "' could not be determined. No item data source has been set.");
  368. }
  369. Property<?> p = getItemDataSource().getItemProperty(propertyId);
  370. if (p == null) {
  371. throw new BindException("Property type for '" + propertyId
  372. + "' could not be determined. No property with that id was found.");
  373. }
  374. return p.getType();
  375. }
  376. /**
  377. * Returns a collection of all property ids that have been bound to fields.
  378. * <p>
  379. * Note that this will return property ids even before the item has been
  380. * set. In that case it returns the property ids that will be bound once the
  381. * item is set.
  382. * </p>
  383. * <p>
  384. * No guarantee is given for the order of the property ids
  385. * </p>
  386. *
  387. * @return A collection of bound property ids
  388. */
  389. public Collection<Object> getBoundPropertyIds() {
  390. return Collections.unmodifiableCollection(propertyIdToField.keySet());
  391. }
  392. /**
  393. * Returns a collection of all property ids that exist in the item set using
  394. * {@link #setItemDataSource(Item)} but have not been bound to fields.
  395. * <p>
  396. * Will always return an empty collection before an item has been set using
  397. * {@link #setItemDataSource(Item)}.
  398. * </p>
  399. * <p>
  400. * No guarantee is given for the order of the property ids
  401. * </p>
  402. *
  403. * @return A collection of property ids that have not been bound to fields
  404. */
  405. public Collection<Object> getUnboundPropertyIds() {
  406. if (getItemDataSource() == null) {
  407. return new ArrayList<>();
  408. }
  409. List<Object> unboundPropertyIds = new ArrayList<>();
  410. unboundPropertyIds.addAll(getItemDataSource().getItemPropertyIds());
  411. unboundPropertyIds.removeAll(propertyIdToField.keySet());
  412. return unboundPropertyIds;
  413. }
  414. /**
  415. * Commits all changes done to the bound fields.
  416. * <p>
  417. * Calls all {@link CommitHandler}s before and after committing the field
  418. * changes to the item data source. The whole commit is aborted and state is
  419. * restored to what it was before commit was called if any
  420. * {@link CommitHandler} throws a CommitException or there is a problem
  421. * committing the fields
  422. *
  423. * @throws CommitException
  424. * If the commit was aborted
  425. */
  426. public void commit() throws CommitException {
  427. if (!isBuffered()) {
  428. // Not using buffered mode, nothing to do
  429. return;
  430. }
  431. startTransactions();
  432. try {
  433. firePreCommitEvent();
  434. Map<Field<?>, InvalidValueException> invalidValueExceptions = commitFields();
  435. if (invalidValueExceptions.isEmpty()) {
  436. firePostCommitEvent();
  437. commitTransactions();
  438. } else {
  439. throw new FieldGroupInvalidValueException(
  440. invalidValueExceptions);
  441. }
  442. } catch (Exception e) {
  443. rollbackTransactions();
  444. throw new CommitException("Commit failed", this, e);
  445. }
  446. }
  447. /**
  448. * Tries to commit all bound fields one by one and gathers any validation
  449. * exceptions in a map, which is returned to the caller
  450. *
  451. * @return a propertyId to validation exception map which is empty if all
  452. * commits succeeded
  453. */
  454. private Map<Field<?>, InvalidValueException> commitFields() {
  455. Map<Field<?>, InvalidValueException> invalidValueExceptions = new HashMap<>();
  456. for (Field<?> f : fieldToPropertyId.keySet()) {
  457. try {
  458. f.commit();
  459. } catch (InvalidValueException e) {
  460. invalidValueExceptions.put(f, e);
  461. }
  462. }
  463. return invalidValueExceptions;
  464. }
  465. /**
  466. * Exception which wraps InvalidValueExceptions from all invalid fields in a
  467. * FieldGroup
  468. *
  469. * @since 7.4
  470. */
  471. @Deprecated
  472. public static class FieldGroupInvalidValueException
  473. extends InvalidValueException {
  474. private Map<Field<?>, InvalidValueException> invalidValueExceptions;
  475. /**
  476. * Constructs a new exception with the specified validation exceptions.
  477. *
  478. * @param invalidValueExceptions
  479. * a property id to exception map
  480. */
  481. public FieldGroupInvalidValueException(
  482. Map<Field<?>, InvalidValueException> invalidValueExceptions) {
  483. super(null, invalidValueExceptions.values().toArray(
  484. new InvalidValueException[invalidValueExceptions.size()]));
  485. this.invalidValueExceptions = invalidValueExceptions;
  486. }
  487. /**
  488. * Returns a map containing fields which failed validation and the
  489. * exceptions the corresponding validators threw.
  490. *
  491. * @return a map with all the invalid value exceptions
  492. */
  493. public Map<Field<?>, InvalidValueException> getInvalidFields() {
  494. return invalidValueExceptions;
  495. }
  496. }
  497. private void startTransactions() throws CommitException {
  498. for (Field<?> f : fieldToPropertyId.keySet()) {
  499. Property.Transactional<?> property = (Property.Transactional<?>) f
  500. .getPropertyDataSource();
  501. if (property == null) {
  502. throw new CommitException(
  503. "Property \"" + fieldToPropertyId.get(f)
  504. + "\" not bound to datasource.");
  505. }
  506. property.startTransaction();
  507. }
  508. }
  509. private void commitTransactions() {
  510. for (Field<?> f : fieldToPropertyId.keySet()) {
  511. ((Property.Transactional<?>) f.getPropertyDataSource()).commit();
  512. }
  513. }
  514. private void rollbackTransactions() {
  515. for (Field<?> f : fieldToPropertyId.keySet()) {
  516. try {
  517. ((Property.Transactional<?>) f.getPropertyDataSource())
  518. .rollback();
  519. } catch (Exception rollbackException) {
  520. // FIXME: What to do ?
  521. }
  522. }
  523. }
  524. /**
  525. * Sends a preCommit event to all registered commit handlers
  526. *
  527. * @throws CommitException
  528. * If the commit should be aborted
  529. */
  530. private void firePreCommitEvent() throws CommitException {
  531. CommitHandler[] handlers = commitHandlers
  532. .toArray(new CommitHandler[commitHandlers.size()]);
  533. for (CommitHandler handler : handlers) {
  534. handler.preCommit(new CommitEvent(this));
  535. }
  536. }
  537. /**
  538. * Sends a postCommit event to all registered commit handlers
  539. *
  540. * @throws CommitException
  541. * If the commit should be aborted
  542. */
  543. private void firePostCommitEvent() throws CommitException {
  544. CommitHandler[] handlers = commitHandlers
  545. .toArray(new CommitHandler[commitHandlers.size()]);
  546. for (CommitHandler handler : handlers) {
  547. handler.postCommit(new CommitEvent(this));
  548. }
  549. }
  550. /**
  551. * Discards all changes done to the bound fields.
  552. * <p>
  553. * Only has effect if buffered mode is used.
  554. *
  555. */
  556. public void discard() {
  557. for (Field<?> f : fieldToPropertyId.keySet()) {
  558. try {
  559. f.discard();
  560. } catch (Exception e) {
  561. // TODO: handle exception
  562. // What can we do if discard fails other than try to discard all
  563. // other fields?
  564. }
  565. }
  566. }
  567. /**
  568. * Returns the field that is bound to the given property id
  569. *
  570. * @param propertyId
  571. * The property id to use to lookup the field
  572. * @return The field that is bound to the property id or null if no field is
  573. * bound to that property id
  574. */
  575. public Field<?> getField(Object propertyId) {
  576. return propertyIdToField.get(propertyId);
  577. }
  578. /**
  579. * Returns the property id that is bound to the given field
  580. *
  581. * @param field
  582. * The field to use to lookup the property id
  583. * @return The property id that is bound to the field or null if the field
  584. * is not bound to any property id by this FieldBinder
  585. */
  586. public Object getPropertyId(Field<?> field) {
  587. return fieldToPropertyId.get(field);
  588. }
  589. /**
  590. * Adds a commit handler.
  591. * <p>
  592. * The commit handler is called before the field values are committed to the
  593. * item ( {@link CommitHandler#preCommit(CommitEvent)}) and after the item
  594. * has been updated ({@link CommitHandler#postCommit(CommitEvent)}). If a
  595. * {@link CommitHandler} throws a CommitException the whole commit is
  596. * aborted and the fields retain their old values.
  597. *
  598. * @param commitHandler
  599. * The commit handler to add
  600. */
  601. public void addCommitHandler(CommitHandler commitHandler) {
  602. commitHandlers.add(commitHandler);
  603. }
  604. /**
  605. * Removes the given commit handler.
  606. *
  607. * @see #addCommitHandler(CommitHandler)
  608. *
  609. * @param commitHandler
  610. * The commit handler to remove
  611. */
  612. public void removeCommitHandler(CommitHandler commitHandler) {
  613. commitHandlers.remove(commitHandler);
  614. }
  615. /**
  616. * Returns a list of all commit handlers for this {@link FieldGroup}.
  617. * <p>
  618. * Use {@link #addCommitHandler(CommitHandler)} and
  619. * {@link #removeCommitHandler(CommitHandler)} to register or unregister a
  620. * commit handler.
  621. *
  622. * @return A collection of commit handlers
  623. */
  624. protected Collection<CommitHandler> getCommitHandlers() {
  625. return Collections.unmodifiableCollection(commitHandlers);
  626. }
  627. /**
  628. * CommitHandlers are used by {@link FieldGroup#commit()} as part of the
  629. * commit transactions. CommitHandlers can perform custom operations as part
  630. * of the commit and cause the commit to be aborted by throwing a
  631. * {@link CommitException}.
  632. */
  633. @Deprecated
  634. public interface CommitHandler extends Serializable {
  635. /**
  636. * Called before changes are committed to the field and the item is
  637. * updated.
  638. * <p>
  639. * Throw a {@link CommitException} to abort the commit.
  640. *
  641. * @param commitEvent
  642. * An event containing information regarding the commit
  643. * @throws CommitException
  644. * if the commit should be aborted
  645. */
  646. public void preCommit(CommitEvent commitEvent) throws CommitException;
  647. /**
  648. * Called after changes are committed to the fields and the item is
  649. * updated.
  650. * <p>
  651. * Throw a {@link CommitException} to abort the commit.
  652. *
  653. * @param commitEvent
  654. * An event containing information regarding the commit
  655. * @throws CommitException
  656. * if the commit should be aborted
  657. */
  658. public void postCommit(CommitEvent commitEvent) throws CommitException;
  659. }
  660. /**
  661. * FIXME javadoc
  662. *
  663. */
  664. @Deprecated
  665. public static class CommitEvent implements Serializable {
  666. private FieldGroup fieldBinder;
  667. private CommitEvent(FieldGroup fieldBinder) {
  668. this.fieldBinder = fieldBinder;
  669. }
  670. /**
  671. * Returns the field binder that this commit relates to
  672. *
  673. * @return The FieldBinder that is being committed.
  674. */
  675. public FieldGroup getFieldBinder() {
  676. return fieldBinder;
  677. }
  678. }
  679. /**
  680. * Checks the validity of the bound fields.
  681. * <p>
  682. * Call the {@link Field#validate()} for the fields to get the individual
  683. * error messages.
  684. *
  685. * @return true if all bound fields are valid, false otherwise.
  686. */
  687. public boolean isValid() {
  688. try {
  689. for (Field<?> field : getFields()) {
  690. field.validate();
  691. }
  692. return true;
  693. } catch (InvalidValueException e) {
  694. return false;
  695. }
  696. }
  697. /**
  698. * Checks if any bound field has been modified.
  699. *
  700. * @return true if at least one field has been modified, false otherwise
  701. */
  702. public boolean isModified() {
  703. for (Field<?> field : getFields()) {
  704. if (field.isModified()) {
  705. return true;
  706. }
  707. }
  708. return false;
  709. }
  710. /**
  711. * Gets the field factory for the {@link FieldGroup}. The field factory is
  712. * only used when {@link FieldGroup} creates a new field.
  713. *
  714. * @return The field factory in use
  715. *
  716. */
  717. public FieldGroupFieldFactory getFieldFactory() {
  718. return fieldFactory;
  719. }
  720. /**
  721. * Sets the field factory for the {@link FieldGroup}. The field factory is
  722. * only used when {@link FieldGroup} creates a new field.
  723. *
  724. * @param fieldFactory
  725. * The field factory to use
  726. */
  727. public void setFieldFactory(FieldGroupFieldFactory fieldFactory) {
  728. this.fieldFactory = fieldFactory;
  729. }
  730. /**
  731. * Binds member fields found in the given object.
  732. * <p>
  733. * This method processes all (Java) member fields whose type extends
  734. * {@link Field} and that can be mapped to a property id. Property id
  735. * mapping is done based on the field name or on a @{@link PropertyId}
  736. * annotation on the field. All non-null fields for which a property id can
  737. * be determined are bound to the property id.
  738. * </p>
  739. * <p>
  740. * For example:
  741. *
  742. * <pre>
  743. * public class MyForm extends VerticalLayout {
  744. * private TextField firstName = new TextField("First name");
  745. * &#64;PropertyId("last")
  746. * private TextField lastName = new TextField("Last name");
  747. * private TextField age = new TextField("Age"); ... }
  748. *
  749. * MyForm myForm = new MyForm();
  750. * ...
  751. * fieldGroup.bindMemberFields(myForm);
  752. * </pre>
  753. *
  754. * </p>
  755. * This binds the firstName TextField to a "firstName" property in the item,
  756. * lastName TextField to a "last" property and the age TextField to a "age"
  757. * property.
  758. *
  759. * @param objectWithMemberFields
  760. * The object that contains (Java) member fields to bind
  761. * @throws BindException
  762. * If there is a problem binding a field
  763. */
  764. public void bindMemberFields(Object objectWithMemberFields)
  765. throws BindException {
  766. buildAndBindMemberFields(objectWithMemberFields, false);
  767. }
  768. /**
  769. * Binds member fields found in the given object and builds member fields
  770. * that have not been initialized.
  771. * <p>
  772. * This method processes all (Java) member fields whose type extends
  773. * {@link Field} and that can be mapped to a property id. Property ids are
  774. * searched in the following order: @{@link PropertyId} annotations, exact
  775. * field name matches and the case-insensitive matching that ignores
  776. * underscores. Fields that are not initialized (null) are built using the
  777. * field factory. All non-null fields for which a property id can be
  778. * determined are bound to the property id.
  779. * </p>
  780. * <p>
  781. * For example:
  782. *
  783. * <pre>
  784. * public class MyForm extends VerticalLayout {
  785. * private TextField firstName = new TextField("First name");
  786. * &#64;PropertyId("last")
  787. * private TextField lastName = new TextField("Last name");
  788. * private TextField age;
  789. *
  790. * MyForm myForm = new MyForm();
  791. * ...
  792. * fieldGroup.buildAndBindMemberFields(myForm);
  793. * </pre>
  794. *
  795. * </p>
  796. * <p>
  797. * This binds the firstName TextField to a "firstName" property in the item,
  798. * lastName TextField to a "last" property and builds an age TextField using
  799. * the field factory and then binds it to the "age" property.
  800. * </p>
  801. *
  802. * @param objectWithMemberFields
  803. * The object that contains (Java) member fields to build and
  804. * bind
  805. * @throws BindException
  806. * If there is a problem binding or building a field
  807. */
  808. public void buildAndBindMemberFields(Object objectWithMemberFields)
  809. throws BindException {
  810. buildAndBindMemberFields(objectWithMemberFields, true);
  811. }
  812. /**
  813. * Binds member fields found in the given object and optionally builds
  814. * member fields that have not been initialized.
  815. * <p>
  816. * This method processes all (Java) member fields whose type extends
  817. * {@link Field} and that can be mapped to a property id. Property ids are
  818. * searched in the following order: @{@link PropertyId} annotations, exact
  819. * field name matches and the case-insensitive matching that ignores
  820. * underscores. Fields that are not initialized (null) are built using the
  821. * field factory is buildFields is true. All non-null fields for which a
  822. * property id can be determined are bound to the property id.
  823. * </p>
  824. *
  825. * @param objectWithMemberFields
  826. * The object that contains (Java) member fields to build and
  827. * bind
  828. * @throws BindException
  829. * If there is a problem binding or building a field
  830. */
  831. protected void buildAndBindMemberFields(Object objectWithMemberFields,
  832. boolean buildFields) throws BindException {
  833. Class<?> objectClass = objectWithMemberFields.getClass();
  834. for (java.lang.reflect.Field memberField : getFieldsInDeclareOrder(
  835. objectClass)) {
  836. if (!Field.class.isAssignableFrom(memberField.getType())) {
  837. // Process next field
  838. continue;
  839. }
  840. PropertyId propertyIdAnnotation = memberField
  841. .getAnnotation(PropertyId.class);
  842. Class<? extends Field> fieldType = (Class<? extends Field>) memberField
  843. .getType();
  844. Object propertyId = null;
  845. if (propertyIdAnnotation != null) {
  846. // @PropertyId(propertyId) always overrides property id
  847. propertyId = propertyIdAnnotation.value();
  848. } else {
  849. try {
  850. propertyId = findPropertyId(memberField);
  851. } catch (SearchException e) {
  852. // Property id was not found, skip this field
  853. continue;
  854. }
  855. if (propertyId == null) {
  856. // Property id was not found, skip this field
  857. continue;
  858. }
  859. }
  860. // Ensure that the property id exists
  861. Class<?> propertyType;
  862. try {
  863. propertyType = getPropertyType(propertyId);
  864. } catch (BindException e) {
  865. // Property id was not found, skip this field
  866. continue;
  867. }
  868. Field<?> field;
  869. try {
  870. // Get the field from the object
  871. field = (Field<?>) ReflectTools.getJavaFieldValue(
  872. objectWithMemberFields, memberField, Field.class);
  873. } catch (Exception e) {
  874. // If we cannot determine the value, just skip the field and try
  875. // the next one
  876. continue;
  877. }
  878. if (field == null && buildFields) {
  879. Caption captionAnnotation = memberField
  880. .getAnnotation(Caption.class);
  881. String caption;
  882. if (captionAnnotation != null) {
  883. caption = captionAnnotation.value();
  884. } else {
  885. caption = DefaultFieldFactory
  886. .createCaptionByPropertyId(propertyId);
  887. }
  888. // Create the component (LegacyField)
  889. field = build(caption, propertyType, fieldType);
  890. // Store it in the field
  891. try {
  892. ReflectTools.setJavaFieldValue(objectWithMemberFields,
  893. memberField, field);
  894. } catch (IllegalArgumentException e) {
  895. throw new BindException("Could not assign value to field '"
  896. + memberField.getName() + "'", e);
  897. } catch (IllegalAccessException e) {
  898. throw new BindException("Could not assign value to field '"
  899. + memberField.getName() + "'", e);
  900. } catch (InvocationTargetException e) {
  901. throw new BindException("Could not assign value to field '"
  902. + memberField.getName() + "'", e);
  903. }
  904. }
  905. if (field != null) {
  906. // Bind it to the property id
  907. bind(field, propertyId);
  908. }
  909. }
  910. }
  911. /**
  912. * Searches for a property id from the current itemDataSource that matches
  913. * the given memberField.
  914. * <p>
  915. * If perfect match is not found, uses a case insensitive search that also
  916. * ignores underscores. Returns null if no match is found. Throws a
  917. * SearchException if no item data source has been set.
  918. * </p>
  919. * <p>
  920. * The propertyId search logic used by
  921. * {@link #buildAndBindMemberFields(Object, boolean)
  922. * buildAndBindMemberFields} can easily be customized by overriding this
  923. * method. No other changes are needed.
  924. * </p>
  925. *
  926. * @param memberField
  927. * The field an object id is searched for
  928. * @return
  929. */
  930. protected Object findPropertyId(java.lang.reflect.Field memberField) {
  931. String fieldName = memberField.getName();
  932. if (getItemDataSource() == null) {
  933. throw new SearchException("Property id type for field '" + fieldName
  934. + "' could not be determined. No item data source has been set.");
  935. }
  936. Item dataSource = getItemDataSource();
  937. if (dataSource.getItemProperty(fieldName) != null) {
  938. return fieldName;
  939. } else {
  940. String minifiedFieldName = minifyFieldName(fieldName);
  941. for (Object itemPropertyId : dataSource.getItemPropertyIds()) {
  942. if (itemPropertyId instanceof String) {
  943. String itemPropertyName = (String) itemPropertyId;
  944. if (minifiedFieldName
  945. .equals(minifyFieldName(itemPropertyName))) {
  946. return itemPropertyName;
  947. }
  948. }
  949. }
  950. }
  951. return null;
  952. }
  953. protected static String minifyFieldName(String fieldName) {
  954. return fieldName.toLowerCase().replace("_", "");
  955. }
  956. /**
  957. * Exception thrown by a FieldGroup when the commit operation fails.
  958. *
  959. * Provides information about validation errors through
  960. * {@link #getInvalidFields()} if the cause of the failure is that all bound
  961. * fields did not pass validation
  962. *
  963. */
  964. @Deprecated
  965. public static class CommitException extends Exception {
  966. private FieldGroup fieldGroup;
  967. public CommitException() {
  968. super();
  969. }
  970. public CommitException(String message, FieldGroup fieldGroup,
  971. Throwable cause) {
  972. super(message, cause);
  973. this.fieldGroup = fieldGroup;
  974. }
  975. public CommitException(String message, Throwable cause) {
  976. super(message, cause);
  977. }
  978. public CommitException(String message) {
  979. super(message);
  980. }
  981. public CommitException(Throwable cause) {
  982. super(cause);
  983. }
  984. /**
  985. * Returns a map containing the fields which failed validation and the
  986. * exceptions the corresponding validators threw.
  987. *
  988. * @since 7.4
  989. * @return a map with all the invalid value exceptions. Can be empty but
  990. * not null
  991. */
  992. public Map<Field<?>, InvalidValueException> getInvalidFields() {
  993. if (getCause() instanceof FieldGroupInvalidValueException) {
  994. return ((FieldGroupInvalidValueException) getCause())
  995. .getInvalidFields();
  996. }
  997. return new HashMap<>();
  998. }
  999. /**
  1000. * Returns the field group where the exception occurred
  1001. *
  1002. * @since 7.4
  1003. * @return the field group
  1004. */
  1005. public FieldGroup getFieldGroup() {
  1006. return fieldGroup;
  1007. }
  1008. }
  1009. @Deprecated
  1010. public static class BindException extends RuntimeException {
  1011. public BindException(String message) {
  1012. super(message);
  1013. }
  1014. public BindException(String message, Throwable t) {
  1015. super(message, t);
  1016. }
  1017. }
  1018. @Deprecated
  1019. public static class SearchException extends RuntimeException {
  1020. public SearchException(String message) {
  1021. super(message);
  1022. }
  1023. public SearchException(String message, Throwable t) {
  1024. super(message, t);
  1025. }
  1026. }
  1027. /**
  1028. * Builds a field and binds it to the given property id using the field
  1029. * binder.
  1030. *
  1031. * @param propertyId
  1032. * The property id to bind to. Must be present in the field
  1033. * finder.
  1034. * @throws BindException
  1035. * If there is a problem while building or binding
  1036. * @return The created and bound field
  1037. */
  1038. public Field<?> buildAndBind(Object propertyId) throws BindException {
  1039. String caption = DefaultFieldFactory
  1040. .createCaptionByPropertyId(propertyId);
  1041. return buildAndBind(caption, propertyId);
  1042. }
  1043. /**
  1044. * Builds a field using the given caption and binds it to the given property
  1045. * id using the field binder.
  1046. *
  1047. * @param caption
  1048. * The caption for the field
  1049. * @param propertyId
  1050. * The property id to bind to. Must be present in the field
  1051. * finder.
  1052. * @throws BindException
  1053. * If there is a problem while building or binding
  1054. * @return The created and bound field. Can be any type of {@link Field}.
  1055. */
  1056. public Field<?> buildAndBind(String caption, Object propertyId)
  1057. throws BindException {
  1058. return buildAndBind(caption, propertyId, Field.class);
  1059. }
  1060. /**
  1061. * Builds a field using the given caption and binds it to the given property
  1062. * id using the field binder. Ensures the new field is of the given type.
  1063. *
  1064. * @param caption
  1065. * The caption for the field
  1066. * @param propertyId
  1067. * The property id to bind to. Must be present in the field
  1068. * finder.
  1069. * @throws BindException
  1070. * If the field could not be created
  1071. * @return The created and bound field. Can be any type of {@link Field}.
  1072. */
  1073. public <T extends Field> T buildAndBind(String caption, Object propertyId,
  1074. Class<T> fieldType) throws BindException {
  1075. Class<?> type = getPropertyType(propertyId);
  1076. T field = build(caption, type, fieldType);
  1077. bind(field, propertyId);
  1078. return field;
  1079. }
  1080. /**
  1081. * Creates a field based on the given data type.
  1082. * <p>
  1083. * The data type is the type that we want to edit using the field. The field
  1084. * type is the type of field we want to create, can be {@link Field} if any
  1085. * LegacyField is good.
  1086. * </p>
  1087. *
  1088. * @param caption
  1089. * The caption for the new field
  1090. * @param dataType
  1091. * The data model type that we want to edit using the field
  1092. * @param fieldType
  1093. * The type of field that we want to create
  1094. * @return A LegacyField capable of editing the given type
  1095. * @throws BindException
  1096. * If the field could not be created
  1097. */
  1098. protected <T extends Field> T build(String caption, Class<?> dataType,
  1099. Class<T> fieldType) throws BindException {
  1100. T field = getFieldFactory().createField(dataType, fieldType);
  1101. if (field == null) {
  1102. throw new BindException(
  1103. "Unable to build a field of type " + fieldType.getName()
  1104. + " for editing " + dataType.getName());
  1105. }
  1106. field.setCaption(caption);
  1107. return field;
  1108. }
  1109. /**
  1110. * Returns an array containing LegacyField objects reflecting all the fields
  1111. * of the class or interface represented by this Class object. The elements
  1112. * in the array returned are sorted in declare order from sub class to super
  1113. * class.
  1114. *
  1115. * @param searchClass
  1116. * @return
  1117. */
  1118. protected static List<java.lang.reflect.Field> getFieldsInDeclareOrder(
  1119. Class searchClass) {
  1120. ArrayList<java.lang.reflect.Field> memberFieldInOrder = new ArrayList<>();
  1121. while (searchClass != null) {
  1122. for (java.lang.reflect.Field memberField : searchClass
  1123. .getDeclaredFields()) {
  1124. memberFieldInOrder.add(memberField);
  1125. }
  1126. searchClass = searchClass.getSuperclass();
  1127. }
  1128. return memberFieldInOrder;
  1129. }
  1130. /**
  1131. * Clears the value of all fields.
  1132. *
  1133. * @since 7.4
  1134. */
  1135. public void clear() {
  1136. for (Field<?> f : getFields()) {
  1137. if (f instanceof AbstractField) {
  1138. ((AbstractField) f).clear();
  1139. }
  1140. }
  1141. }
  1142. }