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

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