001 /* 002 * Sonar, open source software quality management tool. 003 * Copyright (C) 2008-2012 SonarSource 004 * mailto:contact AT sonarsource DOT com 005 * 006 * Sonar is free software; you can redistribute it and/or 007 * modify it under the terms of the GNU Lesser General Public 008 * License as published by the Free Software Foundation; either 009 * version 3 of the License, or (at your option) any later version. 010 * 011 * Sonar is distributed in the hope that it will be useful, 012 * but WITHOUT ANY WARRANTY; without even the implied warranty of 013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 014 * Lesser General Public License for more details. 015 * 016 * You should have received a copy of the GNU Lesser General Public 017 * License along with Sonar; if not, write to the Free Software 018 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 019 */ 020 package org.sonar.core.resource; 021 022 import org.apache.commons.lang.ArrayUtils; 023 import org.apache.commons.lang.StringUtils; 024 import org.apache.ibatis.session.ResultContext; 025 import org.apache.ibatis.session.ResultHandler; 026 import org.apache.ibatis.session.SqlSession; 027 import org.sonar.api.resources.Qualifiers; 028 import org.sonar.api.resources.Scopes; 029 import org.sonar.core.persistence.MyBatis; 030 031 public class ResourceIndexerDao { 032 033 public static final int MINIMUM_KEY_SIZE = 3; 034 035 // The scopes and qualifiers that are not in the following constants are not indexed at all. 036 // Directories and packages are explicitly excluded. 037 private static final String[] RENAMABLE_QUALIFIERS = {Qualifiers.PROJECT, Qualifiers.MODULE, Qualifiers.VIEW, Qualifiers.SUBVIEW}; 038 private static final String[] RENAMABLE_SCOPES = {Scopes.PROJECT}; 039 private static final String[] NOT_RENAMABLE_QUALIFIERS = {Qualifiers.FILE, Qualifiers.UNIT_TEST_FILE, Qualifiers.CLASS}; 040 private static final String[] NOT_RENAMABLE_SCOPES = {Scopes.FILE}; 041 042 private final MyBatis mybatis; 043 044 public ResourceIndexerDao(MyBatis mybatis) { 045 this.mybatis = mybatis; 046 } 047 048 /** 049 * This method is reentrant. It can be executed even if the project is already indexed. 050 */ 051 public ResourceIndexerDao indexProject(final int rootProjectId) { 052 SqlSession session = mybatis.openBatchSession(); 053 try { 054 ResourceIndexerMapper mapper = session.getMapper(ResourceIndexerMapper.class); 055 doIndexProject(rootProjectId, session, mapper); 056 session.commit(); 057 return this; 058 059 } finally { 060 MyBatis.closeQuietly(session); 061 } 062 } 063 064 /** 065 * This method is reentrant. It can be executed even if some projects are already indexed. 066 */ 067 public ResourceIndexerDao indexProjects() { 068 final SqlSession session = mybatis.openBatchSession(); 069 try { 070 final ResourceIndexerMapper mapper = session.getMapper(ResourceIndexerMapper.class); 071 session.select("selectRootProjectIds", /* workaround to get booleans */ResourceIndexerQuery.create(), new ResultHandler() { 072 public void handleResult(ResultContext context) { 073 Integer rootProjectId = (Integer) context.getResultObject(); 074 doIndexProject(rootProjectId, session, mapper); 075 session.commit(); 076 } 077 }); 078 return this; 079 080 } finally { 081 MyBatis.closeQuietly(session); 082 } 083 } 084 085 private void doIndexProject(int rootProjectId, SqlSession session, final ResourceIndexerMapper mapper) { 086 // non indexed resources 087 ResourceIndexerQuery query = ResourceIndexerQuery.create() 088 .setNonIndexedOnly(true) 089 .setQualifiers(NOT_RENAMABLE_QUALIFIERS) 090 .setScopes(NOT_RENAMABLE_SCOPES) 091 .setRootProjectId(rootProjectId); 092 093 session.select("selectResources", query, new ResultHandler() { 094 public void handleResult(ResultContext context) { 095 ResourceDto resource = (ResourceDto) context.getResultObject(); 096 doIndex(resource, mapper); 097 } 098 }); 099 100 // some resources can be renamed, so index must be regenerated 101 // -> delete existing rows and create them again 102 query = ResourceIndexerQuery.create() 103 .setNonIndexedOnly(false) 104 .setQualifiers(RENAMABLE_QUALIFIERS) 105 .setScopes(RENAMABLE_SCOPES) 106 .setRootProjectId(rootProjectId); 107 108 session.select("selectResources", query, new ResultHandler() { 109 public void handleResult(ResultContext context) { 110 ResourceDto resource = (ResourceDto) context.getResultObject(); 111 112 mapper.deleteByResourceId(resource.getId()); 113 doIndex(resource, mapper); 114 } 115 }); 116 } 117 118 119 void doIndex(ResourceDto resource, ResourceIndexerMapper mapper) { 120 String key = nameToKey(resource.getName()); 121 if (key.length() >= MINIMUM_KEY_SIZE) { 122 ResourceIndexDto dto = new ResourceIndexDto() 123 .setResourceId(resource.getId()) 124 .setQualifier(resource.getQualifier()) 125 .setRootProjectId(resource.getRootId()) 126 .setNameSize(resource.getName().length()); 127 128 for (int position = 0; position <= key.length() - MINIMUM_KEY_SIZE; position++) { 129 dto.setPosition(position); 130 dto.setKey(StringUtils.substring(key, position)); 131 mapper.insert(dto); 132 } 133 } 134 } 135 136 public boolean indexResource(int id, String name, String qualifier, int rootProjectId) { 137 boolean indexed = false; 138 if (isIndexableQualifier(qualifier)) { 139 SqlSession session = mybatis.openSession(); 140 try { 141 String key = nameToKey(name); 142 if (key.length() >= MINIMUM_KEY_SIZE) { 143 indexed = true; 144 ResourceIndexerMapper mapper = session.getMapper(ResourceIndexerMapper.class); 145 boolean toBeIndexed = sanitizeIndex(id, key, mapper); 146 if (toBeIndexed) { 147 ResourceIndexDto dto = new ResourceIndexDto() 148 .setResourceId(id) 149 .setQualifier(qualifier) 150 .setRootProjectId(rootProjectId) 151 .setNameSize(name.length()); 152 153 for (int position = 0; position <= key.length() - MINIMUM_KEY_SIZE; position++) { 154 dto.setPosition(position); 155 dto.setKey(StringUtils.substring(key, position)); 156 mapper.insert(dto); 157 } 158 session.commit(); 159 } 160 } 161 } finally { 162 MyBatis.closeQuietly(session); 163 } 164 } 165 return indexed; 166 } 167 168 169 /** 170 * Return true if the resource must be indexed, false if the resource is already indexed. 171 * If the resource is indexed with a different key, then this index is dropped and the 172 * resource must be indexed again. 173 */ 174 private boolean sanitizeIndex(int resourceId, String key, ResourceIndexerMapper mapper) { 175 ResourceIndexDto masterIndex = mapper.selectMasterIndexByResourceId(resourceId); 176 if (masterIndex != null && !StringUtils.equals(key, masterIndex.getKey())) { 177 // resource has been renamed -> drop existing indexes 178 mapper.deleteByResourceId(resourceId); 179 masterIndex = null; 180 } 181 return masterIndex == null; 182 } 183 184 static String nameToKey(String input) { 185 return StringUtils.lowerCase(StringUtils.trimToEmpty(input)); 186 } 187 188 static boolean isIndexableQualifier(String qualifier) { 189 return ArrayUtils.contains(RENAMABLE_QUALIFIERS, qualifier) || ArrayUtils.contains(NOT_RENAMABLE_QUALIFIERS, qualifier); 190 } 191 }