/* * Copyright 2006-2014 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.jrecruiter.service.impl; import static com.rosaloves.bitlyj.Bitly.as; import static com.rosaloves.bitlyj.Bitly.shorten; import java.net.URI; import java.net.URISyntaxException; import java.util.Calendar; import java.util.Date; import java.util.List; import java.util.Map; import javax.validation.constraints.NotNull; import org.jrecruiter.common.AcegiUtil; import org.jrecruiter.common.ApiKeysHolder; import org.jrecruiter.common.CalendarUtils; import org.jrecruiter.common.CollectionUtils; import org.jrecruiter.common.Constants.Roles; import org.jrecruiter.common.Constants.ServerActions; import org.jrecruiter.dao.ConfigurationDao; import org.jrecruiter.dao.IndustryDao; import org.jrecruiter.dao.JobCountPerDayDao; import org.jrecruiter.dao.JobDao; import org.jrecruiter.dao.RegionDao; import org.jrecruiter.dao.StatisticDao; import org.jrecruiter.dao.UserDao; import org.jrecruiter.model.Configuration; import org.jrecruiter.model.Industry; import org.jrecruiter.model.Job; import org.jrecruiter.model.ServerSettings; import org.jrecruiter.model.Statistic; import org.jrecruiter.model.User; import org.jrecruiter.model.statistics.JobCountPerDay; import org.jrecruiter.service.JobService; import org.jrecruiter.service.NotificationService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; import org.jrecruiter.model.Region; import com.rosaloves.bitlyj.Url; /** * @author Gunnar Hillert */ @Transactional @Service("jobService") public class JobServiceImpl implements JobService { /** Initialize Logging. */ private final static Logger LOGGER = LoggerFactory.getLogger(JobServiceImpl.class); /** Job Dao. */ private @Autowired JobDao jobDao; /** Job Count Per Day Dao. */ private @Autowired JobCountPerDayDao jobCountPerDayDao; /** User Dao. */ private @Autowired UserDao userDao; /** Industry Dao. */ private @Autowired IndustryDao industryDao; /** User Region Dao. */ private @Autowired RegionDao regionDao; /** Statistic Dao. */ private @Autowired StatisticDao statisticDao; /** Settings Dao. */ private @Autowired ConfigurationDao configurationDao; private @Autowired NotificationService notificationService; private @Autowired ServerSettings serverSettings; private @Autowired ApiKeysHolder apiKeysHolder; //~~~~~Business Methods~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ /** {@inheritDoc} */ @Override @Transactional(readOnly = true, propagation=Propagation.SUPPORTS) public List<Job> getJobs() { return jobDao.getAllJobs(); } /** {@inheritDoc} */ @Override @Transactional(readOnly = true, propagation=Propagation.SUPPORTS) public List<Job> getJobs(Integer pageSize, Integer pageNumber, Map<String, String> sortOrders, Map<String, String> jobFilters) { return jobDao.getJobs(pageSize, pageNumber, sortOrders, jobFilters); } /** {@inheritDoc} */ @Override @Transactional(readOnly = true, propagation=Propagation.SUPPORTS) public Long getJobsCount() { return jobDao.getJobsCount(); } /** {@inheritDoc} */ @Override @Transactional(readOnly = true, propagation=Propagation.SUPPORTS) public List<Job> getUsersJobs(final String username) { List <Job> jobs = null; User user = userDao.getUser(username); boolean administrator = false; if (AcegiUtil.containsRole(user.getAuthorities(), Roles.ADMIN.name())) { administrator = true; } if (administrator) { jobs = jobDao.getAllJobs(); } else { jobs = jobDao.getAllUserJobs(username); } return jobs; } /** {@inheritDoc} */ @Override @Transactional(readOnly = true, propagation=Propagation.SUPPORTS) public List<Job> getUsersJobsForStatistics(final String username) { final User user = userDao.getUser(username); if (user == null) { throw new IllegalArgumentException("No user found for username " + username); } if (AcegiUtil.hasRole(Roles.ADMIN.name())) { return jobDao.getAll(); } return jobDao.getAllUserJobsForStatistics(user.getId()); } /** {@inheritDoc} */ @Override @Transactional(readOnly = true, propagation=Propagation.SUPPORTS) public List<Job> getUsersJobsForStatistics(String username, Integer maxResult) { final User user = userDao.getUser(username); if (user == null) { throw new IllegalArgumentException("No user found for username " + username); } boolean administrator = false; if (AcegiUtil.hasRole(Roles.ADMIN.name())) { administrator = true; } return jobDao.getUsersJobsForStatistics(user.getId(), maxResult, administrator); } /** {@inheritDoc} */ @Override public Job addJob(final Job job) { if (job.getStatistic() == null) { Statistic statistic = new Statistic(job.getId(), Long.valueOf(0), null, Long.valueOf(0)); statistic.setJob(job); job.setStatistic(statistic); } final Job savedJob = jobDao.save(job); saveJobStatistics(job); return savedJob; } /** {@inheritDoc} */ @Override public void addJobAndSendToMailingList(final Job job) { final Job savedJob = this.addJob(job); final Map<String, Object> context = CollectionUtils.getHashMap(); context.put("jobId", savedJob.getId()); context.put("jobTitle", savedJob.getJobTitle()); context.put("businessLocation", savedJob.getRegionOther()); context.put("businessName", savedJob.getBusinessName()); context.put("description", savedJob.getDescription()); context.put("jobRestrictions", savedJob.getJobRestrictions()); context.put("updateDate", savedJob.getUpdateDate()); context.put("businessEmail", savedJob.getBusinessEmail()); context.put("serverAddress", serverSettings.getServerAddress()); final EmailRequest emailRequest = new EmailRequest( ((Configuration) this.getJRecruiterSetting("mail.jobposting.email")).getMessageText(), savedJob.getJobTitle(), context, "add-job"); notificationService.sendEmail(emailRequest); final String tweetMessage = "New Job: " + savedJob.getJobTitle() + " @ " + savedJob.getBusinessName(); final URI uri = createShortenedJobDetailUrl(savedJob); notificationService.sendTweetToTwitter(tweetMessage + ": " + uri.toString()); } private URI createShortenedJobDetailUrl(final Job job) { final String jobUrlString = this.serverSettings.getServerAddress() + ServerActions.JOB_DETAIL.getPath() + "?jobId=" + job.getId(); final URI jobUri; try { jobUri = new URI(jobUrlString); } catch (URISyntaxException e) { throw new IllegalStateException("Cannot create URI for " + jobUrlString); } if (this.apiKeysHolder.isBitlyEnabled()) { return this.shortenUrl(jobUri.toString()); } else { return jobUri; } } /** {@inheritDoc} */ public URI shortenUrl(final String urlAsString) { //FIXME Handle this better Url url = as(apiKeysHolder.getBitlyUsername(), apiKeysHolder.getBitlyPassword()) .call(shorten(urlAsString)); try { return new URI(url.getShortUrl()); } catch (URISyntaxException e) { throw new IllegalArgumentException( String.format("Please provide a valid URI - %s is not valid", urlAsString)); } } /** {@inheritDoc} */ @Override public void updateJob(final Job job) { if (job.getStatistic() == null) { Statistic statistic = new Statistic(job.getId(), Long.valueOf(0), null, Long.valueOf(0)); statistic.setJob(job); job.setStatistic(statistic); } final Job savedJob = jobDao.save(job); saveJobStatistics(savedJob); String tweetMessage = "Job Update: " + job.getJobTitle() + " @ " + job.getBusinessName(); final URI uri = createShortenedJobDetailUrl(job); tweetMessage = tweetMessage + ": " + uri.toString(); // notificationService.sendTweetToTwitter(tweetMessage); } /** {@inheritDoc} */ public void saveJobStatistics(Job job) { if (job.getId() == null) { JobCountPerDay jobCountPerDay = jobCountPerDayDao.getByDate(job.getRegistrationDate()); if (jobCountPerDay == null) { jobCountPerDay = new JobCountPerDay(); jobCountPerDay.setJobDate(job.getRegistrationDate()); jobCountPerDay.setNumberOfJobsDeleted(Long.valueOf(0)); jobCountPerDay.setNumberOfJobsPosted(Long.valueOf(1)); jobCountPerDay.setTotalNumberOfJobs(jobDao.getJobsCount()); jobCountPerDayDao.save(jobCountPerDay); } else { jobCountPerDay.setNumberOfJobsPosted(jobCountPerDay.getNumberOfJobsPosted() + 1); jobCountPerDay.setTotalNumberOfJobs(jobDao.getJobsCount() + 1); jobCountPerDayDao.save(jobCountPerDay); } } } /** {@inheritDoc} */ @Override @Transactional(readOnly = true, propagation=Propagation.SUPPORTS) public Job getJobForId(final Long jobId) { return jobDao.get(jobId); } /* (non-Javadoc) * @see org.jrecruiter.service.JobService#getJobForUniversalId(java.lang.Long) */ @Override public Job getJobForUniversalId(final String universalId) { return jobDao.getForUniversalId(universalId); } /** {@inheritDoc} */ @Override @Transactional(readOnly = true, propagation=Propagation.SUPPORTS) public List<Job> searchByKeyword(final String keyword) { return jobDao.searchByKeyword(keyword); } /** {@inheritDoc} */ @Override public void deleteJobForId(final Long jobId) { jobDao.remove(jobId); JobCountPerDay jobCountPerDay = jobCountPerDayDao.getByDate(new Date()); if (jobCountPerDay == null) { jobCountPerDay = new JobCountPerDay(); jobCountPerDay.setJobDate(new Date()); } jobCountPerDay.setNumberOfJobsDeleted(Long.valueOf(1)); jobCountPerDay.setNumberOfJobsPosted(Long.valueOf(0)); jobCountPerDay.setTotalNumberOfJobs(jobDao.getJobsCount()); jobCountPerDayDao.save(jobCountPerDay); } /** {@inheritDoc} */ @Override @Transactional(readOnly = true, propagation=Propagation.SUPPORTS) public List<Configuration> getJRecruiterSettings() { return configurationDao.getAll(); } /** {@inheritDoc} */ @Override public Configuration getJRecruiterSetting(final String key) { return configurationDao.get(key); } /** {@inheritDoc} */ @Override public void saveJRecruiterSetting(final Configuration configuration) { configurationDao.save(configuration); } /** {@inheritDoc} */ @Override @Transactional(readOnly = true, propagation=Propagation.SUPPORTS) public List<Industry> getIndustries() { return this.industryDao.getAllIndustriesOrdered(); } /** {@inheritDoc} */ @Override @Transactional(readOnly = true, propagation=Propagation.SUPPORTS) public List<Region> getRegions() { return this.regionDao.getAllRegionsOrdered(); } /** {@inheritDoc} */ @Override public void updateJobStatistic(Statistic statistics) { this.statisticDao.save(statistics); } /** {@inheritDoc} */ @Override public void reindexSearch() { jobDao.reindexSearch(); } /** {@inheritDoc} */ @Override @Transactional(readOnly = true, propagation=Propagation.SUPPORTS) public Industry getIndustry(Long industryId) { return industryDao.get(industryId); } /** {@inheritDoc} */ @Override @Transactional(readOnly = true, propagation=Propagation.SUPPORTS) public Region getRegion(Long regionId) { return regionDao.get(regionId); } /** {@inheritDoc} */ @Override @Transactional(readOnly = true, propagation=Propagation.SUPPORTS) public List<JobCountPerDay> getJobCountPerDayAndPeriod(final Date fromDate, final Date toDate) { //Make sure we have values at least for today and the previous day. this.updateJobCountPerDays(); final List<JobCountPerDay> jobCountPerDays = jobCountPerDayDao.getJobCountPerDayAndPeriod(fromDate, toDate); return jobCountPerDays; } /** {@inheritDoc} */ @Override public void updateJobCountPerDays() { this.updateJobCountPerDays(CalendarUtils.getCalendarWithoutTime()); } /** {@inheritDoc} */ @Override public void updateJobCountPerDays(final Calendar asOfDay) { final Calendar today = CalendarUtils.getCalendarWithoutTime(); today.setTime(asOfDay.getTime()); final Calendar yesterday = CalendarUtils.getCalendarWithoutTime(); yesterday.add(Calendar.DAY_OF_YEAR, -1); final List<JobCountPerDay> latestTwoJobCountPerDays = jobCountPerDayDao.getLatestTwoJobCounts(); //If nothing exists yet, create an entry with zero jobs. if (latestTwoJobCountPerDays.isEmpty()) { jobCountPerDayDao.save(new JobCountPerDay(yesterday.getTime(), 0L, 0L, 0L)); } boolean containsToday = false; //Let's make sure we have a value for today for (JobCountPerDay jobCountPerDay : latestTwoJobCountPerDays) { if (today.getTime().equals(jobCountPerDay.getJobDate())) { containsToday = true; break; } } if (!containsToday) { //We need to create a value for today final Long totalNumberOfJobs = this.getJobsCount(); jobCountPerDayDao.save(new JobCountPerDay(today.getTime(), 0L, 0L, totalNumberOfJobs)); } } /** {@inheritDoc} */ @Override public void removeOldJobs(final @NotNull Integer days) { final Calendar cal = CalendarUtils.getCalendarWithoutTime(); cal.add(Calendar.DAY_OF_YEAR, days.intValue() * -1); final List<Job> jobs = jobDao.getJobsByUpdateDate(cal); LOGGER.info("Found " + jobs.size() + " jobs that are eligible for removal."); if (!jobs.isEmpty()) { final long jobsCount = jobDao.getJobsCount(); for (final Job job : jobs) { jobDao.remove(job.getId()); } JobCountPerDay jobCountPerDay = jobCountPerDayDao.getByDate(new Date()); if (jobCountPerDay == null) { jobCountPerDay = new JobCountPerDay(); jobCountPerDay.setJobDate(new Date()); } jobCountPerDay.setNumberOfJobsDeleted(Long.valueOf(jobs.size())); jobCountPerDay.setNumberOfJobsPosted(Long.valueOf(0)); jobCountPerDay.setTotalNumberOfJobs(jobsCount - jobs.size() ); jobCountPerDay.setAutomaticallyCleaned(Boolean.TRUE); jobCountPerDayDao.save(jobCountPerDay); } } }