001    /*
002     * Sonar, open source software quality management tool.
003     * Copyright (C) 2008-2011 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.ExecutorType;
025    import org.apache.ibatis.session.ResultContext;
026    import org.apache.ibatis.session.ResultHandler;
027    import org.apache.ibatis.session.SqlSession;
028    import org.sonar.api.resources.Qualifiers;
029    import org.sonar.api.resources.Scopes;
030    import org.sonar.core.persistence.MyBatis;
031    
032    public class ResourceIndexerDao {
033    
034      public static final int MINIMUM_KEY_SIZE = 3;
035    
036      // The scopes and qualifiers that are not in the following constants are not indexed at all.
037      // Directories and packages are explicitly excluded.
038      private static final String[] RENAMABLE_QUALIFIERS = {Qualifiers.PROJECT, Qualifiers.MODULE, Qualifiers.VIEW, Qualifiers.SUBVIEW};
039      private static final String[] RENAMABLE_SCOPES = {Scopes.PROJECT};
040      private static final String[] NOT_RENAMABLE_QUALIFIERS = {Qualifiers.FILE, Qualifiers.UNIT_TEST_FILE, Qualifiers.CLASS};
041      private static final String[] NOT_RENAMABLE_SCOPES = {Scopes.FILE};
042    
043      private final MyBatis mybatis;
044    
045      public ResourceIndexerDao(MyBatis mybatis) {
046        this.mybatis = mybatis;
047      }
048    
049      /**
050       * This method is reentrant. It can be executed even if the project is already indexed.
051       */
052      public ResourceIndexerDao indexProject(final int rootProjectId) {
053        SqlSession session = mybatis.openSession(ExecutorType.BATCH);
054        try {
055          ResourceIndexerMapper mapper = session.getMapper(ResourceIndexerMapper.class);
056          doIndexProject(rootProjectId, session, mapper);
057          session.commit();
058          return this;
059    
060        } finally {
061          session.close();
062        }
063      }
064    
065      /**
066       * This method is reentrant. It can be executed even if some projects are already indexed.
067       */
068      public ResourceIndexerDao indexProjects() {
069        final SqlSession session = mybatis.openSession(ExecutorType.BATCH);
070        try {
071          final ResourceIndexerMapper mapper = session.getMapper(ResourceIndexerMapper.class);
072          session.select("selectRootProjectIds", /* workaround to get booleans */ResourceIndexerQuery.create(), new ResultHandler() {
073            public void handleResult(ResultContext context) {
074              Integer rootProjectId = (Integer) context.getResultObject();
075              doIndexProject(rootProjectId, session, mapper);
076              session.commit();
077            }
078          });
079          return this;
080    
081        } finally {
082          session.close();
083        }
084      }
085    
086      private void doIndexProject(int rootProjectId, SqlSession session, final ResourceIndexerMapper mapper) {
087        // non indexed resources
088        ResourceIndexerQuery query = ResourceIndexerQuery.create()
089          .setNonIndexedOnly(true)
090          .setQualifiers(NOT_RENAMABLE_QUALIFIERS)
091          .setScopes(NOT_RENAMABLE_SCOPES)
092          .setRootProjectId(rootProjectId);
093    
094        session.select("selectResources", query, new ResultHandler() {
095          public void handleResult(ResultContext context) {
096            ResourceDto resource = (ResourceDto) context.getResultObject();
097            doIndex(resource, mapper);
098          }
099        });
100    
101        // some resources can be renamed, so index must be regenerated
102        // -> delete existing rows and create them again
103        query = ResourceIndexerQuery.create()
104          .setNonIndexedOnly(false)
105          .setQualifiers(RENAMABLE_QUALIFIERS)
106          .setScopes(RENAMABLE_SCOPES)
107          .setRootProjectId(rootProjectId);
108    
109        session.select("selectResources", query, new ResultHandler() {
110          public void handleResult(ResultContext context) {
111            ResourceDto resource = (ResourceDto) context.getResultObject();
112    
113            mapper.deleteByResourceId(resource.getId());
114            doIndex(resource, mapper);
115          }
116        });
117      }
118    
119    
120      void doIndex(ResourceDto resource, ResourceIndexerMapper mapper) {
121        String key = nameToKey(resource.getName());
122        if (key.length() >= MINIMUM_KEY_SIZE) {
123          ResourceIndexDto dto = new ResourceIndexDto()
124            .setResourceId(resource.getId())
125            .setQualifier(resource.getQualifier())
126            .setRootProjectId(resource.getRootId())
127            .setNameSize(resource.getName().length());
128    
129          for (int position = 0; position <= key.length() - MINIMUM_KEY_SIZE; position++) {
130            dto.setPosition(position);
131            dto.setKey(StringUtils.substring(key, position));
132            mapper.insert(dto);
133          }
134        }
135      }
136    
137      public boolean indexResource(int id, String name, String qualifier, int rootProjectId) {
138        boolean indexed = false;
139        if (isIndexableQualifier(qualifier)) {
140          SqlSession session = mybatis.openSession();
141          try {
142            String key = nameToKey(name);
143            if (key.length() >= MINIMUM_KEY_SIZE) {
144              indexed = true;
145              ResourceIndexerMapper mapper = session.getMapper(ResourceIndexerMapper.class);
146              boolean toBeIndexed = sanitizeIndex(id, key, mapper);
147              if (toBeIndexed) {
148                ResourceIndexDto dto = new ResourceIndexDto()
149                  .setResourceId(id)
150                  .setQualifier(qualifier)
151                  .setRootProjectId(rootProjectId)
152                  .setNameSize(name.length());
153    
154                for (int position = 0; position <= key.length() - MINIMUM_KEY_SIZE; position++) {
155                  dto.setPosition(position);
156                  dto.setKey(StringUtils.substring(key, position));
157                  mapper.insert(dto);
158                }
159                session.commit();
160              }
161            }
162          } finally {
163            session.close();
164          }
165        }
166        return indexed;
167      }
168    
169    
170      /**
171       * Return true if the resource must be indexed, false if the resource is already indexed.
172       * If the resource is indexed with a different key, then this index is dropped and the
173       * resource must be indexed again.
174       */
175      private boolean sanitizeIndex(int resourceId, String key, ResourceIndexerMapper mapper) {
176        ResourceIndexDto masterIndex = mapper.selectMasterIndexByResourceId(resourceId);
177        if (masterIndex != null && !StringUtils.equals(key, masterIndex.getKey())) {
178          // resource has been renamed -> drop existing indexes
179          mapper.deleteByResourceId(resourceId);
180          masterIndex = null;
181        }
182        return masterIndex == null;
183      }
184    
185      static String nameToKey(String input) {
186        return StringUtils.lowerCase(StringUtils.trimToEmpty(input));
187      }
188    
189      static boolean isIndexableQualifier(String qualifier) {
190        return ArrayUtils.contains(RENAMABLE_QUALIFIERS, qualifier) || ArrayUtils.contains(NOT_RENAMABLE_QUALIFIERS, qualifier);
191      }
192    }