diff --git a/laboratory/src/org/labkey/laboratory/LaboratoryContainerListener.java b/laboratory/src/org/labkey/laboratory/LaboratoryContainerListener.java index 43c87c5c..862d6872 100644 --- a/laboratory/src/org/labkey/laboratory/LaboratoryContainerListener.java +++ b/laboratory/src/org/labkey/laboratory/LaboratoryContainerListener.java @@ -68,10 +68,6 @@ public void containerCreated(Container c, User user) { _log.warn("Unable to populate default values for laboratory module", e); } - catch (BatchValidationException e) - { - //ignore, since this may just indicate the table already has these values - } } } @@ -112,69 +108,11 @@ public void propertyChange(PropertyChangeEvent evt) if (evt.getPropertyName().equals(ContainerManager.Property.Name.name())) { - if (evt instanceof ContainerManager.ContainerPropertyChangeEvent) - { - ContainerManager.ContainerPropertyChangeEvent ce = (ContainerManager.ContainerPropertyChangeEvent) evt; - if (ce.container.isWorkbook()) - { - TableInfo ti = LaboratorySchema.getInstance().getTable(LaboratorySchema.TABLE_WORKBOOKS); - TableSelector ts = new TableSelector(ti, new SimpleFilter(FieldKey.fromString(LaboratoryWorkbooksTable.WORKBOOK_COL), ce.container.getId()), null); - if (ts.exists()) - { - try - { - Integer rowId = Integer.parseInt(ce.container.getName()); - WorkbookModel w = ts.getObject(ce.container.getId(), WorkbookModel.class); - w.setWorkbookId(rowId); - Table.update(ce.user, ti, w, ce.container.getId()); - } - catch (NumberFormatException e) - { - - } - } - } - } + updateWorkbookTableOnNameChange(evt); } - else if (evt.getPropertyName().equals(ContainerManager.Property.Policy.name())) + else if (evt.getPropertyName().equals(ContainerManager.Property.Modules.name())) { - if (evt instanceof ContainerManager.ContainerPropertyChangeEvent) - { - ContainerManager.ContainerPropertyChangeEvent ce = (ContainerManager.ContainerPropertyChangeEvent)evt; - - User u = ce.user; - if (u == null && HttpView.hasCurrentView()) - u = HttpView.currentView().getViewContext().getUser(); - - if (u == null || !ce.container.hasPermission(u, InsertPermission.class)) - return; - - if (ce.container.getActiveModules().contains(ModuleLoader.getInstance().getModule(LaboratoryModule.class))) - { - try - { - LaboratoryManager.get().initWorkbooksForContainer(u, ce.container); - } - catch (Exception e) - { - _log.error("Unable to update laboratory workbooks table", e); - } - - //attempt to populate default values on load - try - { - LaboratoryManager.get().populateDefaultData(u, ce.container, null); - } - catch (IllegalArgumentException e) - { - _log.error("Unable to populate defaults in laboratory module tables", e); - } - catch (BatchValidationException e) - { - //ignore, since this may just indicate the table already has these values - } - } - } + possiblyInitializeOnActiveModuleChange(evt); } } @@ -194,4 +132,91 @@ protected void purgeTable(UserSchema userSchema, TableInfo table, Container c) super.purgeTable(userSchema, table, c); } } + + /** + * The container name field stores the workbookId as a string. If that name changes (and this should no longer be permitted + * for the most part in LK, we need to update this table + */ + private void updateWorkbookTableOnNameChange(PropertyChangeEvent evt) + { + if (!(evt instanceof ContainerManager.ContainerPropertyChangeEvent)) + { + return; + } + + ContainerManager.ContainerPropertyChangeEvent ce = (ContainerManager.ContainerPropertyChangeEvent) evt; + if (!ce.container.isWorkbook()) + { + return; + } + + TableInfo ti = LaboratorySchema.getInstance().getTable(LaboratorySchema.TABLE_WORKBOOKS); + TableSelector ts = new TableSelector(ti, new SimpleFilter(FieldKey.fromString(LaboratoryWorkbooksTable.WORKBOOK_COL), ce.container.getId()), null); + if (ts.exists()) + { + try + { + Integer workbookId = Integer.parseInt(ce.container.getName()); + WorkbookModel w = ts.getObject(ce.container.getId(), WorkbookModel.class); + w.setWorkbookId(workbookId); + Table.update(ce.user, ti, w, ce.container.getId()); + } + catch (NumberFormatException e) + { + _log.error("Non-numeric workbook name: " + ce.container.getName() + " for: " + ce.container.getEntityId()); + } + } + } + + /** + * The intent of this is to initialize the laboratory folder if the set of active modules + * changes to include Laboratory. This should only occur on the parent folder, not individual workbooks. + */ + private void possiblyInitializeOnActiveModuleChange(PropertyChangeEvent evt) + { + if (!(evt instanceof ContainerManager.ContainerPropertyChangeEvent)) + { + return; + } + + ContainerManager.ContainerPropertyChangeEvent ce = (ContainerManager.ContainerPropertyChangeEvent)evt; + + //Only make these changes from the parent container for performance reasons + if (ce.container.isWorkbook()) + { + return; + } + + User u = ce.user; + if (u == null && HttpView.hasCurrentView()) + { + u = HttpView.currentView().getViewContext().getUser(); + } + + if (u == null || !ce.container.hasPermission(u, InsertPermission.class)) + { + return; + } + + if (ce.container.getActiveModules().contains(ModuleLoader.getInstance().getModule(LaboratoryModule.class))) + { + try + { + LaboratoryManager.get().recursivelyInitWorkbooksForContainer(u, ce.container); + } + catch (Exception e) + { + _log.error("Unable to update laboratory workbooks table", e); + } + + try + { + LaboratoryManager.get().populateDefaultData(u, ce.container, null); + } + catch (Exception e) + { + _log.error("Unable to populate defaults in laboratory module tables", e); + } + } + } } diff --git a/laboratory/src/org/labkey/laboratory/LaboratoryController.java b/laboratory/src/org/labkey/laboratory/LaboratoryController.java index 3fdfb8a9..7efbe80b 100644 --- a/laboratory/src/org/labkey/laboratory/LaboratoryController.java +++ b/laboratory/src/org/labkey/laboratory/LaboratoryController.java @@ -471,7 +471,7 @@ public boolean handlePost(Object form, BindException errors) throws Exception { try { - LaboratoryManager.get().initWorkbooksForContainer(getUser(), getContainer()); + LaboratoryManager.get().recursivelyInitWorkbooksForContainer(getUser(), getContainer()); return true; } @@ -747,7 +747,7 @@ public ApiResponse execute(UpdateWorkbookForm form, BindException errors) throws return null; } - WorkbookModel model = LaboratoryManager.get().getWorkbookModel(getContainer()); + WorkbookModel model = LaboratoryManager.get().getWorkbookModel(getContainer(), true); if (model == null) { errors.reject(ERROR_MSG, "Unable to find workbook record for this folder"); @@ -759,11 +759,11 @@ public ApiResponse execute(UpdateWorkbookForm form, BindException errors) throws model.setResults(form.getResults()); model.setDescription(form.getDescription(), getUser()); - LaboratoryManager.get().updateWorkbook(getUser(), model); + LaboratoryManager.get().createOrUpdateWorkbook(getUser(), model); if (form.isForceTagUpdate() || form.getTags() != null) { - LaboratoryManager.get().updateWorkbookTags(getUser(), getContainer(), (form.getTags() == null ? (Collection)Collections.emptyList() : Arrays.asList(form.getTags()))); + LaboratoryManager.get().updateWorkbookTags(getUser(), getContainer(), (form.getTags() == null ? Collections.emptyList() : Arrays.asList(form.getTags()))); } results.put("success", true); @@ -2295,74 +2295,6 @@ public void setProtocol(Integer protocol) } } - @RequiresPermission(AdminPermission.class) - public class MigrateWorkbooksAction extends MutatingApiAction - { - @Override - public ApiResponse execute(MigrateWorkbooksForm form, BindException errors) throws Exception - { - if (getContainer().isWorkbook()) - { - errors.reject(ERROR_MSG, "Cannot be run on workbooks"); - return null; - } - - //find current workbook # - - - //find all workbooks where workbookId doesnt match LK ID - TreeMap toFix = new TreeMap<>(); - for (Container c : ContainerManager.getChildren(getContainer())) - { - if (c.isWorkbook()) - { - WorkbookModel w = LaboratoryManager.get().getWorkbookModel(c); - if (w != null) - { - _log.warn("workbook model not found for: " + c.getName()); - } - else if (!c.getName().equals(w.getWorkbookId().toString())) - { - toFix.put(w.getWorkbookId(), c); - } - } - } - - _log.info("workbooks to migrate: " + toFix.size()); - Set list = form.getReverseOrder() == true ? toFix.keySet() : toFix.descendingKeySet(); - for (Integer id : list) - { - Container wb = toFix.get(id); - Container target = ContainerManager.getChild(wb.getParent(), id.toString()); - if (target != null) - { - _log.warn("target workbook exists, skipping: " + id); - } - else - { - ContainerManager.rename(wb, getUser(), id.toString()); - } - } - - return new ApiSimpleResponse("success", true); - } - } - - public static class MigrateWorkbooksForm - { - private Boolean reverseOrder = false; - - public Boolean getReverseOrder() - { - return reverseOrder; - } - - public void setReverseOrder(Boolean reverseOrder) - { - this.reverseOrder = reverseOrder; - } - } - @RequiresPermission(ReadPermission.class) public class DataBrowserAction extends SimpleViewAction { diff --git a/laboratory/src/org/labkey/laboratory/LaboratoryManager.java b/laboratory/src/org/labkey/laboratory/LaboratoryManager.java index e62b5336..2fe1e982 100644 --- a/laboratory/src/org/labkey/laboratory/LaboratoryManager.java +++ b/laboratory/src/org/labkey/laboratory/LaboratoryManager.java @@ -27,10 +27,10 @@ import org.labkey.api.data.Container; import org.labkey.api.data.ContainerManager; import org.labkey.api.data.DbSchema; +import org.labkey.api.data.DbSchemaType; import org.labkey.api.data.DbScope; import org.labkey.api.data.RuntimeSQLException; import org.labkey.api.data.SQLFragment; -import org.labkey.api.data.Selector; import org.labkey.api.data.SimpleFilter; import org.labkey.api.data.Sort; import org.labkey.api.data.SqlExecutor; @@ -52,6 +52,7 @@ import org.labkey.api.query.QueryUpdateServiceException; import org.labkey.api.query.UserSchema; import org.labkey.api.security.User; +import org.labkey.api.security.permissions.InsertPermission; import org.labkey.api.util.PageFlowUtil; import org.labkey.api.view.Portal; import org.labkey.laboratory.query.ContainerIncrementingTable; @@ -94,44 +95,88 @@ public static LaboratoryManager get() return _instance; } - public void initWorkbooksForContainer(User u, Container c) throws Exception + public synchronized void initLaboratoryWorkbook(Container c, User u) { - if (c.isWorkbookOrTab()) + if (!c.isWorkbook()) + { return; + } - boolean labModuleEnabled = c.getActiveModules().contains(ModuleLoader.getInstance().getModule(LaboratoryModule.class)); + recursivelyInitWorkbooksForContainer(u, c); + } + + public synchronized void recursivelyInitWorkbooksForContainer(User u, Container c) + { + if (u == null || !c.hasPermission(u, InsertPermission.class)) + { + return; + } + + TableInfo workbookDbTable = DbSchema.get(LaboratoryModule.SCHEMA_NAME, DbSchemaType.Module).getTable(LaboratorySchema.TABLE_WORKBOOKS); + List rows = new ArrayList<>(); - List containers = c.getChildren(); - sortContainers(containers); - for (Container child : containers) + doInitWorkbooksForContainer(workbookDbTable, c, rows); + + if (!rows.isEmpty()) { - if (labModuleEnabled && child.isWorkbook()) + try (DbScope.Transaction t = workbookDbTable.getSchema().getScope().beginTransaction()) { - initLaboratoryWorkbook(child, u); + rows.forEach(wb -> Table.insert(u, workbookDbTable, wb)); + t.commit(); } - - initWorkbooksForContainer(u, child); } } - public synchronized void initLaboratoryWorkbook(Container c, User u) throws Exception + private void doInitWorkbooksForContainer(TableInfo workbookDbTable, Container c, List rows) { - if (c.isWorkbook()) + boolean labModuleEnabled = c.getActiveModules().contains(ModuleLoader.getInstance().getModule(LaboratoryModule.class)); + + if (labModuleEnabled && c.isWorkbook()) + { + prepareToInitLaboratoryWorkbook(workbookDbTable, c, rows); + return; + } + + Set childIdsPresent = new HashSet<>(); + if (labModuleEnabled) + { + childIdsPresent.addAll(new TableSelector(workbookDbTable, PageFlowUtil.set(LaboratoryWorkbooksTable.WORKBOOK_COL), new SimpleFilter(FieldKey.fromString(LaboratoryWorkbooksTable.PARENT_COL), c.getId()), null).getArrayList(String.class)); + } + + List children = c.getChildren(); + sortContainers(children); + for (Container child : children) { - UserSchema us = QueryService.get().getUserSchema(u, c, LaboratoryModule.SCHEMA_NAME); - if (us != null) + if (childIdsPresent.contains(child.getId())) { - TableInfo ti = us.getTable(LaboratorySchema.TABLE_WORKBOOKS); - TableSelector ts = new TableSelector(ti, new SimpleFilter(FieldKey.fromString(LaboratoryWorkbooksTable.WORKBOOK_COL), c.getId()), null); - if (!ts.exists()) - { - List> rows = new ArrayList>(); - Map row = new CaseInsensitiveHashMap<>(); - rows.add(row); + continue; + } - ti.getUpdateService().insertRows(u, c, rows, new BatchValidationException(), null, new HashMap<>()); + if (child.isWorkbook()) + { + if (labModuleEnabled) + { + prepareToInitLaboratoryWorkbook(workbookDbTable, child, rows); } } + else + { + doInitWorkbooksForContainer(workbookDbTable, child, rows); + } + } + } + + private void prepareToInitLaboratoryWorkbook(TableInfo workbookDbTable, Container c, List rows) + { + if (!c.isWorkbook()) + { + return; + } + + TableSelector ts = new TableSelector(workbookDbTable, new SimpleFilter(FieldKey.fromString(LaboratoryWorkbooksTable.WORKBOOK_COL), c.getId()), null); + if (!ts.exists()) + { + rows.add(WorkbookModel.createNew(c)); } } @@ -140,14 +185,28 @@ private void sortContainers(List containers) containers.sort(Comparator.comparingInt(Container::getRowId)); } - public WorkbookModel getWorkbookModel(Container c) + public WorkbookModel getWorkbookModel(Container c, boolean createIfNotPresent) { + if (!c.isWorkbook()) + { + return null; + } + TableInfo ti = LaboratorySchema.getInstance().getSchema().getTable(LaboratorySchema.TABLE_WORKBOOKS); TableSelector ts = new TableSelector(ti, new SimpleFilter(FieldKey.fromString(LaboratoryWorkbooksTable.WORKBOOK_COL), c.getId()), null); WorkbookModel[] arr = ts.getArray(WorkbookModel.class); WorkbookModel m = arr.length == 0 ? null : arr[0]; if (m == null) - return null; + { + if (createIfNotPresent) + { + m = WorkbookModel.createNew(c); + } + else + { + return null; + } + } if (m.getContainer() == null) { @@ -214,10 +273,18 @@ else if (expt.equals(c.getFolderType())) } } - public void updateWorkbook(User u, WorkbookModel model) + public void createOrUpdateWorkbook(User u, WorkbookModel model) { TableInfo ti = LaboratorySchema.getInstance().getTable(LaboratorySchema.TABLE_WORKBOOKS); - Table.update(u, ti, model, model.getContainer()); + TableSelector ts = new TableSelector(ti, new SimpleFilter(FieldKey.fromString("container"), model.getContainer()), null); + if (!ts.exists()) + { + Table.insert(u, ti, model); + } + else + { + Table.update(u, ti, model, model.getContainer()); + } } public void updateWorkbookTags(User u, Container c, Collection tags) @@ -230,7 +297,7 @@ public void updateWorkbookTags(User u, Container c, Collection tags, boo assert u != null : "No user provided"; TableInfo ti = LaboratorySchema.getInstance().getTable(LaboratorySchema.TABLE_WORKBOOK_TAGS); - Set newTags = new HashSet(); + Set newTags = new HashSet<>(); newTags.addAll(tags); SimpleFilter filter = new SimpleFilter(FieldKey.fromString("container"), c.getId()); @@ -242,7 +309,7 @@ public void updateWorkbookTags(User u, Container c, Collection tags, boo newTags.addAll(existingTags); } - List toDelete = new ArrayList(existingTags); + List toDelete = new ArrayList<>(existingTags); toDelete.removeAll(newTags); if (toDelete.size() > 0) @@ -258,7 +325,7 @@ public void updateWorkbookTags(User u, Container c, Collection tags, boo Date created = new Date(); for (String tag : toAdd) { - Map row = new HashMap(); + Map row = new HashMap<>(); row.put("tag", tag); row.put("created", created); row.put("createdby", u.getUserId()); @@ -268,8 +335,13 @@ public void updateWorkbookTags(User u, Container c, Collection tags, boo } //pass null to populate all supported tables - public void populateDefaultData(User u, Container c, @Nullable List tableNames) throws BatchValidationException + public void populateDefaultData(User u, Container c, @Nullable List tableNames) { + if (c.isWorkbook()) + { + return; + } + if (tableNames == null) { tableNames = new ArrayList<>(); @@ -280,15 +352,17 @@ public void populateDefaultData(User u, Container c, @Nullable List tabl { if (LaboratorySchema.TABLE_SAMPLE_TYPE.equalsIgnoreCase(name)) { - populateDefaultDataForTable(u, c, "laboratory", LaboratorySchema.TABLE_SAMPLE_TYPE, PageFlowUtil.set("type")); + populateDefaultDataForTable(u, c, "laboratory", LaboratorySchema.TABLE_SAMPLE_TYPE, PageFlowUtil.set("type"), "type"); } } } /** + * This copies the values of this table that exist in /Shared to the target folder. + * * NOTE: this makes the assumption that the schema/query match a physical table */ - private void populateDefaultDataForTable(User u, Container c, String schema, String query, final Set columns) throws BatchValidationException + private void populateDefaultDataForTable(User u, Container c, String schema, String query, final Set columns, final String keyCol) { assert u != null : "No user provided"; @@ -297,12 +371,11 @@ private void populateDefaultDataForTable(User u, Container c, String schema, Str throw new IllegalArgumentException("Schema " + schema + " not found"); - DbSchema sourceSchema = DbSchema.get(schema); + DbSchema sourceSchema = DbSchema.get(schema, DbSchemaType.Module); if (sourceSchema == null) - throw new IllegalArgumentException("Schema " + schema + " not found in /shared"); + throw new IllegalArgumentException("Schema " + schema + " not found in: " + c.getPath()); - TableInfo ti = us.getTable(query); - final QueryUpdateService qus = ti.getUpdateService(); + TableInfo targetTable = us.getTable(query, null); TableInfo sourceTable = sourceSchema.getTable(query); if (sourceTable.getColumn(FieldKey.fromString("container")) == null) @@ -310,46 +383,50 @@ private void populateDefaultDataForTable(User u, Container c, String schema, Str throw new IllegalArgumentException("Table " + schema + "." + query + " does not have a container column"); } - SimpleFilter filter = new SimpleFilter(FieldKey.fromString("container"), ContainerManager.getSharedContainer().getId()); + //Find unique values in the target table: + List existingValues = new TableSelector(targetTable, PageFlowUtil.set(keyCol)).getArrayList(Object.class); + + //Now find the values from /Shared + Set selectCols = new HashSet<>(columns); + columns.add(keyCol); final List> rows = new ArrayList<>(); - TableSelector ts = new TableSelector(sourceTable, columns, filter, null); - ts.forEach(new Selector.ForEachBlock() - { - @Override - public void exec(ResultSet rs) throws SQLException + TableSelector ts = new TableSelector(sourceTable, selectCols, new SimpleFilter(FieldKey.fromString("container"), ContainerManager.getSharedContainer().getId()), null); + ts.forEach(rs -> { + + if (existingValues.contains(rs.getObject(keyCol))) { - Map row = new CaseInsensitiveHashMap<>(); - for (String col : columns) - { - row.put(col, rs.getObject(col)); - } + return; + } - rows.add(row); + Map row = new CaseInsensitiveHashMap<>(); + for (String col : columns) + { + row.put(col, rs.getObject(col)); } - }); - BatchValidationException errors = new BatchValidationException(); + rows.add(row); + }); - try - { - qus.insertRows(u, c, rows, errors, null, new HashMap()); - } - catch (QueryUpdateServiceException e) + if (rows.isEmpty()) { - throw new RuntimeException(e); - } - catch (DuplicateKeyException e) - { - //ignore + return; } - catch (SQLException e) + + try { - throw new RuntimeSQLException(e); + BatchValidationException errors = new BatchValidationException(); + QueryUpdateService qus = targetTable.getUpdateService(); + qus.insertRows(u, c, rows, errors, null, new HashMap<>()); + + if (errors.hasErrors()) + { + throw errors; + } } - catch (BatchValidationException e) + catch (SQLException | QueryUpdateServiceException | DuplicateKeyException | BatchValidationException e) { - //ignore + _log.error("Unable to insert default laboratory data", e); } } @@ -362,7 +439,7 @@ public void initContainerIncrementingTableIds(Container c, User u, String schema if (us == null) throw new IllegalArgumentException("Unknown schema: " + schemaName); - TableInfo table = us.getTable(queryName); + TableInfo table = us.getTable(queryName, null); if (table == null) throw new IllegalArgumentException("Unknown table: " + schemaName + "." + queryName); diff --git a/laboratory/src/org/labkey/laboratory/LaboratoryModule.java b/laboratory/src/org/labkey/laboratory/LaboratoryModule.java index e7b7d494..1e204b49 100644 --- a/laboratory/src/org/labkey/laboratory/LaboratoryModule.java +++ b/laboratory/src/org/labkey/laboratory/LaboratoryModule.java @@ -35,6 +35,7 @@ import org.labkey.api.security.permissions.UpdatePermission; import org.labkey.api.security.roles.RoleManager; import org.labkey.api.settings.AdminConsole; +import org.labkey.api.util.HtmlString; import org.labkey.api.util.PageFlowUtil; import org.labkey.api.view.BaseWebPartFactory; import org.labkey.api.view.HtmlView; @@ -89,10 +90,15 @@ protected Collection createWebPartFactories() @Override public WebPartView getWebPartView(@NotNull ViewContext portalCtx, @NotNull Portal.WebPart webPart) { - WorkbookModel model = LaboratoryManager.get().getWorkbookModel(portalCtx.getContainer()); + if (!portalCtx.getContainer().isWorkbook()) + { + return new HtmlView(HtmlString.of("This container is not a workbook")); + } + + WorkbookModel model = LaboratoryManager.get().getWorkbookModel(portalCtx.getContainer(), true); if (model == null) { - return new HtmlView("This container is not a workbook"); + model = WorkbookModel.createNew(portalCtx.getContainer()); } JspView view = new JspView<>("/org/labkey/laboratory/view/workbookHeader.jsp", model); diff --git a/laboratory/src/org/labkey/laboratory/LaboratoryUpgradeCode.java b/laboratory/src/org/labkey/laboratory/LaboratoryUpgradeCode.java index 7c24fee4..a9e0dbd9 100644 --- a/laboratory/src/org/labkey/laboratory/LaboratoryUpgradeCode.java +++ b/laboratory/src/org/labkey/laboratory/LaboratoryUpgradeCode.java @@ -37,7 +37,7 @@ public void initWorkbookTable(final ModuleContext moduleContext) { try { - LaboratoryManager.get().initWorkbooksForContainer(moduleContext.getUpgradeUser(), ContainerManager.getRoot()); + LaboratoryManager.get().recursivelyInitWorkbooksForContainer(moduleContext.getUpgradeUser(), ContainerManager.getRoot()); } catch (Exception e) { diff --git a/laboratory/src/org/labkey/laboratory/query/WorkbookModel.java b/laboratory/src/org/labkey/laboratory/query/WorkbookModel.java index c2e4e6a3..29ab7412 100644 --- a/laboratory/src/org/labkey/laboratory/query/WorkbookModel.java +++ b/laboratory/src/org/labkey/laboratory/query/WorkbookModel.java @@ -123,4 +123,18 @@ public void setTags(String[] tags) { _tags = tags; } + + public static WorkbookModel createNew(Container c) + { + if (!c.isWorkbook()) + { + throw new IllegalArgumentException("Container is not a workbook: " + c.getPath()); + } + + WorkbookModel model = new WorkbookModel(); + model.setWorkbookId(Integer.parseInt(c.getName())); + model.setContainer(c.getId()); + + return model; + } }