/** * When upsert is requested on different versions but same ID there should be duplicate * key exception thrown by Mongo since there will be an attempt to insert new document (same id * different version) * Based on criteria it is a new document, based on primary key ({@code _id}) it exists already. */ @Test public void duplicateKeyUpsertSameKeyDifferentVersions() throws Exception { ImmutableEntity entity = ImmutableEntity.builder().id("e1").version(0).value("v0").build(); repository.upsert(entity).getUnchecked(); // first upsert successful (document should be with new version) repository.find(repository.criteria().id(entity.id()).version(0)) .andReplaceFirst(entity.withVersion(1)) .upsert() .getUnchecked(); try { // this should fail because here upsert == insert (document e1 with version 0 doesn't exist) repository.find(repository.criteria().id(entity.id()).version(0)) .andReplaceFirst(entity.withVersion(1)) .upsert() .getUnchecked(); fail("Should fail with " + DuplicateKeyException.class.getName()); } catch (Exception e) { MongoAsserts.assertDuplicateKeyException(e); } }
@Override public boolean add(List<JobFeedbackPo> jobFeedbackPos) { if (CollectionUtils.isEmpty(jobFeedbackPos)) { return true; } for (JobFeedbackPo jobFeedbackPo : jobFeedbackPos) { String tableName = JobQueueUtils.getFeedbackQueueName( jobFeedbackPo.getTaskTrackerJobResult().getJobWrapper().getJob().getSubmitNodeGroup()); try { template.save(tableName, jobFeedbackPo); } catch (DuplicateKeyException e) { LOGGER.warn("duplicate key for job feedback po: " + JSON.toJSONString(jobFeedbackPo)); } } return true; }
@Override public boolean add(JobPo jobPo) { try { String tableName = JobQueueUtils.getExecutableQueueName(jobPo.getTaskTrackerNodeGroup()); if (!EXIST_TABLE.contains(tableName)) { createQueue(jobPo.getTaskTrackerNodeGroup()); } jobPo.setGmtCreated(SystemClock.now()); jobPo.setGmtModified(jobPo.getGmtCreated()); template.save(tableName, jobPo); } catch (DuplicateKeyException e) { // 已经存在 throw new DupEntryException(e); } return true; }
/** * Attempts to insert a lock record to the db * * @returns true if successful, false if lock already exists. Any other case * would be an exception. */ private boolean acquire(String callerId, String resourceId, Long ttl, Date now, Date expiration) { BasicDBObject update = new BasicDBObject(). append(CALLERID, callerId). append(RESOURCEID, resourceId). append(TIMESTAMP, now). append(TTL, ttl). append(EXPIRATION, expiration). append(COUNT, 1). append(VERSION, 1); try { LOGGER.debug("insert: {}", update); coll.insert(update, WriteConcern.ACKNOWLEDGED); } catch (DuplicateKeyException dke) { return false; } return true; }
@Override public void saveBlob(final MD5 md5, final InputStream data, final boolean sorted) throws BlobStoreCommunicationException { if(data == null || md5 == null) { throw new NullPointerException("Arguments cannot be null"); } if (getFile(md5) != null) { return; //already exists } final GridFSInputFile gif = gfs.createFile(data, true); gif.setId(md5.getMD5()); gif.setFilename(md5.getMD5()); gif.put(Fields.GFS_SORTED, sorted); try { gif.save(); } catch (DuplicateKeyException dk) { // already here, done } catch (MongoException me) { throw new BlobStoreCommunicationException( "Could not write to the mongo database", me); } }
@Override public Instant renameWorkspace(final ResolvedWorkspaceID rwsi, final String newname) throws WorkspaceCommunicationException, CorruptWorkspaceDBException { if (newname.equals(rwsi.getName())) { throw new IllegalArgumentException("Workspace is already named " + newname); } final Instant now = Instant.now(); try { wsjongo.getCollection(COL_WORKSPACES) .update(M_WS_ID_QRY, rwsi.getID()) .with(M_RENAME_WS_WTH, newname, Date.from(now)); } catch (DuplicateKeyException medk) { throw new IllegalArgumentException( "There is already a workspace named " + newname); } catch (MongoException me) { throw new WorkspaceCommunicationException( "There was a problem communicating with the database", me); } return now; }
/** * {@inheritDoc} */ @Override public void createNewUser(UserData data) { BasicDBObjectBuilder insertObjBuilder = BasicDBObjectBuilder.start() .add(FIELD_RANDOMIZER, data.getRandomizer()) .add(FIELD_USERNAME, data.getUsername()) .add(FIELD_PASSWORD, data.getPassword()) .add(FIELD_CREATION_STATE, data.getCreationState().toString()) .add(FIELD_FIRST_NAME, data.getFirstName()) .add(FIELD_LAST_NAME, data.getLastName()) .add(FIELD_EMAIL, data.getEmail()) .add(FIELD_DOMAIN, data.getDomain()) .add(FIELD_GROUPS, data.getGroups()); DBObject insertObj = insertObjBuilder.get(); try { collection.insert(insertObj); } catch (DuplicateKeyException e) { // We just rethrow as per the API throw e; } }
/** * Create a new folder entry with the given data */ public void createNewFolder(FolderData data) { BasicDBObjectBuilder insertObjBuilder = BasicDBObjectBuilder.start() .add(FIELD_ID, data.getId()) .add(FIELD_CONTEXT, data.getContext()) .add(FIELD_PATH, data.getPath()) .add(FIELD_LEVEL, data.getLevel()) .add(FIELD_PARENT_PATH, data.getParentPath()) .add(FIELD_NAME, data.getName()) .add(FIELD_FOLDER_COUNT, data.getFolderCount()) .add(FIELD_FILE_COUNT, data.getFileCount()); DBObject insertObj = insertObjBuilder.get(); try { collection.insert(insertObj); } catch (DuplicateKeyException e) { // We just rethrow as per the API throw e; } }
@Test public void testDuplicateUsername() { UserData user = createUserData("testDuplicateUsername" + System.nanoTime()); userDataService.createNewUser(user); UserData userDup = createUserData("testDuplicateUsername" + System.nanoTime()); userDup.setUsername(user.getUsername()); // This should fail try { userDataService.createNewUser(userDup); Assert.fail("Should fail due to duplicate username."); } catch (DuplicateKeyException e) { // Expected } }
@Test public void testDuplicateEmail() { UserData user = createUserData("testDuplicateEmail" + System.nanoTime()); userDataService.createNewUser(user); UserData userDup = createUserData("testDuplicateEmail" + System.nanoTime()); userDup.setEmail(user.getEmail()); // This should fail try { userDataService.createNewUser(userDup); Assert.fail("Should fail due to duplicate email."); } catch (DuplicateKeyException e) { // Expected } }
@Override public SmartiUser create(SmartiUser user) { try { mongoTemplate.insert(user); return mongoTemplate.findById(user.getLogin(), SmartiUser.class); } catch (DuplicateKeyException e) { return null; } }
public Client save(Client client) { if(client.getId() != null) { Client client_old = clientRepository.findOne(client.getId()); if(client_old == null) { throw new ConflictException(Client.class, "id", "New clients may not have an id set"); } if(!client_old.getName().equals(client.getName())) { validateClientName(client); } } else { if(!isProperClientName(client.getName())) { throw new IllegalArgumentException("Client name must match pattern: " + NAME_PATTERN); } } if(client.isDefaultClient()) { clientRepository.save(clientRepository.findByDefaultClientTrue().stream().map( c -> { c.setDefaultClient(false); return c; } ).collect(Collectors.toList())); } //save the client client.setLastUpdate(new Date()); try { client = clientRepository.save(client); } catch (DuplicateKeyException | org.springframework.dao.DuplicateKeyException e) { throw new ConflictException(Client.class, "name", "A Client with the name '" + client.getName() + "' already exists!"); } //init the client configuration initClientConfiguration(client); return client; }
@Override public V save(V entity) { prePersist(entity); try { morphiaDao.save(entity); } catch (DuplicateKeyException e) { throw new RestDslException("Duplicate mongo key: " + e.getMessage(), RestDslException.Type.DUPLICATE_KEY); } return entity; }
protected V findAndModify(ServiceQuery<K> q, UpdateOperations<V> updateOperations, boolean oldVersion, boolean createIfMissing) throws RestDslException { preUpdate(q, updateOperations); Query<V> morphiaQuery = convertToMorphiaQuery(q, false); try { return morphiaDao.getDatastore().findAndModify(morphiaQuery, updateOperations, oldVersion, createIfMissing); } catch (DuplicateKeyException e) { throw new RestDslException("Duplicate mongo key: " + e.getMessage(), RestDslException.Type.DUPLICATE_KEY); } }
@Override public void addNodeGroup(NodeType nodeType, String name) { try { NodeGroupPo nodeGroupPo = new NodeGroupPo(); nodeGroupPo.setNodeType(nodeType); nodeGroupPo.setName(name); nodeGroupPo.setGmtCreated(SystemClock.now()); template.save(nodeGroupPo); } catch (DuplicateKeyException e) { // ignore } }
@Override public boolean add(JobPo jobPo) { try { template.save(jobPo); } catch (DuplicateKeyException e) { // already exist throw new DupEntryException(e); } return true; }
@Override public boolean add(JobPo jobPo) { try { template.save(jobPo); } catch (DuplicateKeyException e) { // 已经存在 throw new DupEntryException(e); } return true; }
@ExceptionHandler @ResponseBody @ResponseStatus(HttpStatus.BAD_REQUEST) ErrorListDto handleDuplicateKeyException(DuplicateKeyException exception) { ErrorListDto errorListDto = new ErrorListDto(); if (exception.getErrorMessage().contains("filename")) { ErrorDto error = new ErrorDto(null, "global.error.import.file-already-exists", null,"filename"); errorListDto.add(error); } else { throw exception; } return errorListDto; }
private Error analyzeException(Exception e, final String otherwise, final String msg, boolean specialHandling) { if (e instanceof Error) { return (Error) e; } if (e instanceof MongoException) { MongoException me = (MongoException) e; if (me.getCode() == 18) { return Error.get(CrudConstants.ERR_AUTH_FAILED, e.getMessage()); } else if (me instanceof MongoTimeoutException || me instanceof MongoExecutionTimeoutException) { LOGGER.error(CrudConstants.ERR_DATASOURCE_TIMEOUT, e); return Error.get(CrudConstants.ERR_DATASOURCE_TIMEOUT, e.getMessage()); } else if (me instanceof DuplicateKeyException) { return Error.get(MongoCrudConstants.ERR_DUPLICATE, e.getMessage()); } else if (me instanceof MongoSocketException) { LOGGER.error(MongoCrudConstants.ERR_CONNECTION_ERROR, e); return Error.get(MongoCrudConstants.ERR_CONNECTION_ERROR, e.getMessage()); } else { LOGGER.error(MongoCrudConstants.ERR_MONGO_ERROR, e); return Error.get(MongoCrudConstants.ERR_MONGO_ERROR, e.getMessage()); } } else if (msg == null) { return Error.get(otherwise, e.getMessage()); } else if (specialHandling) { return Error.get(otherwise, msg, e); } else { return Error.get(otherwise, msg); } }
private void checkConfig() throws WorkspaceCommunicationException, WorkspaceDBInitializationException, CorruptWorkspaceDBException { final DBObject cfg = new BasicDBObject( Fields.CONFIG_KEY, Fields.CONFIG_VALUE); cfg.put(Fields.CONFIG_UPDATE, false); cfg.put(Fields.CONFIG_SCHEMA_VERSION, SCHEMA_VERSION); try { wsmongo.getCollection(COL_CONFIG).insert(cfg); } catch (DuplicateKeyException dk) { //ok, the version doc is already there, this isn't the first //startup final DBCursor cur = wsmongo.getCollection(COL_CONFIG) .find(new BasicDBObject( Fields.CONFIG_KEY, Fields.CONFIG_VALUE)); if (cur.size() != 1) { throw new CorruptWorkspaceDBException( "Multiple config objects found in the database. " + "This should not happen, something is very wrong."); } final DBObject storedCfg = cur.next(); if ((Integer)storedCfg.get(Fields.CONFIG_SCHEMA_VERSION) != SCHEMA_VERSION) { throw new WorkspaceDBInitializationException(String.format( "Incompatible database schema. Server is v%s, DB is v%s", SCHEMA_VERSION, storedCfg.get(Fields.CONFIG_SCHEMA_VERSION))); } if ((Boolean)storedCfg.get(Fields.CONFIG_UPDATE)) { throw new CorruptWorkspaceDBException(String.format( "The database is in the middle of an update from " + "v%s of the schema. Aborting startup.", storedCfg.get(Fields.CONFIG_SCHEMA_VERSION))); } } catch (MongoException me) { throw new WorkspaceCommunicationException( "There was a problem communicating with the database", me); } }
private void ensureIndexes() throws CorruptWorkspaceDBException { final HashMap<String, List<IndexSpecification>> indexes = getIndexSpecs(); for (final String col: indexes.keySet()) { // wsmongo.getCollection(col).resetIndexCache(); for (final IndexSpecification index: indexes.get(col)) { try { wsmongo.getCollection(col).createIndex(index.index, index.options); } catch (DuplicateKeyException dk) { throw new CorruptWorkspaceDBException( "Found duplicate index keys in the database, " + "aborting startup", dk); } } } }
private Instant updateClonedWorkspaceInformation( final WorkspaceUser user, final boolean globalRead, final long id, final String newname) throws PreExistingWorkspaceException, WorkspaceCommunicationException, CorruptWorkspaceDBException { final DBObject q = new BasicDBObject(Fields.WS_ID, id); final Date moddate = new Date(); final DBObject ws = new BasicDBObject(); ws.put(Fields.WS_MODDATE, moddate); ws.put(Fields.WS_NAME, newname); final DBObject update = new BasicDBObject( "$unset", new BasicDBObject(Fields.WS_CLONING, "")); update.put("$set", ws); final WriteResult wr; try { wr = wsmongo.getCollection(COL_WORKSPACES).update(q, update); } catch (DuplicateKeyException mdk) { throw new PreExistingWorkspaceException(String.format( "Workspace name %s is already in use", newname)); } catch (MongoException me) { throw new WorkspaceCommunicationException( "There was a problem communicating with the database", me); } if (wr.getN() != 1) { throw new IllegalStateException("A programming error occurred: " + "there is no workspace with ID " + id); } setCreatedWorkspacePermissions(user, globalRead, new ResolvedWorkspaceID(id, newname, false, false)); return moddate.toInstant(); }
@Override public ObjectInfoWithModDate renameObject( final ObjectIDResolvedWS oi, final String newname) throws NoSuchObjectException, WorkspaceCommunicationException { Set<ObjectIDResolvedWS> input = new HashSet<ObjectIDResolvedWS>( Arrays.asList(oi)); final ResolvedObjectID roi = resolveObjectIDs(input).get(oi); if (newname.equals(roi.getName())) { throw new IllegalArgumentException("Object is already named " + newname); } final Instant time = Instant.now(); try { wsjongo.getCollection(COL_WORKSPACE_OBJS) .update(M_RENAME_OBJ_QRY, roi.getWorkspaceIdentifier().getID(), roi.getId()) .with(M_RENAME_OBJ_WTH, newname, Date.from(time)); } catch (DuplicateKeyException medk) { throw new IllegalArgumentException( "There is already an object in the workspace named " + newname); } catch (MongoException me) { throw new WorkspaceCommunicationException( "There was a problem communicating with the database", me); } final ObjectIDResolvedWS oid = new ObjectIDResolvedWS( roi.getWorkspaceIdentifier(), roi.getId(), roi.getVersion()); input = new HashSet<ObjectIDResolvedWS>(Arrays.asList(oid)); final ObjectInformation oinf = getObjectInformation(input, false, true, false, true).get(oid); updateWorkspaceModifiedDate(roi.getWorkspaceIdentifier()); return new ObjectInfoWithModDate(oinf, time); }
@Override public Instant setWorkspaceOwner( final ResolvedWorkspaceID rwsi, final WorkspaceUser owner, final WorkspaceUser newUser, final Optional<String> newname) throws WorkspaceCommunicationException, CorruptWorkspaceDBException { final Instant now = Instant.now(); try { if (!newname.isPresent()) { wsjongo.getCollection(COL_WORKSPACES) .update(M_WS_ID_QRY, rwsi.getID()) .with(M_CHOWN_WS_WTH, newUser.getUser(), Date.from(now)); } else { wsjongo.getCollection(COL_WORKSPACES) .update(M_WS_ID_QRY, rwsi.getID()) .with(M_CHOWN_WS_NEWNAME_WTH, newUser.getUser(), newname.get(), Date.from(now)); } } catch (DuplicateKeyException medk) { throw new IllegalArgumentException( "There is already a workspace named " + newname.get()); } catch (MongoException me) { throw new WorkspaceCommunicationException( "There was a problem communicating with the database", me); } final ResolvedWorkspaceID newRwsi = new ResolvedWorkspaceID( rwsi.getID(), newname.isPresent() ? newname.get() : rwsi.getName(), false, false); setPermissionsForWorkspaceUsers(newRwsi, Arrays.asList(owner), Permission.ADMIN, false); setPermissionsForWorkspaceUsers(newRwsi, Arrays.asList(newUser), Permission.OWNER, false); return now; }
OperationResult createFile( Database db, String dbName, String bucketName, BsonDocument metadata, Path filePath) throws IOException, DuplicateKeyException;
@Test(expected=DuplicateKeyException.class) public void uniqueId() { FolderData folderData = new FolderData("A123", "home", "/myfolders/tests", 6L, 17L); fileFolderService.createNewFolder(folderData); FolderData folderData2 = new FolderData("A123", "home", "/myfolders/reports", 6L, 5L); fileFolderService.createNewFolder(folderData2); }
@Test(expected=DuplicateKeyException.class) public void uniquePath() { FolderData folderData = new FolderData("A123", "home", "/myfolders/tests", 3L, 17L); fileFolderService.createNewFolder(folderData); FolderData folderData2 = new FolderData("B456", "home", "/myfolders/tests", 2L, 5L); fileFolderService.createNewFolder(folderData2); }
/** * Ensures current exception has been generated due to a duplicate (primary) key. * Differentiates between Fongo and Mongo exceptions since the behaviour under these databases * is different. */ public static void assertDuplicateKeyException(Throwable exception) { Preconditions.checkNotNull(exception, "exception"); // unwrap, if necessary exception = exception instanceof MongoException ? exception : exception.getCause(); // fongo throws directly DuplicateKeyException if (exception instanceof DuplicateKeyException) return; // MongoDB throws custom exception if (exception instanceof MongoCommandException) { String codeName = ((MongoCommandException) exception).getResponse().get("codeName").asString().getValue(); int errorCode = ((MongoCommandException) exception).getErrorCode(); check(codeName).is("DuplicateKey"); check(errorCode).is(11000); // all good here (can return) return; } // for bulk writes as well if (exception instanceof MongoBulkWriteException) { List<BulkWriteError> errors = ((MongoBulkWriteException) exception).getWriteErrors(); check(errors).hasSize(1); check(errors.get(0).getCode()).is(11000); check(errors.get(0).getMessage()).contains("duplicate key"); return; } // if we got here means there is a problem (no duplicate key exception) fail("Should get duplicate key exception after " + exception); }
@Test(expected = DuplicateKeyException.class) public void testUniqueIndexedEntity() throws Exception { getDs().ensureIndexes(); assertThat(getDs().getCollection(UniqueIndexOnValue.class).getIndexInfo(), hasIndexNamed("l_ascending")); getDs().save(new UniqueIndexOnValue("a")); // this should throw... getDs().save(new UniqueIndexOnValue("v")); }
@ExceptionHandler(DuplicateKeyException.class) @ResponseStatus(value = CONFLICT, reason = "An user with that mail already exists") public void duplicateKeyException() {}
@ExceptionHandler(DuplicateKeyException.class) @ResponseStatus(value = CONFLICT, reason = "A template with that name already exists") void duplicateKeyException() {}
@ExceptionHandler(DuplicateKeyException.class) @ResponseStatus(value = CONFLICT, reason = "An option with that name already exists") void duplicateKeyException() {}
@ExceptionHandler(DuplicateKeyException.class) @ResponseStatus(value = CONFLICT, reason = "The specified name already exists") void duplicateKeyException() {}
private IDName saveWorkspaceObject( final ResolvedWorkspaceID wsid, final long objectid, final String name) throws WorkspaceCommunicationException { if (name == null) { throw new NullPointerException("name"); } final DBObject dbo = new BasicDBObject(); dbo.put(Fields.OBJ_WS_ID, wsid.getID()); dbo.put(Fields.OBJ_ID, objectid); dbo.put(Fields.OBJ_VCNT, 0); //Integer dbo.put(Fields.OBJ_REFCOUNTS, new LinkedList<Integer>()); dbo.put(Fields.OBJ_NAME, name); dbo.put(Fields.OBJ_LATEST, null); //TODO DBUPDATE remove this field. Deleting versions is out, just delete the entire object. dbo.put(Fields.OBJ_DEL, false); dbo.put(Fields.OBJ_HIDE, false); try { //maybe could speed things up with batch inserts but dealing with //errors would really suck //do this later if it becomes a bottleneck wsmongo.getCollection(COL_WORKSPACE_OBJS).insert(dbo); } catch (DuplicateKeyException dk) { //ok, someone must've just this second added this name to an object //asshole //this should be a rare event //TODO BUG if id dupe throw exception, stack overflow otherwise. Can't actually happen unless bug in code though. final ObjectIDNoWSNoVer o = new ObjectIDNoWSNoVer(name); final Map<ObjectIDNoWSNoVer, ResolvedObjectID> objID = resolveObjectIDsIgnoreExceptions(wsid, new HashSet<ObjectIDNoWSNoVer>(Arrays.asList(o))); if (objID.isEmpty()) { //oh ffs, name deleted again, try again return saveWorkspaceObject(wsid, objectid, name); } //save version via the id associated with our name which already exists return new IDName(objID.get(o).getId(), objID.get(o).getName()); } catch (MongoException me) { throw new WorkspaceCommunicationException( "There was a problem communicating with the database", me); } return new IDName(objectid, name); }
@Override public void handleRequest( HttpServerExchange exchange, RequestContext context) throws Exception { if (context.isInError()) { next(exchange, context); return; } final BsonValue _metadata = context.getContent(); // must be an object if (!_metadata.isDocument()) { ResponseHelper.endExchangeWithMessage( exchange, context, HttpStatus.SC_NOT_ACCEPTABLE, "data cannot be an array"); next(exchange, context); return; } BsonDocument metadata = _metadata.asDocument(); OperationResult result; try { if (context.getFilePath() != null) { result = gridFsDAO .createFile(getDatabase(), context.getDBName(), context.getCollectionName(), metadata, context.getFilePath()); } else { throw new RuntimeException("error. file data is null"); } } catch (IOException | RuntimeException t) { if (t instanceof DuplicateKeyException) { // update not supported String errMsg = "file resource update is not yet implemented"; ResponseHelper.endExchangeWithMessage( exchange, context, HttpStatus.SC_NOT_IMPLEMENTED, errMsg); next(exchange, context); return; } throw t; } context.setDbOperationResult(result); // insert the Location handler exchange.getResponseHeaders() .add(HttpString.tryFromString("Location"), getReferenceLink( context, exchange.getRequestURL(), result.getNewId())); context.setResponseStatusCode(result.getHttpCode()); next(exchange, context); }
/** * Create a new test for the precise release and schema * * @param test * a globally-unique name using * {@link ConfigConstants#TEST_NAME_REGEX} * @param description * any description * @param release * the test definition software release * @param schema * the schema number * @return <tt>true</tt> if the test was written other <tt>false</tt> if not */ public boolean createTest(String test, String description, String release, Integer schema) { if (test == null || test.length() == 0) { throw new IllegalArgumentException("Name length must be non-zero"); } else if (release == null || schema == null) { throw new IllegalArgumentException("A release and schema number must be supplied for a test."); } Pattern pattern = Pattern.compile(TEST_NAME_REGEX); Matcher matcher = pattern.matcher(test); if (!matcher.matches()) { throw new IllegalArgumentException( "The test name '" + test + "' is invalid. " + "Test names must start with a character and contain only characters, numbers or underscore."); } // There are no properties to start with DBObject writeObj = BasicDBObjectBuilder .start() .add(FIELD_NAME, test) .add(FIELD_VERSION, Integer.valueOf(0)) .add(FIELD_DESCRIPTION, description) .add(FIELD_RELEASE, release) .add(FIELD_SCHEMA, schema) .get(); try { WriteResult result = tests.insert(writeObj); if (logger.isDebugEnabled()) { logger.debug( "Created test: " + result + "\n" + " Name: " + test + "\n" + " Descr: " + description + "\n" + " Release: " + release + "\n" + " Schema: " + schema); } return true; } catch (DuplicateKeyException e) { if (logger.isDebugEnabled()) { logger.debug("Test exists: " + test + "."); } return false; } }