Java tutorial
/* * Copyright 2018, Andrew Lindesay * Distributed under the terms of the MIT License. */ package org.haiku.haikudepotserver.api1; import com.google.common.base.Preconditions; import com.google.common.base.Strings; import com.googlecode.jsonrpc4j.spring.AutoJsonRpcServiceImpl; import org.apache.cayenne.ObjectContext; import org.apache.cayenne.configuration.server.ServerRuntime; import org.apache.commons.lang.StringUtils; import org.haiku.haikudepotserver.api1.model.userrating.*; import org.haiku.haikudepotserver.api1.support.AuthorizationFailureException; import org.haiku.haikudepotserver.api1.support.ObjectNotFoundException; import org.haiku.haikudepotserver.dataobjects.*; import org.haiku.haikudepotserver.dataobjects.auto._PkgVersion; import org.haiku.haikudepotserver.job.model.JobService; import org.haiku.haikudepotserver.job.model.JobSnapshot; import org.haiku.haikudepotserver.pkg.model.PkgService; import org.haiku.haikudepotserver.security.model.AuthorizationService; import org.haiku.haikudepotserver.security.model.Permission; import org.haiku.haikudepotserver.support.VersionCoordinates; import org.haiku.haikudepotserver.userrating.model.UserRatingDerivationJobSpecification; import org.haiku.haikudepotserver.userrating.model.UserRatingSearchSpecification; import org.haiku.haikudepotserver.userrating.model.UserRatingService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; import java.util.Collections; import java.util.List; import java.util.Optional; import java.util.UUID; import java.util.stream.Collectors; @Component @AutoJsonRpcServiceImpl(additionalPaths = "/api/v1/userrating") // TODO; legacy path - remove public class UserRatingApiImpl extends AbstractApiImpl implements UserRatingApi { protected static Logger LOGGER = LoggerFactory.getLogger(UserApiImpl.class); private ServerRuntime serverRuntime; private AuthorizationService authorizationService; private JobService jobService; private UserRatingService userRatingService; private PkgService pkgService; public UserRatingApiImpl(ServerRuntime serverRuntime, AuthorizationService authorizationService, JobService jobService, UserRatingService userRatingService, PkgService pkgService) { this.serverRuntime = Preconditions.checkNotNull(serverRuntime); this.authorizationService = Preconditions.checkNotNull(authorizationService); this.jobService = Preconditions.checkNotNull(jobService); this.userRatingService = Preconditions.checkNotNull(userRatingService); this.pkgService = Preconditions.checkNotNull(pkgService); } private AbstractGetUserRatingResult.User createUser(User user) { Preconditions.checkNotNull(user); AbstractGetUserRatingResult.User result = new AbstractUserRatingResult.User(); result.nickname = user.getNickname(); return result; } private AbstractUserRatingResult.PkgVersion createPkgVersion(PkgVersion pkgVersion) { Preconditions.checkNotNull(pkgVersion); AbstractUserRatingResult.PkgVersion result = new AbstractUserRatingResult.PkgVersion(); result.repositoryCode = pkgVersion.getRepositorySource().getRepository().getCode(); result.architectureCode = pkgVersion.getArchitecture().getCode(); result.major = pkgVersion.getMajor(); result.minor = pkgVersion.getMinor(); result.micro = pkgVersion.getMicro(); result.preRelease = pkgVersion.getPreRelease(); result.revision = pkgVersion.getRevision(); result.pkg = new AbstractUserRatingResult.Pkg(); result.pkg.name = pkgVersion.getPkg().getName(); return result; } /** * <p>Some of the result subclass from this abstract one. This convenience method will full the * abstract result with the data from the user rating so that this code does not need to be * repeated across multiple API methods.</p> */ private void fillAbstractGetUserRatingResult(UserRating userRating, AbstractGetUserRatingResult result) { Preconditions.checkNotNull(userRating); Preconditions.checkNotNull(result); result.active = userRating.getActive(); result.code = userRating.getCode(); result.naturalLanguageCode = userRating.getNaturalLanguage().getCode(); result.user = createUser(userRating.getUser()); result.rating = userRating.getRating(); result.comment = userRating.getComment(); result.modifyTimestamp = userRating.getModifyTimestamp().getTime(); result.createTimestamp = userRating.getCreateTimestamp().getTime(); result.pkgVersion = createPkgVersion(userRating.getPkgVersion()); if (null != userRating.getUserRatingStability()) { result.userRatingStabilityCode = userRating.getUserRatingStability().getCode(); } } @Override public DeriveAndStoreUserRatingForPkgResult deriveAndStoreUserRatingForPkg( DeriveAndStoreUserRatingForPkgRequest request) throws ObjectNotFoundException { Preconditions.checkNotNull(request); Preconditions.checkState(!Strings.isNullOrEmpty(request.pkgName)); final ObjectContext context = serverRuntime.newContext(); if (!authorizationService.check(context, tryObtainAuthenticatedUser(context).orElse(null), null, Permission.USERRATING_DERIVEANDSTOREFORPKG)) { throw new AuthorizationFailureException(); } Pkg.tryGetByName(context, request.pkgName) .orElseThrow(() -> new ObjectNotFoundException(Pkg.class.getSimpleName(), request.pkgName)); jobService.submit(new UserRatingDerivationJobSpecification(request.pkgName), JobSnapshot.COALESCE_STATUSES_QUEUED); return new DeriveAndStoreUserRatingForPkgResult(); } @Override public DeriveAndStoreUserRatingsForAllPkgsResult deriveAndStoreUserRatingsForAllPkgs( DeriveAndStoreUserRatingsForAllPkgsResult request) { Preconditions.checkNotNull(request); final ObjectContext context = serverRuntime.newContext(); if (!authorizationService.check(context, tryObtainAuthenticatedUser(context).orElse(null), null, Permission.USERRATING_DERIVEANDSTOREFORPKG)) { throw new AuthorizationFailureException(); } jobService.submit(new UserRatingDerivationJobSpecification(), JobSnapshot.COALESCE_STATUSES_QUEUED); LOGGER.info("did enqueue request to derive and store user ratings for all packages"); return new DeriveAndStoreUserRatingsForAllPkgsResult(); } @Override public GetUserRatingResult getUserRating(GetUserRatingRequest request) throws ObjectNotFoundException { Preconditions.checkNotNull(request); Preconditions.checkState(!Strings.isNullOrEmpty(request.code)); final ObjectContext context = serverRuntime.newContext(); UserRating userRating = UserRating.tryGetByCode(context, request.code) .orElseThrow(() -> new ObjectNotFoundException(UserRating.class.getSimpleName(), request.code)); GetUserRatingResult result = new GetUserRatingResult(); fillAbstractGetUserRatingResult(userRating, result); return result; } @Override public GetUserRatingByUserAndPkgVersionResult getUserRatingByUserAndPkgVersion( GetUserRatingByUserAndPkgVersionRequest request) throws ObjectNotFoundException { Preconditions.checkNotNull(request); Preconditions.checkState(!Strings.isNullOrEmpty(request.pkgName), "the package name must be supplied"); Preconditions.checkState(!Strings.isNullOrEmpty(request.userNickname), "the user nickname must be supplied"); Preconditions.checkState(!Strings.isNullOrEmpty(request.pkgVersionArchitectureCode), "the pkg version architecture code must be supplied"); Preconditions.checkState(!Strings.isNullOrEmpty(request.pkgVersionMajor), "the package version major code must be supplied"); Preconditions.checkArgument(!Strings.isNullOrEmpty(request.repositoryCode), "the repository code should be supplied"); final ObjectContext context = serverRuntime.newContext(); Architecture architecture = getArchitecture(context, request.pkgVersionArchitectureCode); User user = User.tryGetByNickname(context, request.userNickname) .orElseThrow(() -> new ObjectNotFoundException(User.class.getSimpleName(), request.userNickname)); Pkg pkg = Pkg.tryGetByName(context, request.pkgName) .orElseThrow(() -> new ObjectNotFoundException(Pkg.class.getSimpleName(), request.pkgName)); Repository repository = getRepository(context, request.repositoryCode); VersionCoordinates versionCoordinates = new VersionCoordinates(request.pkgVersionMajor, request.pkgVersionMinor, request.pkgVersionMicro, request.pkgVersionPreRelease, request.pkgVersionRevision); Optional<PkgVersion> pkgVersionOptional = PkgVersion.getForPkg(context, pkg, repository, architecture, versionCoordinates); if (!pkgVersionOptional.isPresent() || !pkgVersionOptional.get().getActive()) { throw new ObjectNotFoundException(PkgVersion.class.getSimpleName(), request.pkgName + "@" + versionCoordinates.toString()); } Optional<UserRating> userRatingOptional = UserRating.getByUserAndPkgVersion(context, user, pkgVersionOptional.get()); if (!userRatingOptional.isPresent()) { throw new ObjectNotFoundException(UserRating.class.getSimpleName(), ""); } GetUserRatingByUserAndPkgVersionResult result = new GetUserRatingByUserAndPkgVersionResult(); fillAbstractGetUserRatingResult(userRatingOptional.get(), result); return result; } @Override public CreateUserRatingResult createUserRating(CreateUserRatingRequest request) throws ObjectNotFoundException { Preconditions.checkNotNull(request); Preconditions.checkState(!Strings.isNullOrEmpty(request.naturalLanguageCode)); Preconditions.checkState(!Strings.isNullOrEmpty(request.pkgName)); Preconditions.checkState(!Strings.isNullOrEmpty(request.pkgVersionArchitectureCode)); Preconditions.checkArgument(!Strings.isNullOrEmpty(request.repositoryCode), "the repository code should be supplied"); Preconditions.checkArgument(null == request.rating || request.rating >= UserRating.MIN_USER_RATING, "the user rating " + request.rating + " is less than the minimum allowed of " + UserRating.MIN_USER_RATING); Preconditions.checkArgument(null == request.rating || request.rating <= UserRating.MAX_USER_RATING, "the user rating " + request.rating + " is greater than the maximum allowed of " + UserRating.MIN_USER_RATING); if (null != request.comment) { request.comment = Strings.emptyToNull(request.comment.trim()); } // check to see that the user has not tried to create a user rating with essentially nothing // in it. if (Strings.isNullOrEmpty(request.comment) && null == request.userRatingStabilityCode && null == request.rating) { throw new IllegalStateException( "it is not possible to create a user rating with no meaningful rating data"); } final ObjectContext context = serverRuntime.newContext(); Optional<Pkg> pkgOptional = Pkg.tryGetByName(context, request.pkgName); if (!pkgOptional.isPresent()) { throw new ObjectNotFoundException(Pkg.class.getSimpleName(), request.pkgName); } Architecture architecture = getArchitecture(context, request.pkgVersionArchitectureCode); NaturalLanguage naturalLanguage = getNaturalLanguage(context, request.naturalLanguageCode); Repository repository = getRepository(context, request.repositoryCode); User user = User.tryGetByNickname(context, request.userNickname) .orElseThrow(() -> new ObjectNotFoundException(User.class.getSimpleName(), request.userNickname)); Optional<UserRatingStability> userRatingStabilityOptional = Optional.empty(); if (null != request.userRatingStabilityCode) { userRatingStabilityOptional = UserRatingStability.getByCode(context, request.userRatingStabilityCode); if (!userRatingStabilityOptional.isPresent()) { throw new ObjectNotFoundException(UserRatingStability.class.getSimpleName(), request.userRatingStabilityCode); } } // check authorization Optional<User> authenticatedUserOptional = tryObtainAuthenticatedUser(context); if (!authenticatedUserOptional.isPresent()) { LOGGER.warn("only authenticated users are able to add user ratings"); throw new AuthorizationFailureException(); } if (!authenticatedUserOptional.get().getNickname().equals(user.getNickname())) { LOGGER.warn("it is not allowed to add a user rating for another user"); throw new AuthorizationFailureException(); } if (!authorizationService.check(context, authenticatedUserOptional.orElse(null), pkgOptional.get(), Permission.PKG_CREATEUSERRATING)) { throw new AuthorizationFailureException(); } // check the package version Optional<PkgVersion> pkgVersionOptional; switch (request.pkgVersionType) { case LATEST: pkgVersionOptional = pkgService.getLatestPkgVersionForPkg(context, pkgOptional.get(), repository, Collections.singletonList(architecture)); break; case SPECIFIC: pkgVersionOptional = PkgVersion.getForPkg(context, pkgOptional.get(), repository, architecture, new VersionCoordinates(request.pkgVersionMajor, request.pkgVersionMinor, request.pkgVersionMicro, request.pkgVersionPreRelease, request.pkgVersionRevision)); break; default: throw new IllegalStateException("unsupported pkg version type; " + request.pkgVersionType.name()); } if (!pkgVersionOptional.isPresent()) { throw new ObjectNotFoundException(PkgVersion.class.getSimpleName(), pkgOptional.get().getName() + "_" + request.pkgVersionType.name()); } if (!pkgVersionOptional.get().getIsLatest()) { throw new IllegalStateException( "it is not possible to add a user rating to a version other than the latest version."); } List<UserRating> legacyUserRatings = UserRating.findByUserAndPkg(context, user, pkgOptional.get()); for (UserRating legacyUserRating : legacyUserRatings) { if (legacyUserRating.getPkgVersion().equals(pkgVersionOptional.get())) { throw new IllegalStateException("an existing user rating '" + legacyUserRating.getCode() + "' already exists for this package version; it is not possible to add another one"); } } // now create the new user rating. UserRating userRating = context.newObject(UserRating.class); userRating.setCode(UUID.randomUUID().toString()); userRating.setUserRatingStability(userRatingStabilityOptional.orElse(null)); userRating.setUser(user); userRating.setComment(Strings.emptyToNull(Strings.nullToEmpty(request.comment).trim())); userRating.setPkgVersion(pkgVersionOptional.get()); userRating.setNaturalLanguage(naturalLanguage); userRating.setRating(request.rating); context.commitChanges(); LOGGER.info("did create user rating for user {} on package {}", user.toString(), pkgOptional.get().toString()); return new CreateUserRatingResult(userRating.getCode()); } @Override public UpdateUserRatingResult updateUserRating(UpdateUserRatingRequest request) throws ObjectNotFoundException { Preconditions.checkNotNull(request); Preconditions.checkState(!Strings.isNullOrEmpty(request.code)); Preconditions.checkNotNull(request.filter); final ObjectContext context = serverRuntime.newContext(); UserRating userRating = UserRating.tryGetByCode(context, request.code) .orElseThrow(() -> new ObjectNotFoundException(UserRating.class.getSimpleName(), request.code)); User authenticatedUser = obtainAuthenticatedUser(context); if (!authorizationService.check(context, authenticatedUser, userRating, Permission.USERRATING_EDIT)) { LOGGER.error("unable to edit the userrating as {}", authenticatedUser.toString()); throw new AuthorizationFailureException(); } for (UpdateUserRatingRequest.Filter filter : request.filter) { switch (filter) { case ACTIVE: if (null == request.active) { throw new IllegalStateException("the active flag must be supplied to configure this field"); } userRating.setActive(request.active); break; case COMMENT: if (null != request.comment) { userRating.setComment(Strings.emptyToNull(request.comment.trim())); } else { userRating.setComment(null); } break; case NATURALLANGUAGE: NaturalLanguage naturalLanguage = getNaturalLanguage(context, request.naturalLanguageCode); userRating.setNaturalLanguage(naturalLanguage); break; case RATING: userRating.setRating(request.rating); break; case USERRATINGSTABILITY: if (null == request.userRatingStabilityCode) { userRating.setUserRatingStability(null); } else { userRating.setUserRatingStability( UserRatingStability.getByCode(context, request.userRatingStabilityCode) .orElseThrow(() -> new ObjectNotFoundException( UserRatingStability.class.getSimpleName(), request.userRatingStabilityCode))); } break; default: throw new IllegalStateException("the filter; " + filter.name() + " is not handled"); } } LOGGER.info("did update user rating for user {} on package {}", userRating.getUser().toString(), userRating.getPkgVersion().getPkg().toString()); context.commitChanges(); return new UpdateUserRatingResult(); } @Override public SearchUserRatingsResult searchUserRatings(SearchUserRatingsRequest request) throws ObjectNotFoundException { Preconditions.checkNotNull(request); Preconditions.checkNotNull(request.limit); Preconditions.checkState(request.limit > 0); final ObjectContext context = serverRuntime.newContext(); UserRatingSearchSpecification searchSpecification = new UserRatingSearchSpecification(); if (null != request.daysSinceCreated) { searchSpecification.setDaysSinceCreated(request.daysSinceCreated.intValue()); } Architecture architecture = null; if (null != request.pkgVersionArchitectureCode) { architecture = getArchitecture(context, request.pkgVersionArchitectureCode); } Optional<Pkg> pkgOptional = Optional.empty(); if (null != request.pkgName) { pkgOptional = Pkg.tryGetByName(context, request.pkgName); if (!pkgOptional.isPresent()) { throw new ObjectNotFoundException(Pkg.class.getSimpleName(), request.pkgName); } } Optional<Repository> repositoryOptional = Optional.empty(); if (!Strings.isNullOrEmpty(request.repositoryCode)) { searchSpecification.setRepository(getRepository(context, request.repositoryCode)); } // if there is a major version specified then we must be requesting a specific package version, // otherwise we will constrain based on the architecture and/or the package name. if (null != request.pkgVersionMajor) { if (!repositoryOptional.isPresent()) { throw new IllegalStateException("the repository is required when a pkg version is specified"); } if (!pkgOptional.isPresent()) { throw new IllegalStateException("the pkg is required when a pkg version is specified"); } if (null == architecture) { throw new IllegalStateException("the architecture is required when a pkg version is specified"); } PkgVersion pkgVersion = PkgVersion .getForPkg(context, pkgOptional.get(), repositoryOptional.get(), architecture, new VersionCoordinates(request.pkgVersionMajor, request.pkgVersionMinor, request.pkgVersionMicro, request.pkgVersionPreRelease, request.pkgVersionRevision)) .filter(_PkgVersion::getActive) .orElseThrow(() -> new ObjectNotFoundException(PkgVersion.class.getSimpleName(), "")); searchSpecification.setPkgVersion(pkgVersion); } else { searchSpecification.setArchitecture(architecture); if (pkgOptional.isPresent()) { searchSpecification.setPkg(pkgOptional.get()); } } if (null != request.userNickname) { Optional<User> userOptional = User.tryGetByNickname(context, request.userNickname); if (!userOptional.isPresent()) { throw new ObjectNotFoundException(User.class.getSimpleName(), request.userNickname); } searchSpecification.setUser(userOptional.get()); } searchSpecification.setLimit(request.limit); searchSpecification.setOffset(request.offset); List<UserRating> foundUserRatings = userRatingService.search(context, searchSpecification); final SearchUserRatingsResult result = new SearchUserRatingsResult(); result.total = userRatingService.total(context, searchSpecification); result.items = foundUserRatings.stream().map(ur -> { SearchUserRatingsResult.UserRating resultUserRating = new SearchUserRatingsResult.UserRating(); resultUserRating.active = ur.getActive(); resultUserRating.code = ur.getCode(); resultUserRating.comment = ur.getComment(); resultUserRating.createTimestamp = ur.getCreateTimestamp().getTime(); resultUserRating.modifyTimestamp = ur.getModifyTimestamp().getTime(); resultUserRating.userRatingStabilityCode = null != ur.getUserRatingStability() ? ur.getUserRatingStability().getCode() : null; resultUserRating.naturalLanguageCode = ur.getNaturalLanguage().getCode(); resultUserRating.pkgVersion = createPkgVersion(ur.getPkgVersion()); resultUserRating.rating = ur.getRating(); resultUserRating.user = createUser(ur.getUser()); return resultUserRating; }).collect(Collectors.toList()); return result; } @Override public RemoveUserRatingResult removeUserRating(RemoveUserRatingRequest request) throws ObjectNotFoundException { Preconditions.checkNotNull(request); Preconditions.checkState(StringUtils.isNotBlank(request.code)); final ObjectContext context = serverRuntime.newContext(); UserRating userRating = UserRating.tryGetByCode(context, request.code) .orElseThrow(() -> new ObjectNotFoundException(UserRating.class.getSimpleName(), request.code)); User authenticatedUser = obtainAuthenticatedUser(context); if (!authorizationService.check(context, authenticatedUser, userRating, Permission.USERRATING_EDIT)) { LOGGER.error("unable to delete the userrating as {}", authenticatedUser.toString()); throw new AuthorizationFailureException(); } userRatingService.removeUserRatingAtomically(userRating.getCode()); return new RemoveUserRatingResult(); } }